dsh-home-hosted 0.2.1 → 0.3.1

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
@@ -1,6 +1,13 @@
1
- # dsh-home-hosted
1
+ <div align="center">
2
2
 
3
- **Manage [home-hosted](https://github.com/NamesMT/home-hosted) — its boot entry, its panel and its servers — from inside DeepSeek Harness.**
3
+ # 📌 dsh-home-hosted
4
+
5
+ **Your whole home stack, up after every reboot — your `dsh` web server included.**
6
+
7
+ A [DeepSeek Harness](https://github.com/deepseek-ai/dsh) plugin for
8
+ [home-hosted](https://github.com/NamesMT/home-hosted), the panel that supervises your services.
9
+ Declare the servers once; this page manages the panel, the boot entry and the entries.
10
+ <sub>Nothing is installed or started until you say so.</sub>
4
11
 
5
12
  [![npm](https://img.shields.io/npm/v/dsh-home-hosted?label=npm&color=blue)](https://www.npmjs.com/package/dsh-home-hosted)
6
13
  [![CI](https://github.com/NamesMT/dsh-home-hosted/actions/workflows/ci.yml/badge.svg)](https://github.com/NamesMT/dsh-home-hosted/actions/workflows/ci.yml)
@@ -8,24 +15,72 @@
8
15
  [![node](https://img.shields.io/badge/node-%3E%3D24-339933?logo=node.js&logoColor=white)](https://nodejs.org)
9
16
  [![dsh](https://img.shields.io/badge/dsh-%3E%3D0.1.7--rc.2-5b21b6)](https://github.com/deepseek-ai/dsh)
10
17
 
11
- ![The plugin's page, under Settings → Home Hosted](https://raw.githubusercontent.com/NamesMT/dsh-home-hosted/main/assets/settings.png)
18
+ [🚀 Quick start](#-quick-start) · [🎛️ What the plugin does](#-what-the-plugin-does) · [🤖 Agent tools](#-agent-tools) · [🧩 Depth](#-depth)
19
+
20
+ </div>
21
+
22
+ <div align="center">
23
+
24
+ ![The plugin's page: Detailed and Compact styles, under Settings → Home Hosted](https://raw.githubusercontent.com/NamesMT/dsh-home-hosted/main/assets/settings.gif)
25
+
26
+ <sub>The Detailed style, then the same sections in Compact — both switchable from the page header.</sub>
27
+
28
+ </div>
12
29
 
13
- ## Install
30
+ ---
31
+
32
+ ## 🚀 Quick start
14
33
 
15
34
  ```sh
16
35
  dsh plugin --profile web add dsh-home-hosted
17
36
  ```
18
37
 
19
- Then open **Settings → Home Hosted**. Nothing is installed or started until you say so.
38
+ Open **Settings → Home Hosted**, turn on **Manage dsh**, and enable autostart.
39
+ <sub>Reboot once; `dsh` comes back without you touching anything.</sub>
40
+
41
+ ## 🤔 Why
42
+
43
+ A panel restart shouldn't kill a prompt mid-flight, and a reboot shouldn't cost you an SSH session.
44
+
45
+ ```text
46
+ reboot
47
+ │
48
+ ▼
49
+ OS boot entry ──▶ home-hosted panel ──▶ ┌─────────────┐
50
+ systemd · │ │ dsh web ✓ │
51
+ launchd · │ └─────────────┘
52
+ XDG · Run key │
53
+ ├────────▶ ┌─────────────┐
54
+ │ │ docker │
55
+ │ │ compose │
56
+ │ └─────────────┘
57
+ │
58
+ └────────▶ ┌─────────────┐
59
+ │ postgres │
60
+ │ :5432 │
61
+ └─────────────┘
62
+ ```
63
+
64
+ **`dsh` is one entry in the panel, not the whole product.** The panel is the supervisor: it starts
65
+ each entry, watches it, restarts what dies and reclaims its port. Add your compose stack, your
66
+ database, your bot — manage them from the page, or hand the entry ids to an agent.
67
+
68
+ A plugin cannot run at boot, so this one writes the OS entry while dsh runs and the OS takes over.
69
+ One managed entry (`dsh`) is kept alive under home-hosted's nanny, so a panel restart leaves it
70
+ running.
71
+
72
+ ## 🎛️ What the plugin does
20
73
 
21
74
  | | |
22
75
  |---|---|
23
- | 🚀 **Boot autostart** | Installs, verifies and removes the OS entry that starts the panel. Opt in per machine. |
76
+ | 🚀 **Boot autostart** | Installs, verifies and removes the OS entry. Opt in per machine. |
77
+ | 🛡️ **Survives a panel restart** | The managed entry runs under home-hosted's nanny, so restarting the panel leaves it running. |
24
78
  | 📦 **Ships its own panel** | The pinned `home-hosted` is a dependency, so boot runs the version this plugin is tested against. |
25
79
  | 🖥️ **Server control** | Add, edit, start, stop and restart entries — written through the panel's API, so nothing restarts behind your back. |
26
- | 🤖 **Agent tools** | On by default: the agent can inspect state, manage servers, install autostart and switch the panel's UI. |
80
+ | 🧭 **Token warnings that mean something** | A missing or refused API token is called out on the page, with one click to mint a working one. |
81
+ | 🤖 **Agent tools** | On by default, session permissions still gate every write. |
27
82
 
28
- ## Agent tools
83
+ ## 🤖 Agent tools
29
84
 
30
85
  | Tool | Does |
31
86
  |---|---|
@@ -37,6 +92,9 @@ Then open **Settings → Home Hosted**. Nothing is installed or started until yo
37
92
  | `home_hosted_ui_manage` | `status` · `update` · `revert` · `switch` the panel's own UI |
38
93
 
39
94
  A tool that changes something asks for approval **only** when the session is not already Full access.
95
+ <sub>A refused token is re-enrolled on the spot and the tool retries — see **Auto reclaim** below.</sub>
96
+
97
+ ## 🧩 Depth
40
98
 
41
99
  <details>
42
100
  <summary><b>Boot autostart, per platform</b></summary>
@@ -47,26 +105,55 @@ A tool that changes something asks for approval **only** when the session is not
47
105
  | macOS | `launchd-agent`, `launchd-daemon` | login, or boot with the daemon (one-time `sudo`) |
48
106
  | Windows | Run key, Task Scheduler | login |
49
107
 
50
- A plugin cannot act at boot: it installs and re-syncs the entry while dsh runs, and the OS takes over from there. When the process cannot elevate, the page prints the exact commands to run instead — including the `launchd-daemon` that starts a Mac **before** login.
108
+ When the process cannot elevate, the page prints the exact commands instead — including the
109
+ `launchd-daemon` that starts a Mac **before** login.
51
110
 
52
111
  </details>
53
112
 
54
113
  <details>
55
114
  <summary><b>Which home-hosted runs</b></summary>
56
115
 
57
- The pinned dependency by default. The page can switch to a global install, or install the pinned range globally for you. Boot entries run a small stable launcher the plugin writes, so a `node_modules` path that moves never breaks boot.
116
+ The pinned dependency by default; the page can switch to a global install, or install the pinned
117
+ range globally for you. Boot entries run a small stable launcher the plugin writes, so a
118
+ `node_modules` path that moves never breaks boot.
119
+
120
+ </details>
121
+
122
+ <details>
123
+ <summary><b>dsh from a local clone</b></summary>
124
+
125
+ A `dsh` you cloned and built yourself is supported: the managed entry starts a stable launcher that
126
+ re-finds your build at boot, so a rebuild, a moved checkout or a fresh profile does not strand it.
127
+ <sub>Falls back to whatever `dsh` is on PATH, and fails with the reason when it finds neither.</sub>
58
128
 
59
129
  </details>
60
130
 
61
131
  <details>
62
132
  <summary><b>Managed entry and port policy</b></summary>
63
133
 
64
- One entry (`dsh`) is managed from the page with a single toggle: the panel keeps it alive, restarts it and reclaims its port. A detached restart is recognised on macOS and Linux (`follow`); Windows cannot prove identity through a `.cmd` shim, so it uses `onPortConflict: kill` — which needs home-hosted 0.6.0 or newer, and the plugin refuses that write against an older panel rather than producing a config it cannot parse.
134
+ One entry (`dsh`), one toggle: the panel keeps it alive, restarts it and reclaims its port. A
135
+ detached restart is recognised on macOS and Linux (`follow`); Windows cannot prove identity through
136
+ a `.cmd` shim, so it uses `onPortConflict: kill` — needing home-hosted 0.6.0+, and the plugin
137
+ refuses that write against an older panel rather than producing a config it cannot parse.
65
138
 
66
139
  </details>
67
140
 
68
141
  <details>
69
- <summary><b>Configuration</b></summary>
142
+ <summary><b>API tokens and Auto reclaim</b></summary>
143
+
144
+ The plugin needs its own home-hosted API token to read and write the panel. home-hosted keeps only
145
+ the token's hash, so a token it did not mint can never be recovered — `<stateDir>/panel-token` is
146
+ `0600`, never rendered or logged, and **Regenerate** clears the old hash and enrols a new one.
147
+
148
+ A token that stops working (someone ran `home-hosted set-token`, or cleared it) shows on the page as
149
+ a warning with that button. **Auto reclaim**, on by default, does the same thing automatically when a
150
+ panel call is refused: the tool re-enrols and retries once instead of failing. Turn it off to be
151
+ asked instead of having a working token silently replaced.
152
+
153
+ </details>
154
+
155
+ <details>
156
+ <summary><b>Operator config</b></summary>
70
157
 
71
158
  The Cordis row config is for operator overrides only:
72
159
 
@@ -79,7 +166,7 @@ The Cordis row config is for operator overrides only:
79
166
  defaultEntryId: dsh
80
167
  ```
81
168
 
82
- Everything a person toggles lives in `<stateDir>/settings.json`. The panel API token it mints is kept `0600` there, and is never rendered or logged.
169
+ Everything a person toggles lives in `<stateDir>/settings.json`.
83
170
 
84
171
  </details>
85
172
 
@@ -91,8 +178,14 @@ pnpm install
91
178
  pnpm typecheck && pnpm test && pnpm build
92
179
  ```
93
180
 
94
- Node 24+, pnpm, 360 tests. Same four commands CI runs.
181
+ Node 24+, pnpm. The same commands CI runs.
95
182
 
96
183
  </details>
97
184
 
185
+ ---
186
+
187
+ <div align="center">
188
+
98
189
  MIT · [npm](https://www.npmjs.com/package/dsh-home-hosted) · [releases](https://github.com/NamesMT/dsh-home-hosted/releases) · [issues](https://github.com/NamesMT/dsh-home-hosted/issues) · built on [home-hosted](https://github.com/NamesMT/home-hosted)
190
+
191
+ </div>
package/docs/DESIGN.md CHANGED
@@ -47,6 +47,26 @@ the rest from the platform default policy; removing one restores an entry this
47
47
  plugin merely adopted, and deletes one it created. Removing an entry the panel
48
48
  supervises stops that process — which may be the session asking for it.
49
49
 
50
+ ## The sign-in page points at the log
51
+
52
+ dsh web's unauthenticated 401 is a plain-text line written by the in-box
53
+ connection plugin; it has no config and no event. The plugin wraps that one
54
+ internal writer (`connection.browserAuth.writeUnauthorized`) and appends where
55
+ the tokenised URL is — `<panel url>/logs?server=<entry>`. It rewrites **only** the
56
+ exact stock body, guards every assignment, and replaces the response's `end` for
57
+ the single call, so an authentication plugin's own page (or an exotic response)
58
+ is passed through untouched rather than fought over. `authNotice` turns it off;
59
+ `{url}` is read per 401.
60
+
61
+ ## Persistence keeps dsh alive across a panel restart
62
+
63
+ A managed entry asks for `persistent: true`: home-hosted then runs it under its
64
+ nanny, which owns the child and outlives `stopAll()`/`dispose()` — so restarting
65
+ or stopping the *panel* no longer ends a prompt mid-flight. The key arrived in
66
+ home-hosted 0.6.3, so the write and the drift check both consult the panel's
67
+ version: an older panel gets no such key (it would only be dropped and warned),
68
+ while the stored intent keeps what the person asked for.
69
+
50
70
  ## Approvals follow the session's sandbox
51
71
 
52
72
  A mutating agent tool asks the approval service only when the calling session is
@@ -144,6 +164,22 @@ one, passes it to `home-hosted set-token` through `HHOSTED_TOKEN` (the CLI print
144
164
  nothing), and keeps it 0600 in its own state directory. It is never rendered,
145
165
  logged, or embedded in a unit file.
146
166
 
167
+ The token state the page shows is measured, not assumed. When the panel answers,
168
+ the plugin proves its token with a non-mutating `listServers()`: a refusal is
169
+ `stale` and the write path drops to the config file, while a panel that never
170
+ answered leaves the state `unknown` — a timeout is not a refusal, and
171
+ `tokenVerified` records whether a proof actually happened.
172
+
173
+ A refused token, or a hash this plugin never had, is replaced by
174
+ `panel.reclaimToken`: it clears the old hash first (`set-token --clear`, without
175
+ which `set-token` refuses to overwrite one), mints and enrols a fresh token
176
+ through the same `HHOSTED_TOKEN` path, and proves it against the panel before
177
+ returning the refreshed status. Every token operation on one state directory is
178
+ serialised; two concurrent mints used to leave the stored plaintext and the
179
+ panel's hash disagreeing, and nothing could ever recover. If the CLI cannot run
180
+ nothing is written; if it fails after the clear, the stored token is removed,
181
+ because home-hosted no longer accepts it and a stored plaintext would only lie.
182
+
147
183
  ## Privilege
148
184
 
149
185
  Boot scope needs privilege somewhere on every platform: a system unit, or