home-hosted 0.4.1 → 0.5.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/AGENTS.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  `home-hosted` is a Node 24 / TypeScript harness for self-hosted servers: `up` starts a Hono/srvx
4
4
  panel (default `127.0.0.1:3999`) that supervises the entries in `$HHOSTED_HOME/servers.config.json`
5
- and serves a UI. User docs: `README.md`, `SERVERS.md` (entries and port conflicts),
6
- `NOTIFICATIONS.md`; UI authors: `UI_CREATION.md`.
5
+ and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port conflicts),
6
+ `docs/NOTIFICATIONS.md`; UI authors: `docs/UI_CREATION.md`.
7
7
 
8
8
  State lives only in `$HHOSTED_HOME` (default `~/.home-hosted`): `servers.config.json`,
9
9
  `.control-secrets.json` (0600: password hash, API token hash, Telegram bot token), `.logs/`, `.tls/`,
@@ -37,9 +37,22 @@ exists, so the first release has to be published by hand.
37
37
 
38
38
  ## Architecture (and why)
39
39
 
40
- - `src/cli.ts` — the CLI. Its only static imports are node builtins: `--home`/`--project` must set
41
- `HHOSTED_HOME`/`HHOSTED_PROJECT` before any `#src` module resolves paths, so every `#src` import is
42
- dynamic. `up` re-spawns itself detached as `up --foreground`.
40
+ - `src/cli.ts` — the CLI's thin root. Two things happen before [citty](https://github.com/unjs/citty)
41
+ is asked anything: `--home`/`--project` are peeled off and applied (`src/cli/args.ts`), and the
42
+ curated dispatch — `help`/`version`, `unknown command`, and the `-p 4000` shorthand for `up` — is
43
+ decided, because `#src/helpers/paths.ts` resolves at import time. Its static imports stay node
44
+ builtins, citty and those two path-free local modules; every command is a lazy
45
+ `() => import('#src/cli/<command>')` in citty's `subCommands`, so a command module *may* use static
46
+ `#src` imports (that is the whole point of the pre-pass). citty's `runMain` is deliberately not
47
+ used: it prints its own usage and `console.error`s before `process.exit(1)`, replacing `fail()`'s
48
+ one error shape; the root calls `runCommand` and catches. `src/cli/<command>.ts` is one command per
49
+ file — citty owns dispatch and argument parsing, with `--no-autostart`/`--no-install` declared as
50
+ boolean negations rather than literal arg names. The curated `--help` prose stays in `src/cli.ts`
51
+ because citty cannot generate it. `up` re-spawns itself detached as `up --foreground …`, with the
52
+ argv built from the *parsed* flags (`buildDaemonArgv`) plus this file's own URL as the entry, so it
53
+ works under tsx and from `dist/cli.js` alike. `src/cli/io.ts` is the one place the readline prompts
54
+ and colours live; commands take `prompt`/`style` through that seam (`src/cli/ui-switch.ts` keeps
55
+ `UiSwitchIo`).
43
56
  - `src/index.ts` — `runControlPlane()`: wiring, startup guards (exposure, free port, live run.json),
44
57
  `run.json`, signals. Wiring belongs here and nowhere else.
45
58
  - `src/app.ts` — the Hono root, chained routes only. `/_hh` is mounted *before* the `/api/*` auth
@@ -55,10 +68,10 @@ exists, so the first release has to be published by hand.
55
68
  - `src/providers/` — stateless leaves: `process` (spawn, `terminate`, `terminatePid` for a process we
56
69
  adopted), `port` (probe, holder lookup, `terminatePids`), `proc` (the sampler, plus
57
70
  `processCarriesServerId` for ownership), health-check, host, telegram, archive.
58
- - `src/services/` — stateful orchestration: supervisor, control-server, state, auth + exposure,
59
- dependencies, history, log-buffer/log-files, notifications, host-monitor, backups, tls, ui, plus
60
- `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the prompts stay in
61
- the CLI). It names no server — the scaffold must stay as neutral as the supervisor.
71
+ - `src/services/` — stateful orchestration: supervisor, control-server, config-watch, state,
72
+ auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor, backups,
73
+ tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the
74
+ prompts stay in the CLI). It names no server — the scaffold must stay as neutral as the supervisor.
62
75
  - `src/middleware/auth.ts` — the `/api/*` guard, and `requestIdentity()`, the one place a request's
63
76
  credentials are read: the `hh2_session` cookie or `Authorization: Bearer <api token>`. A token is
64
77
  a first-class credential (same authority as a signed-in browser) and is verified from the secrets
@@ -74,12 +87,20 @@ exists, so the first release has to be published by hand.
74
87
  - `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
75
88
  `capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
76
89
  (used by the release workflow), `dev.mjs` (`pnpm dev`).
77
- - `docs/` — `mockups/*.html`, hand-written UI examples in four directions, and `media/*`: their
78
- screenshots plus the README's `tour.gif`, all regenerated by `pnpm run media`.
90
+ - `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `UI_CREATION.md`), `mockups/*.html`
91
+ (hand-written UI examples in four directions) and `media/*`: their screenshots plus the README's
92
+ `tour.gif`, all regenerated by `pnpm run media`. Only the three `.md` files are published; the
93
+ media is a build artifact.
79
94
 
80
95
  ## Conventions
81
96
 
82
97
  - `#src/*` imports inside `src/`; UIs use `@shared/*`.
98
+ - A single on/off setting is a `ToggleSwitch`; `CheckField` is only for picking items out of a set
99
+ (the restore plan). A checkbox in a `FieldGroup` grid reads as misaligned next to the inputs.
100
+ - A create body carries only what differs from what the entry would inherit — the schema's defaults
101
+ with the panel's `Settings → Server defaults` on top (`inheritBaseline` + `diffFields` in both
102
+ UIs). A value written into `servers.config.json` stops following those defaults, so anything the
103
+ person did not decide stays out; the editor applies the same rule to an edit.
83
104
  - ArkType at every runtime boundary: routes use `validate('json'|'query'|'param', schema)` then
84
105
  `c.req.valid(...)`; ad-hoc payloads use `parseOrThrow`. Schemas reject undeclared keys.
85
106
  - Every failure is a `DetailedError` (`@namesmt/utils`), mapped by `src/helpers/error.ts` into one
@@ -175,8 +196,44 @@ either is a last resort, and never an accidental one.
175
196
  `PATCH /api/settings`, the TLS routes and `/_hh/shutdown` defer with `afterResponse()`.
176
197
  - `Supervisor.start()` sets `starting` synchronously before its first await, and `stop()` sets
177
198
  `stopping` first: overlapping calls would double-spawn or resurrect a stopped process. Tests must
178
- call `supervisor.dispose()`.
199
+ call `supervisor.dispose()`. `stopping` is cleared on **every** exit path of `stopEntry()`: the
200
+ early return for an entry with no child used to leave it set, and since `start()` refuses while it
201
+ is true, one Stop on an already-stopped server made that entry unstartable until a daemon restart.
202
+ - Timers and `void`-ed promises in the supervisor carry a `.catch`: a throw in the tick or in a retry
203
+ is an unhandled rejection, and Node 24 ends the process on one.
204
+ - **`Settings → Server defaults` merge into the nested groups key by key** (`mergeDefaults` in
205
+ `src/config/schema.ts`, used by both the config parser and `validateServer`). A shallow
206
+ `{ ...defaults, ...entry }` lets an entry that decides `restart.maxRetries` silently drop the
207
+ panel's `restart.baseDelayMs` — and the create/edit diff flow depends on inheriting exactly that.
208
+ - The curated CLI dispatch reads only the **flag** forms (`--help`/`-h`, `--version`/`-v`) after a
209
+ command name, never a bare `help`/`version`: that is an option's value (`init --name help`).
179
210
  - Port preflight re-probes after 300 ms — a just-closed listener can still complete a handshake.
211
+ - **The config is re-read whenever the file changes on disk** (`src/services/config-watch.ts` →
212
+ `ConfigStore.reloadFromDisk()`, wired in `src/index.ts`). The watch is on the *directory*, because
213
+ an editor's save is a temporary file renamed over the target — the inode changes, the name does not
214
+ — and a two-second poll backs it up where `fs.watch` is undependable (network mounts). The store
215
+ compares the bytes it last read and the bytes it wrote, so the panel's own saves never reload
216
+ anything, and a revision it cannot read (unparseable JSON included — that path used to fall back to
217
+ an *empty* config until a watcher made it reachable, which would have stopped every server) is
218
+ reported in the state frame while the running config is kept. A changed definition takes effect on
219
+ that entry's next start; only an added `autostart` entry is started, and `--no-autostart` still
220
+ means the panel starts nothing on its own.
221
+ - **The shell must watch the *session*, not only the flags it derives.** With authentication off,
222
+ `authRequired` and `authenticated` are both `false` from the first paint to the last, so a watcher
223
+ on those two never ran after the session landed: the panel never opened its event stream, the
224
+ connection badge sat on "Connecting", and the dashboard showed one stale snapshot forever. Both
225
+ UIs turn it into a single `streamDecision(session)` value (`wait` | `connect` | `login`) and watch
226
+ that, which is also what the test pins.
227
+ - **A form that copies live state must guard per block, never globally.** The host thresholds and the
228
+ backups policy arrive from `/api/settings` *after* the SSE frame, so a single "has anything
229
+ changed?" gate leaves them showing schema defaults forever — the backups toggle reported itself as
230
+ changed and flipped back to `true` on every reload. `uis/stock` compares each block against the
231
+ snapshot it was last filled from (`blockSnapshot`/`isBlockEdited`, `syncFromLive`) and reads the
232
+ file-only blocks on their own.
233
+ - **A dialog's footer has to be a flex sibling of a scrolling body** (`uis/stock/src/components/ui/Modal.vue`):
234
+ the sheet is `flex flex-col` with `max-h-[88dvh]`, the body `min-h-0 flex-1 overflow-y-auto`. When
235
+ only the body carried a max-height, a tall form pushed its own save button below the clipped
236
+ edge — the add-server dialog looked like it had no save button at all.
180
237
  - Vue does not notify a computed's subscribers when its recomputed value is `Object.is`-equal to the
181
238
  old one, so anything mutated in place silently freezes every value derived from it. The log buffers
182
239
  (`uis/stock/src/composables/useControlPlane.ts`) therefore hand out a **new array per batch**, and
@@ -197,6 +254,10 @@ either is a last resort, and never an accidental one.
197
254
  - Backups are zips; a password makes them WinZip AES-256/AE-2, and zero-byte entries stay
198
255
  unencrypted on purpose (p7zip 16.02 reports a CRC failure otherwise). `list()` is sync, so
199
256
  encryption flags are cached and refreshed in the background.
257
+ - Generated directories are skipped by the `filter` handed to `fs.cpSync`, matched on an exact path
258
+ segment at any depth (`src/shared/generated.ts`): `dist/` and `app/node_modules/` go, while
259
+ `distributed/` and `my-node_modules/` stay. Only the paths an entry declares are filtered — a
260
+ global `backups.includePaths` entry is captured as it stands.
200
261
  - `vite.server.config.ts` targets `node22` while `engines` requires >= 24 — deliberate margin, leave it.
201
262
  - `pnpm test` watches; CI runs `vitest run`.
202
263
  - `pnpm run media` drives Chromium through Playwright, which needs fonts *and* the X/NSS/Mesa
@@ -211,11 +272,11 @@ either is a last resort, and never an accidental one.
211
272
  - **Server field**: `serverSchema` + its patch in contracts, merge keys in `config/store.ts` when
212
273
  nested, the form in `uis/stock/src/components/settings/`, and the contract tests.
213
274
  - **UI**: a new `uis/<name>/` with a `vite.config.ts` from the shared factory;
214
- `node scripts/build-uis.mjs <name> --zip`. The contract is `UI_CREATION.md`.
275
+ `node scripts/build-uis.mjs <name> --zip`. The contract is `docs/UI_CREATION.md`.
215
276
  - **Capability**: a stateless `src/providers/*` returning plain data.
216
277
 
217
278
  ## Publishing
218
279
 
219
- `pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `SERVERS.md`,
220
- `NOTIFICATIONS.md`, `UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
280
+ `pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/SERVERS.md`,
281
+ `docs/NOTIFICATIONS.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
221
282
  build; `vue`/`vue-router` are devDependencies because the UIs are prebuilt.
package/README.md CHANGED
@@ -16,7 +16,7 @@ them, watches them, restarts what dies, and shows you one page of what is going
16
16
  [![License](https://img.shields.io/npm/l/home-hosted.svg)](./LICENSE)
17
17
  [![Node](https://img.shields.io/node/v/home-hosted.svg)](https://nodejs.org)
18
18
 
19
- [🚀 Quick start](#-quick-start) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🧩 Servers](./SERVERS.md) · [🛠 CLI](#-cli) · [🔔 Notifications](./NOTIFICATIONS.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
19
+ [🚀 Quick start](#-quick-start) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🧩 Servers](./docs/SERVERS.md) · [🛠 CLI](#-cli) · [🔔 Notifications](./docs/NOTIFICATIONS.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
20
20
 
21
21
  </div>
22
22
 
@@ -34,6 +34,40 @@ shortcuts wizards), and UI directions you could build yourself —
34
34
 
35
35
  ---
36
36
 
37
+ ## 🤔 Why?
38
+
39
+ Running services on a home machine usually means one of two extremes: `tmux` sessions you
40
+ forget about, or a hand-written systemd unit per service (times six) — or a complex docker/k8s
41
+ setup?
42
+
43
+ | | |
44
+ | --- | --- |
45
+ | ❌ **"Is it still running?"** | You check with `ps`, then `curl`, then hope. |
46
+ | ❌ **Silent deaths** | Something crashes at 3am and you notice days later. |
47
+ | ❌ **One terminal per service** | Logs scroll away in tabs you closed. |
48
+ | ❌ **Fragile restarts** | The box reboots and half the stack is gone. |
49
+ | ✅ **home-hosted** | Declare it once, watch it forever, one command to stop it all. |
50
+
51
+ ```text
52
+ ┌──────────────────────────────────────┐
53
+ your browser ──▶│ home-hosted · 127.0.0.1:3999 │
54
+ │ your UI + JSON API + SSE logs │
55
+ └───────────────┬──────────────────────┘
56
+ │ supervises
57
+ ┌──────────────────────────┼──────────────────────────┐
58
+ ▼ ▼ ▼
59
+ ┌─────────┐ ┌─────────┐ ┌─────────┐
60
+ │ gateway │ │ files │ │ bot │
61
+ │ :4000 │ │ :4010 │ │ ... │
62
+ └─────────┘ └─────────┘ └─────────┘
63
+ health ✓ health ✓ restarts ↻
64
+ ```
65
+
66
+ It ships with **nothing**. No blessed paths, no "data directory" setting, no opinion about
67
+ what you run — a server is a command, some arguments, and the environment you give it.
68
+
69
+ ---
70
+
37
71
  ## ⚡ Quick start
38
72
 
39
73
  ```bash
@@ -104,6 +138,10 @@ Worked example, with per-server data inside the project:
104
138
 
105
139
  <sub>Call them as `pnpm run up` — `pnpm up` is pnpm's own update, not your script.</sub>
106
140
 
141
+ > [!TIP]
142
+ > Give a project its own panel port (`control.port`, e.g. `4399`) — the default `3999` is what the
143
+ > global instance and every other project also want.
144
+
107
145
  </details>
108
146
 
109
147
  <details>
@@ -160,7 +198,7 @@ revokes it instantly.
160
198
  | `PATCH /api/servers/:id`, `PATCH /api/settings` | edit configuration |
161
199
  | `GET /api/logs`, `/api/backups`, `/api/notifications` | logs, archives, Telegram |
162
200
  | `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) |
163
- | `GET /api/metrics` | Prometheus text |
201
+ | `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) |
164
202
 
165
203
  `GET /openapi/spec.json` describes all of it, `/openapi/ui` is the browsable version, and every error
166
204
  comes back as one envelope (`{ message, code, detail }`) with a stable `code` a tool can branch on.
@@ -175,9 +213,9 @@ Give the agent four things and it can run your home server without guessing:
175
213
  1. the token (`home-hosted set-token --generate`),
176
214
  2. `http://127.0.0.1:3999/openapi/spec.json` — the API it may call,
177
215
  3. `home-hosted status --json` — where things are,
178
- 4. [SERVERS.md](./SERVERS.md) — how an entry is declared when it needs a new server.
216
+ 4. [SERVERS.md](./docs/SERVERS.md) — how an entry is declared when it needs a new server.
179
217
 
180
- For a UI rather than the API, [UI_CREATION.md](./UI_CREATION.md) is the whole contract, and the panel
218
+ For a UI rather than the API, [UI_CREATION.md](./docs/UI_CREATION.md) is the whole contract, and the panel
181
219
  can be told what to be: *"Help me build a UI for home-hosted: nostalgic game theme, including …"*.
182
220
 
183
221
  </details>
@@ -189,16 +227,17 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
189
227
  | | |
190
228
  | --- | --- |
191
229
  | 🚦 **Lifecycle** | Start, stop, restart from the panel or the API; `autostart` entries come up with it. |
230
+ | 📝 **Hand edits welcome** | Change `servers.config.json` in an editor, a `git checkout` or a config tool: the panel notices within seconds, no restart. A file it cannot read is reported in the panel, and the running servers are left alone. |
192
231
  | ♻️ **Auto-restart** | Exponential backoff on crash, with the counter reset once a process stays up. |
193
- | 🩺 **Health that acts** | TCP or HTTP probes per server: warn on the card, force a restart after a timeout, check ports before starting — and [follow or replace](./SERVERS.md#when-a-program-restarts-itself) a program that restarts itself. |
232
+ | 🩺 **Health that acts** | TCP or HTTP probes per server: warn on the card, force a restart after a timeout, check ports before starting — and [follow or replace](./docs/SERVERS.md#when-a-program-restarts-itself) a program that restarts itself. |
194
233
  | 🔗 **Ordered startup** | `dependsOn` waits for a dependency to be *healthy* — not merely spawned — and stops in reverse. |
195
234
  | 📜 **Logs** | Live per-server stream, buffer plus rotated files on disk, search, download, one click to clear. |
196
235
  | 📈 **Resources** | CPU and RSS of the whole process tree, with an optional memory ceiling that triggers a restart. |
197
236
  | 🌡️ **Host vitals** | Load, memory, swap, disk and CPU temperature, with thresholds that notify once and again on recovery. |
198
237
  | 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
199
- | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery and host thresholds — [setup here](./NOTIFICATIONS.md). |
238
+ | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery and host thresholds — [setup here](./docs/NOTIFICATIONS.md). |
200
239
  | 💾 **Backups** | One click for config, secrets, TLS and your declared data directories — plain `.zip`, or AES-256 with a password, restored per path. |
201
- | 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./UI_CREATION.md) |
240
+ | 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./docs/UI_CREATION.md) |
202
241
  | 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
203
242
  | 🧩 **No special treatment** | A server is `command` + `args` + `env` + `cwd`; nothing is built in for any particular app. |
204
243
  | 🖥 **Cross-platform** | Linux, macOS and Windows: `/proc`, `ps` or Win32_Process, process groups or `taskkill /T`, no shell dependencies. |
@@ -222,7 +261,7 @@ An entry is a few lines. Add one with **➕ Add server**, or write it into `serv
222
261
 
223
262
  `dataEnvs` declares a data directory once: it is exported to the process *and* picked up by Backups.
224
263
  **Every field, every placeholder, the port-conflict policies (including adopting a server that
225
- restarts itself), and how hand-edits are validated: [SERVERS.md](./SERVERS.md).**
264
+ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.md).**
226
265
 
227
266
  ---
228
267
 
@@ -238,11 +277,14 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./SERVERS.md).*
238
277
  | `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
239
278
  | `home-hosted migrate` | bring `servers.config.json` up to this release's schema (`--dry-run`, `--yes`) |
240
279
  | `home-hosted init` | scaffold a project that keeps `state/` and its data in the repo |
280
+ | `home-hosted ui-switch` | install a UI from a release asset, a zip file or a URL (`--list` to see them) |
241
281
  | `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
242
282
 
243
283
  <details>
244
284
  <summary><b>⚙️ Flags</b></summary>
245
285
 
286
+ `home-hosted <command> --help` prints the options that command takes.
287
+
246
288
  ```text
247
289
  -c, --config <file> servers config (default: <state>/servers.config.json)
248
290
  -p, --port <port> control panel port (default: 3999)
@@ -299,7 +341,7 @@ Everything binds `127.0.0.1` until you say otherwise.
299
341
  - **Port conflicts** are named — `port 4010 is already in use (pid 4242)` — and can be resolved from
300
342
  a confirmation popover on that banner or card. The process is looked up again at that moment,
301
343
  never taken from the message, and anything the panel supervises is refused, not killed. A server
302
- that [restarts itself](./SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
344
+ that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
303
345
  with a supervised copy.
304
346
  - **Secrets never enter the config**: the password hash, the API token hash, the Telegram bot token
305
347
  and the TLS key live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`.
@@ -313,14 +355,16 @@ Everything binds `127.0.0.1` until you say otherwise.
313
355
  Telegram, when something happens while you are not looking: a server that gave up restarting, a
314
356
  failing health check, a forced restart, a recovery, or a host threshold (disk, memory, swap, load,
315
357
  temperature). Opt-in, rate-limited per server *and* reason, and the bot token stays in the secrets
316
- file. **Two minutes of setup: [NOTIFICATIONS.md](./NOTIFICATIONS.md).**
358
+ file. **Two minutes of setup: [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
317
359
 
318
360
  ---
319
361
 
320
362
  ## 💾 Backups
321
363
 
322
364
  **Settings → Backups** archives the config, secrets, TLS pair and every data directory your entries
323
- declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path.
365
+ declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path. Known build output
366
+ and dependency directories (`node_modules`, `dist`, `.next`, framework caches) are skipped per entry;
367
+ `backupIgnoreGenerated: false` captures them anyway.
324
368
 
325
369
  <details>
326
370
  <summary><b>🚚 One archive is a whole setup</b></summary>
@@ -355,7 +399,7 @@ specific:
355
399
 
356
400
  > Help me build a UI for `home-hosted`: nostalgic game theme, including … features.
357
401
 
358
- [UI_CREATION.md](./UI_CREATION.md) has the endpoints, the SSE frames, the auth rules and a checklist.
402
+ [UI_CREATION.md](./docs/UI_CREATION.md) has the endpoints, the SSE frames, the auth rules and a checklist.
359
403
 
360
404
  </details>
361
405
 
@@ -384,7 +428,7 @@ the same way: every supervised process tree is stopped before the panel exits.
384
428
 
385
429
  A supervised server whose port is taken is reported rather than started over — the panel names the
386
430
  holder and offers to free it, and a program that restarts itself can be followed or reclaimed instead
387
- ([SERVERS.md](./SERVERS.md#a-busy-port)). The control port itself is checked before the listener is
431
+ ([SERVERS.md](./docs/SERVERS.md#a-busy-port)). The control port itself is checked before the listener is
388
432
  opened.
389
433
 
390
434
  </details>
@@ -430,10 +474,13 @@ expose them to an unprivileged process, and adopting a self-restarted process is
430
474
 
431
475
  ```text
432
476
  src/ control plane: config, supervisor, API, providers, services
433
- src/cli.ts the command line (up/down/status/restart/set-password/set-token/migrate)
477
+ src/cli.ts the command line; one file per command under src/cli/
434
478
  src/index.ts the control plane itself, used by `up --foreground`
435
- uis/ UIs: `stock` (shipped) and alternatives — any framework, static output
479
+ uis/ UIs: `stock` (shipped in the package) and alternatives — any framework, static output
436
480
  bin/ the published entry point
481
+ docs/ topic docs, UI examples and the README's media
482
+ scripts/ builds, typechecks, media capture, release helpers
483
+ test/ the vitest suite
437
484
  ```
438
485
 
439
486
  `pnpm dev` runs the panel with `tsx watch` plus the stock UI's dev server (state goes to
@@ -445,9 +492,9 @@ types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
445
492
 
446
493
  | file | for |
447
494
  | --- | --- |
448
- | [SERVERS.md](./SERVERS.md) | declaring a server: every field, placeholders, port conflicts |
449
- | [NOTIFICATIONS.md](./NOTIFICATIONS.md) | Telegram alerts, end to end |
450
- | [UI_CREATION.md](./UI_CREATION.md) | building a UI against the API |
495
+ | [SERVERS.md](./docs/SERVERS.md) | declaring a server: every field, placeholders, port conflicts |
496
+ | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) | Telegram alerts, end to end |
497
+ | [UI_CREATION.md](./docs/UI_CREATION.md) | building a UI against the API |
451
498
  | [AGENTS.md](./AGENTS.md) | the architecture and the rules worth knowing before changing anything |
452
499
  | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) | the live API, on your own panel |
453
500