@monster-orange/den 0.4.4 → 0.4.6

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +24 -9
  3. package/docs/distributions.md +320 -26
  4. package/docs/reference.md +8 -3
  5. package/package.json +4 -3
  6. package/skills/install-den/SKILL.md +114 -0
  7. package/src/shared/agent-setup.ts +9 -0
  8. package/src/shared/auth/sudo-vault.ts +115 -11
  9. package/src/shared/cli-command.ts +186 -0
  10. package/src/shared/confirm-code.ts +34 -0
  11. package/src/shared/constants.ts +7 -0
  12. package/src/shared/den-config.ts +7 -0
  13. package/src/shared/dev-metrics/emit.ts +59 -2
  14. package/src/shared/edge/config.ts +39 -0
  15. package/src/shared/edge/render.ts +92 -18
  16. package/src/shared/edge/runtime.ts +57 -1
  17. package/src/shared/edge/types.ts +11 -1
  18. package/src/shared/github-app.ts +94 -0
  19. package/src/shared/github-device.ts +286 -0
  20. package/src/shared/port-pick.ts +60 -0
  21. package/src/shared/secrets/onepassword.ts +27 -4
  22. package/src/shared/web-owner.ts +91 -0
  23. package/src/shared/web-session.ts +135 -1
  24. package/src/ui/cli/create-main.ts +1 -1
  25. package/src/ui/cli/create.ts +263 -30
  26. package/src/ui/cli/doctor-command.ts +67 -4
  27. package/src/ui/cli/doctor-onboard.ts +291 -0
  28. package/src/ui/cli/flag-parser.ts +5 -1
  29. package/src/ui/cli/index.ts +246 -4
  30. package/src/ui/cli/json-out.ts +49 -0
  31. package/src/ui/cli/mcp-connect.ts +179 -15
  32. package/src/ui/cli/repo-command.ts +490 -0
  33. package/src/ui/cli/repo-detect.ts +192 -0
  34. package/src/ui/cli/web-command.ts +513 -33
  35. package/src/ui/mcp/connect.ts +134 -0
  36. package/src/ui/server/device-flows.ts +274 -0
  37. package/src/ui/server/edge-exposure.ts +16 -4
  38. package/src/ui/server/github-routes.ts +321 -0
  39. package/src/ui/server/http-errors.ts +10 -0
  40. package/src/ui/server/index.ts +463 -27
  41. package/src/ui/server/login-waits.ts +203 -0
  42. package/src/ui/server/pair-requests.ts +237 -0
  43. package/src/ui/server/pair-routes.ts +196 -0
  44. package/src/ui/server/public/error-page.js +452 -40
  45. package/src/ui/server/public/error.html +59 -0
  46. package/src/ui/server/public/i18n/es.js +74 -8
  47. package/src/ui/server/public/i18n/ja.js +74 -8
  48. package/src/ui/server/public/local-dev.html +16 -4
  49. package/src/ui/server/public/qr.js +317 -0
  50. package/src/ui/server/public/settings-page.js +10 -3
  51. package/src/ui/server/public/settings-registry.js +392 -1
  52. package/src/ui/server/public/shell.js +123 -10
  53. package/src/ui/server/public/topbar.js +7 -3
  54. package/src/ui/server/public/workspace-picker.js +1 -1
  55. package/src/ui/server/request-guard.ts +103 -36
  56. package/src/ui/server/route-classes.ts +102 -2
  57. package/src/ui/server/ui-assets.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,249 @@ All notable changes to Den are documented here. Each release lists the
4
4
  config and manifest changes a distribution must make to move to it under
5
5
  **Migrations**.
6
6
 
7
+ ## [0.4.6] - 2026-11-07
8
+
9
+ Den in five minutes, set up by your own agent (ON-2, with ON-4's dry run).
10
+ A developer pastes one line into their agent, and every command the agent
11
+ runs answers in JSON, never prompts, and is safe to re-run.
12
+
13
+ ### Added
14
+
15
+ - **Set up by your agent.** `den --help` and the README print the line to
16
+ paste: "Set up Den for me by following
17
+ https://raw.githubusercontent.com/Monster-Orange/den/main/skills/install-den/SKILL.md". The install-den skill it points
18
+ at ships in the package as `skills/install-den/SKILL.md`: plain Markdown
19
+ with Claude skill front matter, at most three questions, no secrets in
20
+ chat, no edits to the person's repos, nothing sent off the machine.
21
+ - **`<cli> repo add <path|git-url>`** adds a repo with no prompts and never
22
+ writes into it. A git URL is cloned where Den keeps managed clones. A repo
23
+ with no manifest (or `--sidecar`) gets a side-car manifest in the
24
+ distribution's `manifests/`, detected from its Makefile `up` target, its
25
+ compose file or its `package.json` `dev`/`start` scripts (and `test`), and
26
+ validated before it is written. When detection can't decide, nothing is
27
+ written and the answer asks, with the choices; a re-run passes
28
+ `--run "<cmd>"`. `--health <url>` adds a health check. The workspace is
29
+ `-w`, else the focused one, else a new `default`. The same repo again
30
+ answers `existed: true`.
31
+ - **`--json` for setup.** `create --yes --json` (a directory that already
32
+ holds the distribution is `existed: true`), `up --json` (each service's
33
+ result and health), `doctor --json` and `mcp connect --json`. Stdout is
34
+ one JSON object, the log goes to stderr, and a failure carries a `fix`
35
+ the agent can run or an `ask` for the person.
36
+ - **`doctor --json`'s onboarding checks:** Node 22.13 or later, git, Docker
37
+ (only when a registered repo needs it), the web server and its URL, and
38
+ Den's MCP through it, each `ok`, `warn` or `fail`. The human `doctor` is
39
+ unchanged.
40
+ - **`mcp connect --runtime claude|codex|gemini|grok`** names the agent by
41
+ its runtime. Grok CLI is a new client: Den writes its own
42
+ `[mcp_servers.<cli>]` table in `~/.grok/config.toml` (or the project's
43
+ `.grok/config.toml`), as xAI documents. A config file that already has the
44
+ entry is no longer rewritten.
45
+ - **`create` picks a free web port.** When 7777 is in use on this machine,
46
+ `create` picks the next free port above it (never 7778, 7791 or 7799),
47
+ writes it to the new distribution's `config/local/user.yml` and says so,
48
+ in JSON as `port` and `portPicked`. `--port <n>` sets it outright. The
49
+ check connects and binds only; it sends nothing to the port's holder.
50
+ `DEN_WEB_PORT_START` moves the starting port.
51
+ - **`web up --json`** answers `{ ok, url, port }` once the server answers,
52
+ and `doctor --json`'s web check carries the `port`.
53
+
54
+ ### Changed
55
+
56
+ - `create` into a directory that already holds the same distribution says
57
+ so and exits 0, instead of refusing a non-empty directory.
58
+
59
+ ### Fixed
60
+
61
+ - **An opted-in CLI sends its metrics.** The CLI records each run as it
62
+ exits, and a send started then never ran. With `metrics.share: true`, the
63
+ CLI now hands the event to a small detached sender that gives up after 2
64
+ seconds. The web server sends as before. Not opted in, nothing is sent.
65
+ - **Linked devices read plainly.** A device approved with a code says
66
+ "approved with a code", not "signed in with approved with a code".
67
+ - **An agent's refusal names the admin password.** It said "the lock",
68
+ which 0.4.4 removed.
69
+
70
+ ### Metric
71
+
72
+ - Time from the paste to "service running, MCP connected" (R11): not
73
+ measured before; ON-4's dry run (create, repo add, up, web up, doctor,
74
+ mcp connect, each run twice) now records it, about 16 s locally. Target:
75
+ 5 minutes or less.
76
+
77
+ ### Migrations
78
+
79
+ - None.
80
+
81
+ ## [0.4.5] - 2026-11-07
82
+
83
+ Sign in with GitHub as the owner, from your other devices; approve a
84
+ computer with a code; see that you're signed in (SEC-2a). Save the admin
85
+ password without editing YAML (LK-4). Dev-metrics stay on this machine
86
+ unless you opt in.
87
+
88
+ ### Added
89
+
90
+ - **Sign in with GitHub.** A Den has one owner, a GitHub account:
91
+ `<cli> web owner link` runs GitHub's device flow in the terminal (a code
92
+ to enter at https://github.com/login/device) and stores only
93
+ `github:<id>` and the login (`web-owner.json`, 0600), never the token.
94
+ `web owner show` and `web owner unlink` too. The signed-out page then
95
+ has **Sign in with GitHub**: the server runs the flow, and the owner's
96
+ approval sets the same never-expiring session a one-time link does;
97
+ any other account sees "@<login> isn't allowed on this Den". At most
98
+ three sign-ins wait at once. Before an owner is linked the page says to
99
+ run `web owner link`. See docs/distributions.md, "Signing in with GitHub".
100
+ - **Settings › Access.** The owner (link and unlink: shell-grade, so a
101
+ signed-in browser and never an agent's token), the
102
+ linked devices (browser, how it signed in, the client address the edge
103
+ vouched for, first and last seen) each with **Revoke**, which ends that
104
+ session at once, and **Pair a device**: a one-time link and its QR code
105
+ for an edge hostname you pick.
106
+ - **`web.github_client_id`**: the GitHub App's client id (public). The
107
+ default is Den's own App, "Monster Orange Den" (`Iv23liKL12gnN6WvRV5b`);
108
+ a distribution sets its own App's id here, and an empty value turns Sign
109
+ in with GitHub off (the page then offers only the code and the link).
110
+ The App needs "Enable Device Flow" ticked and "Expire user authorization
111
+ tokens" off (docs/distributions.md, "The GitHub App").
112
+ - **Expiring GitHub tokens are noticed.** If the App hands out expiring
113
+ tokens (`expires_in` or `refresh_token` in the answer), sign-in still
114
+ completes, and Den says once to switch off token expiry in the App's
115
+ settings (never with the token). `GET /api/web/owner` reports it as
116
+ `tokenExpiry`. Den never asks for `offline_access`.
117
+ - **Approve this computer with a code.** The signed-out page shows a
118
+ six-character code (`XXX-XXX`, about 30 bits; the page polls with its own
119
+ 128-bit id) and the command that approves it on the machine Den runs on:
120
+ `<cli> web approve <code>` (any case, dash optional; `--list` shows what
121
+ waits). The page signs itself in when it's approved, onto the page it
122
+ asked for. From a machine with SSH access the page gives the one-liner
123
+ (`ssh <host> '<cli> web approve <code>'`) and, for a host name, an Open
124
+ Terminal link. Codes expire after 10 minutes, are single-use, at most
125
+ five wait at once (the oldest drops) and are kept in memory only;
126
+ approvals are rate-limited and audited with who approved. The device is
127
+ labelled "approved with a code".
128
+ - **Settings › Access › Waiting devices**: the computers waiting with a
129
+ code (browser and OS, address, when), each with **Approve** and **Deny**.
130
+ - **`web.machine_name`** (Settings › Access › Machine name, in user.yml):
131
+ the name the signed-out page gives this machine. Default: the short
132
+ hostname. It's never taken from the browser's Host; the ssh one-liner's
133
+ target is the Host the browser used (without the port).
134
+ - **The signed-out page says where to act.** "Sign in to <product>", which
135
+ machine Den runs on and why it asks (once per computer), Sign in with
136
+ GitHub (only with a client id), Approve this computer, and the one-time
137
+ link under "Other ways". Every command it prints names the machine it
138
+ runs on, has a copy button, and is `<cli>` when that's on PATH and this
139
+ distribution's, else the absolute path of its bin. English, Japanese and
140
+ Spanish.
141
+ - **See that you're signed in.** A chip at the foot of the rail says
142
+ "Local", "Signed in · this device" or "@<login>" and opens Settings ›
143
+ Access. The first page after a sign-in says "This device is signed in
144
+ to <product> on <machine>", once. Linked devices lists every session
145
+ (GitHub, a code, a one-time link) with how it signed in, when, from
146
+ where, last seen and Revoke, and marks the one you're using as "This
147
+ device".
148
+ - **Save the admin password without editing YAML (LK-4).** When a secrets
149
+ backend that keeps secrets (1Password, Doppler) is available and no
150
+ `secrets.refs.sudo` is set, the admin password prompt (shown when a
151
+ command is about to run sudo, LK-0) still offers "Save to <backend>".
152
+ Saving creates the item (1Password: "<Product> admin password" in your
153
+ default vault; Doppler: `<PREFIX>_LOCAL_ADMIN_PWD`) and writes
154
+ `secrets.refs.sudo` to your `config/local/user.yml`, so the password
155
+ survives restarts. A ref in the distribution's config still wins. The
156
+ vault status says so as `canSave`. A save error no longer carries the
157
+ backend's command line.
158
+ - **`edge.proxy_protocol.trusted_ips`** (empty by default): the front
159
+ proxies (a TCP passthrough in front of the edge) whose PROXY protocol
160
+ every entrypoint accepts, so Den sees the real client through them.
161
+ - **`web.edge_unattested: local | signin`** (default `signin`, read only
162
+ from `config/local/user.yml`): whether an edge client Den can't place
163
+ signs in. `local` keeps 0.4.4's (and 0.4.3's) behaviour.
164
+
165
+ ### Changed
166
+
167
+ - **Dev-metrics are local unless you opt in.** Den sent its dev-metrics
168
+ whenever a DSN was present (in the env or a synced
169
+ `dev-metrics.config`). Sending now needs `metrics: { share: true }` in
170
+ `config/local/user.yml` (the boolean only); a DSN alone never sends. The
171
+ local `events.jsonl` is unchanged, and `CI=true` and
172
+ `DEV_METRICS_DISABLED=1` still stop sending.
173
+ - **`<cli> web login` waits.** It prints the link and an 8-character code,
174
+ and blocks until the link is used ("Signed in: <browser> (<address>) at
175
+ <time>") or expires ("Expired: nobody used the link", exit 1). The link
176
+ asks for the code before it signs in; a wrong code is refused (five burn
177
+ the link, and wrong codes are rate-limited) and the link stays
178
+ single-use. `--no-wait` prints the link alone, as before, and asks for no
179
+ code; with no server running it behaves like `--no-wait` and says so.
180
+ - **Edge visitors Den can't place sign in.** A client the edge reports
181
+ with no `X-Forwarded-For`, or with a Docker/VM gateway or bridge address
182
+ (what a TCP passthrough or Docker Desktop and OrbStack hand it), now sees
183
+ the signed-out page with Sign in with GitHub, unless
184
+ `web.edge_unattested: local`. Loopback and this machine's own addresses,
185
+ as the edge reports them, stay local, and localhost straight to the
186
+ server's port never signs in.
187
+ - **The client is the right-most `X-Forwarded-For` entry**, the one the
188
+ edge appended (0.4.4 and earlier read the left-most, which a client could
189
+ write).
190
+ - **The edge believes forwarded headers only from its own networks.** Den
191
+ renders `forwardedHeaders.trustedIPs` (loopback and the Docker ranges)
192
+ on every entrypoint, never `forwardedHeaders.insecure`. An `edge.args`
193
+ entry that sets `forwardedHeaders` or `proxyProtocol` is dropped with a
194
+ notice from `edge up`.
195
+ - **"Your edge is reachable from other machines"** now reads "A browser it
196
+ can't place as this one signs in first (Sign in with GitHub, or a
197
+ pairing link)." (with `web.edge_unattested: local`, that anyone it can't
198
+ place can use terminals).
199
+ - **`edge up` says when the running edge's command differs** from the one
200
+ it renders, and why, before recreating it.
201
+
202
+ ### Fixed
203
+
204
+ - **The workspace commands run in is named: "CLI default".** Local Dev's
205
+ workspace list marked it with a bare accent dot and no label, and
206
+ picking a workspace in the top bar never moved the dot (by design: that
207
+ picks the view), so it looked broken. It now says "CLI default", with
208
+ a tooltip: CLI commands run there when you don't name a workspace, and
209
+ the top bar only changes what pages show. The top bar's Workspace menu
210
+ tags it "CLI default" (was "focus") and offers "Make <ws> the CLI
211
+ default"; the Workspaces pane's badge reads "active · CLI default" and
212
+ its button "Make CLI default". Nothing behaves differently.
213
+
214
+ - **A browser signs in on a plain-http LAN bind** (`DEN_BIND_HOST` with
215
+ `web.allow_plain_http_remote`). A `web login --origin http://<LAN IP>:<port>`
216
+ link got 403 `origin_rejected`: the browser sends a plain-http origin that
217
+ isn't localhost no `Sec-Fetch-*` headers, and a navigation sends no
218
+ Origin. The sign-in GETs (`/auth` and the GitHub sign-in poll), which
219
+ carry their own one-time credential, now pass with neither header. Either
220
+ header, when present, is still checked, and every other GET that runs
221
+ something still needs this server's page or a bearer.
222
+ - **`web up` with `DEN_BIND_HOST` on one address is ready when it serves.**
223
+ Its readiness probe (and `web sessions`/`web logout`'s calls) asked
224
+ 127.0.0.1, so a server bound to a LAN IP or `::1` printed "did not become
225
+ ready" and exited non-zero. They ask the bound address now (loopback for
226
+ 0.0.0.0 and `::`), with a Host the allowlist accepts and the CLI's
227
+ bearer: a LAN address answers an unsigned probe 401 `signin_required`.
228
+ `web up` names the bound address (`http://<LAN IP>:<port>`), not
229
+ localhost, which a LAN-only bind doesn't answer.
230
+
231
+ ### Migrations
232
+
233
+ - **One edge recreate.** The edge's command changes (forwarded-header
234
+ trust), so the first `<cli> edge up` (or `stack up`) after upgrading
235
+ recreates it once, and says so: "<org>-edge's settings changed
236
+ (forwarded-header trust): recreating it once." Routes drop only while it
237
+ restarts.
238
+ - **Edge visitors now see Sign in with GitHub** where 0.4.4 let a client
239
+ it couldn't place in. Link an owner (`<cli> web owner link`), approve
240
+ the computer with its code (`<cli> web approve <code>`) or pair with
241
+ `<cli> web login --host <name>`;
242
+ or set `web.edge_unattested: local` in `config/local/user.yml` to keep
243
+ 0.4.4's behaviour on that machine.
244
+ - **Scripts that run `<cli> web login` and read its output** should pass
245
+ `--no-wait`: it now waits for the link to be used.
246
+ - **Remove `forwardedHeaders.*` and `proxyProtocol.*` from `edge.args`.**
247
+ They are ignored now; use `edge.proxy_protocol.trusted_ips` for a front
248
+ proxy.
249
+
7
250
  ## [0.4.4] - 2026-11-07
8
251
 
9
252
  The admin password is for sudo, and nothing else (LK-0). An agent's
package/README.md CHANGED
@@ -10,10 +10,24 @@
10
10
  </p>
11
11
 
12
12
  <picture>
13
- <source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.4/hero-dark.png">
14
- <img src="docs/images/0.4.4/hero-light.png" width="100%" alt="Den's Graph page: the demo workspace's services grouped by application, all healthy, with the status chip and the left rail">
13
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.6/hero-dark.png">
14
+ <img src="docs/images/0.4.6/hero-light.png" width="100%" alt="Den's Graph page: the demo workspace's services grouped by application, all healthy, with the status chip and the left rail">
15
15
  </picture>
16
16
 
17
+ ## Set up by your agent
18
+
19
+ Paste this line into your coding agent (Claude Code, Codex, Gemini, Grok, …):
20
+
21
+ ```text
22
+ Set up Den for me by following https://raw.githubusercontent.com/Monster-Orange/den/main/skills/install-den/SKILL.md
23
+ ```
24
+
25
+ The agent follows the [install-den skill](skills/install-den/SKILL.md). It asks you at most three questions,
26
+ creates your Den, adds one of your repos without changing it, starts it, checks it, and connects itself to Den's
27
+ MCP server. Then it tells you what's running and the URL to open. It never asks for a password in chat, never
28
+ edits your repos and sends nothing off your machine. Every step it runs answers in JSON and is safe to re-run:
29
+ see [Set up by your agent](docs/distributions.md#set-up-by-your-agent).
30
+
17
31
  ## What Den is
18
32
 
19
33
  Den is an opinionated local development framework for solo developers and small teams. You point it at the
@@ -24,7 +38,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
24
38
  yours. Den connects them and turns on good practice from the start.
25
39
 
26
40
  <p align="center">
27
- <img src="docs/images/0.4.4/up.gif" width="100%" alt="make up in the Graph page: every stack down, then demo-api, demo-web and demo-notes come up in dependency order until the status chip reads All 3 healthy">
41
+ <img src="docs/images/0.4.6/up.gif" width="100%" alt="make up in the Graph page: every stack down, then demo-api, demo-web and demo-notes come up in dependency order until the status chip reads All 3 healthy">
28
42
  </p>
29
43
 
30
44
  ## How it helps
@@ -46,17 +60,18 @@ yours. Den connects them and turns on good practice from the start.
46
60
  and audit trail as a person, under a token of its own. Agents can read and investigate, and acting is opt-in.
47
61
  Starting an agent never asks for your admin password: Den asks for it only when a command it runs needs sudo.
48
62
  - **Safe on localhost, signed in beyond it.** On localhost there is nothing to sign in to. Reached through the
49
- edge or your network, every page and call needs a sign-in, and there is no password: `acme web login` prints a
50
- one-time link.
63
+ edge from another machine, or over your network, every page and call needs a sign-in, and there is no password:
64
+ Sign in with GitHub as the Den's owner, approve the computer with a code (`acme web approve <code>`), or open a
65
+ one-time link from `acme web login`.
51
66
  - **Opinionated, not locked in.** Good defaults are switched on, and almost everything can be switched off. The
52
67
  safety guards stay on. Den never needs our accounts, secrets or certificates. Everything specific to you
53
68
  lives in your own repo.
54
69
 
55
70
  <p align="center">
56
- <img src="docs/images/0.4.4/agents.png" width="49%" alt="The Agents tab: the demo workspace's sessions on the left, and one open in a terminal pane on the right, running demo-api's test and then the service">
57
- <img src="docs/images/0.4.4/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
58
- <img src="docs/images/0.4.4/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
59
- <img src="docs/images/0.4.4/edge.png" width="49%" alt="The Edge page: the edge container up, its ports and TLS, and every route it serves, Den's own and each stack's, with its upstream">
71
+ <img src="docs/images/0.4.6/agents.png" width="49%" alt="The Agents tab: the demo workspace's sessions on the left, and one open in a terminal pane on the right, running demo-api's test and then the service">
72
+ <img src="docs/images/0.4.6/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
73
+ <img src="docs/images/0.4.6/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
74
+ <img src="docs/images/0.4.6/edge.png" width="49%" alt="The Edge page: the edge container up, its ports and TLS, and every route it serves, Den's own and each stack's, with its upstream">
60
75
  </p>
61
76
 
62
77
  ## Get started