@monster-orange/den 0.4.20 → 0.4.22

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 (46) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/README.md +7 -7
  3. package/docs/distributions.md +167 -19
  4. package/package.json +1 -1
  5. package/service/install.sh +213 -63
  6. package/src/shared/agents/den-claude.ts +13 -1
  7. package/src/shared/agents/records.ts +6 -0
  8. package/src/shared/agents/runtime.ts +6 -0
  9. package/src/shared/agents/session-name.ts +21 -1
  10. package/src/shared/attest-code.ts +125 -0
  11. package/src/shared/claim-code.ts +6 -2
  12. package/src/{ui/server/edge-exposure.ts → shared/edge/exposure.ts} +7 -1
  13. package/src/shared/github-app.ts +4 -0
  14. package/src/shared/github-device.ts +38 -1
  15. package/src/shared/launchd-session.ts +92 -0
  16. package/src/shared/owner-github.ts +100 -0
  17. package/src/shared/product.ts +45 -5
  18. package/src/shared/profile.ts +26 -2
  19. package/src/shared/sessions/client.ts +24 -0
  20. package/src/shared/sessions/host.ts +114 -5
  21. package/src/shared/sessions/types.ts +5 -0
  22. package/src/shared/web-session.ts +112 -6
  23. package/src/ui/cli/agent-ops.ts +20 -2
  24. package/src/ui/cli/agents-command.ts +32 -1
  25. package/src/ui/cli/create.ts +92 -6
  26. package/src/ui/cli/doctor-command.ts +126 -15
  27. package/src/ui/cli/doctor-owner.ts +122 -0
  28. package/src/ui/cli/doctor-where.ts +203 -0
  29. package/src/ui/cli/index.ts +1 -1
  30. package/src/ui/cli/service-command.ts +233 -11
  31. package/src/ui/cli/sessions-command.ts +46 -12
  32. package/src/ui/cli/web-command.ts +78 -12
  33. package/src/ui/server/device-flows.ts +45 -10
  34. package/src/ui/server/github-routes.ts +169 -16
  35. package/src/ui/server/index.ts +147 -12
  36. package/src/ui/server/public/agents-page.js +103 -6
  37. package/src/ui/server/public/agents.html +30 -0
  38. package/src/ui/server/public/error-page.js +115 -33
  39. package/src/ui/server/public/error.html +16 -0
  40. package/src/ui/server/public/header.js +5 -7
  41. package/src/ui/server/public/i18n/es.js +24 -1
  42. package/src/ui/server/public/i18n/ja.js +24 -1
  43. package/src/ui/server/public/settings-registry.js +1 -1
  44. package/src/ui/server/public/shell.js +24 -1
  45. package/src/ui/server/route-classes.ts +7 -0
  46. package/src/ui/server/session-routes.ts +4 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,170 @@ 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.22] - 2026-11-10
8
+
9
+ ### Added
10
+
11
+ - **Opt-in: agents meant to keep running across logout (D18, D20).**
12
+ `service install --mode always` installs the web server and the session
13
+ host as LaunchDaemons that run as you (`UserName`): they start at boot
14
+ and are meant to keep running when you log out of the desktop, and so
15
+ every terminal and agent. It is not yet proven across a logout: `service
16
+ help`, `--plan`, install, `doctor` and the agents page say so. It takes your admin password once (sudo); `--mode login`, the
17
+ default, keeps LaunchAgents in the desktop session (they stop at
18
+ logout). Without `--mode`, install keeps the mode already installed.
19
+ Switching removes the other mode's jobs.
20
+ - **The session host is its own launchd job** (`<label>` with `.web` →
21
+ `.sessions`), in both modes, so it runs in the mode's session whoever
22
+ starts the first terminal: before, it inherited the launchd session of
23
+ whatever started it, and one started from the desktop ended at logout
24
+ with every agent in it. `<cli> sessions` and agents wait for the job.
25
+ - **Full Disk Access for Den, not for every node program.** The host job
26
+ runs a copy of node Den owns, `<stateDir>/bin/<cli>-sessions-host`
27
+ (0700), the path to grant Full Disk Access to (protected folders: other
28
+ volumes, Desktop, Documents); agents inherit it. Install refreshes the
29
+ copy when node changes and says when the grant must be given again (a
30
+ new signer, an ad-hoc signed node). A node that can't be copied
31
+ (Homebrew's) is run directly, and install says so.
32
+ - **Where it runs, shown honestly.** Every session records the launchd
33
+ session its host ran in (`launchctl managername`). The Agents page tags
34
+ each agent **Stops at logout** (the desktop session; tap for the fix) or
35
+ **No GUI** (kept across logout: no login keychain, no GUI apps; 1Password
36
+ through the service-account token, headless browsers, Full Disk Access
37
+ for protected folders). `doctor` ends with **where it runs**: the
38
+ server's and the host's session, Full Disk Access and the path to grant,
39
+ and the service's mode. `web up` from an ssh (Background) shell says what
40
+ a server started there means.
41
+ - **Rename, visible (D25).** **Rename…** in an agent pane's ⋯ menu and
42
+ **Rename** on a stopped agent's row, by touch or keyboard, and
43
+ `<cli> agents rename <id> <name>` (`--clear`: back to its id). Before,
44
+ only a double-click on an open tab renamed one.
45
+ - **Agents tell apart in a list of sessions (D24).** Den names each
46
+ Claude Code agent's session `<cli>-<ws>:<name, else id>` (`--name`) at
47
+ start and resume, so another session listing its peers sees which agent
48
+ is which, not a generic name. A rename shows at its next start or resume.
49
+
50
+ ### Fixed
51
+
52
+ - **`service install` said running while its job crash-looped (D26).** A
53
+ plain `web up` server of the same install on the port answered the
54
+ check, while the job failed to start on the taken port. Install now
55
+ stops a plain server of this install first, and says running only when
56
+ the server answering is the job's own process; `service status` says
57
+ when another process holds the port. `--plan` says when install would
58
+ stop one.
59
+ - **`service status` names a job whose plist is present but not loaded**,
60
+ with the `launchctl bootstrap` that loads it (`sudo` for a LaunchDaemon);
61
+ `doctor` shows it too.
62
+ - **Tests never reach this machine's launchctl, gh or vendor CLIs**, even
63
+ through a spawn without `env`: the inert stand-ins are now first on the
64
+ real PATH (jest's globalSetup), not only on each test file's sandboxed
65
+ `process.env`.
66
+
67
+ ### Migrations
68
+
69
+ - None required. An existing install keeps its mode (login). To try
70
+ keeping agents running across logout (not yet proven), run
71
+ `<cli> service install --mode always` once (one sudo), then grant Full
72
+ Disk Access to the path `doctor` names if agents read protected folders.
73
+ - `<cli> service` with no subcommand prints its help (before, an error).
74
+
75
+ ## [0.4.21] - 2026-11-09
76
+
77
+ ### Added
78
+
79
+ - **Every developer connects GitHub from the browser (D17).** A company
80
+ makes its own Den repo, every developer runs their own instance, and
81
+ everyone opens the same edge URL (such as `https://den.local.<zone>`,
82
+ whose DNS says 127.0.0.1). Before, they all got the signed-out page with
83
+ no way in from the browser: behind Docker Desktop, OrbStack or a TCP
84
+ passthrough the edge sees a gateway's address, so Den couldn't tell a
85
+ browser on its own machine; the one-time link needed a terminal, pairing
86
+ needed Settings, and Sign in with GitHub needed an owner already linked.
87
+ Now, while an instance has no owner, the signed-out page leads with
88
+ **Connect your GitHub account**:
89
+ - **This is my computer** (for a member of `web.allowed_github_org`):
90
+ a top-level round trip through `http://localhost:<port>/attest`, which
91
+ only a browser on this machine reaches, back to the edge page with a
92
+ short-lived, single-use code bound to that edge hostname and signed
93
+ with `web.secret` (in the URL fragment, never logged; only to this
94
+ Den's own edge hostnames). The GitHub sign-in that follows makes a
95
+ member of the org the owner and signs the browser in. The proof alone
96
+ never makes a session (any program on the machine can reach
97
+ localhost), so the GitHub approval is always the person's.
98
+ - **Sign in as @login** (`web.owner_github`): that login claims it from
99
+ any browser, such as a laptop reaching a devbox.
100
+ - Anyone else is refused with the reason (not a member; a member but not
101
+ on this computer; not that login; a private membership the GitHub App
102
+ can't read, naming the fix). With neither set, the page names both
103
+ settings and offers no claim.
104
+ - **`create --github-org <org>`** writes `web.allowed_github_org` into the
105
+ team config; without the flag it uses the GitHub owner of the repo the
106
+ new folder is in, else writes a commented line and says to set it
107
+ (`create --json`: `githubOrg`, null when unknown).
108
+ - **`web up`, `web restart` and `service install` record
109
+ `web.owner_github`** in `config/local/user.yml` from `gh api user` when
110
+ `gh` is signed in, no owner is linked and it isn't set, and print it.
111
+ When `gh` isn't signed in, they and `doctor` say so, with the fix
112
+ (`gh auth login`, or set `web.owner_github`).
113
+ - **doctor** shows who may claim the instance (`web:owner` in
114
+ `doctor --json`), and fails when there is no owner, neither setting, and
115
+ the edge is reachable from other machines.
116
+ - **`web.login_link_ttl` (D16)**: how long a sign-in link lasts, 5 minutes
117
+ unless set (it was 60 s, and expired while people switched windows);
118
+ 30s to 60m. `web login` shows the time left while it waits.
119
+
120
+ ### Fixed
121
+
122
+ - doctor no longer shows `usage (invalid config) ✗ unavailable: no selector
123
+ for this kind` on every fresh install (D9). Usage sources are all read at
124
+ once, so the row names them (`claude-code (implicit)`), as any strategy
125
+ kind with no selector of its own now does; never a failure. The state
126
+ column lines up past 20 characters.
127
+ - **Every page was broken for a distribution whose name has a hyphen**
128
+ (`acme-den`), since 0.4.20 at least. `header.js` put the
129
+ `__DEN_CLI__` placeholder inside a variable name, so the served file
130
+ read `let _acme-denConfig`, a syntax error: no page mounted anything,
131
+ the signed-out page included ("Error / local"), and Settings showed an
132
+ error. A new check fills every served page and script with hyphenated,
133
+ digit-bearing and short names and parses each one, and no placeholder
134
+ may touch a name's letters.
135
+ - `/attest` reached through the edge shows its own "only on the computer
136
+ this Den runs on" page, not the generic signed-out one.
137
+ - doctor, when `web.owner_github` isn't set but `gh` is signed in, says to
138
+ run `web restart` to record it (not that gh isn't signed in).
139
+
140
+ ### Changed
141
+
142
+ - The sign-in smoke runs against a remote browser with `DEN_PW_WS` (a
143
+ Playwright run-server): WebKit from Playwright's Linux image when the
144
+ shell can't start a GUI app (CONTRIBUTING.md).
145
+ - The GitHub code on the sign-in page has its copy button right beside it,
146
+ on its line (as every command on that page), at any width.
147
+ - Tests put a stub `launchctl` first on PATH, as for gh and the agent
148
+ CLIs: no test can reach this machine's real launchd.
149
+ - Pages, `--version`, doctor and the server agent's name say the brand's
150
+ name (`brand.name` in the org config: "Sign in to Acme Den"), not the CLI
151
+ name upper-cased ("Acme-den") (D11). A `brand.name` with characters that
152
+ could break a page (quotes, `&`, `<`) falls back to the old title.
153
+ - `product.cli` in den.yml must be lower-case letters, digits and `-`,
154
+ starting with a letter (as `product.name`). `product.config`,
155
+ `manifest`, `services_dir` and `workspaces_dir` may use letters, digits,
156
+ `.`, `_`, `-` and `/` only: the pages carry these names, and a quote or
157
+ `$` would break their scripts. A value outside that is refused at start,
158
+ naming the key.
159
+ - The page behind a `web login` link that asks for its code is a plain
160
+ sign-in page: no shell frame and no request id, the code field focused.
161
+
162
+ ### Migrations
163
+
164
+ - A `product.cli` or product path outside the characters above now stops
165
+ the CLI and server with a message naming the key: rename it.
166
+ - To let your team connect GitHub from the browser, set
167
+ `web.allowed_github_org: <your GitHub org>` in the team config (or run
168
+ `web up` with `gh` signed in, for `web.owner_github`). The GitHub App
169
+ needs the org's Members (read) permission to see private memberships.
170
+
7
171
  ## [0.4.20] - 2026-11-09
8
172
 
9
173
  ### Added
package/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
  </p>
11
11
 
12
12
  <picture>
13
- <source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.20/hero-dark.png">
14
- <img src="docs/images/0.4.20/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.21/hero-dark.png">
14
+ <img src="docs/images/0.4.21/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
17
  ## Set up by your agent
@@ -39,7 +39,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
39
39
  yours. Den connects them and turns on good practice from the start.
40
40
 
41
41
  <p align="center">
42
- <img src="docs/images/0.4.20/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">
42
+ <img src="docs/images/0.4.21/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">
43
43
  </p>
44
44
 
45
45
  ## How it helps
@@ -69,10 +69,10 @@ yours. Den connects them and turns on good practice from the start.
69
69
  lives in your own repo.
70
70
 
71
71
  <p align="center">
72
- <img src="docs/images/0.4.20/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">
73
- <img src="docs/images/0.4.20/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
74
- <img src="docs/images/0.4.20/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
75
- <img src="docs/images/0.4.20/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">
72
+ <img src="docs/images/0.4.21/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">
73
+ <img src="docs/images/0.4.21/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
74
+ <img src="docs/images/0.4.21/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
75
+ <img src="docs/images/0.4.21/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">
76
76
  </p>
77
77
 
78
78
  ## Get started
@@ -1004,15 +1004,21 @@ brand:
1004
1004
  - " └───────────┘"
1005
1005
  ```
1006
1006
 
1007
- | Field | Used for |
1008
- | --------- | ---------------------------------------------------------------------------- |
1009
- | `name` | Web page `<title>`s, the banner fallback, the web route on the edge (`acme`) |
1010
- | `tagline` | The line under the banner |
1011
- | `banner` | The CLI and web server startup banner |
1007
+ | Field | Used for |
1008
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1009
+ | `name` | The name people see: page titles and copy ("Sign in to Acme Den"), `--version`, doctor, the server agent's name, the banner fallback, the web route on the edge (`acme`) |
1010
+ | `tagline` | The line under the banner |
1011
+ | `banner` | The CLI and web server startup banner |
1012
1012
 
1013
1013
  Runtime names (env vars, files, directories) always come from `den.yml`,
1014
1014
  never from the brand.
1015
1015
 
1016
+ `brand.name` is used in pages and messages only when it is letters, digits,
1017
+ spaces, `.`, `_` and `-` (up to 40, starting with a letter or digit), so
1018
+ it can't break a page. Anything else (a quote, `&`, `<`) falls back to the
1019
+ product name with its first letter upper-cased ("Acme-den"); the page
1020
+ `<title>` still shows it.
1021
+
1016
1022
  ## Plugins
1017
1023
 
1018
1024
  A plugin adds to Den without changing it: a theme, a page in the web UI, and
@@ -1613,7 +1619,10 @@ user, and the CLI does that for you:
1613
1619
 
1614
1620
  - **The link** is `http://localhost:<port>/auth?c=<code>`, with `&next=<path>`
1615
1621
  for where to land (`web login --next /settings`). The code works once,
1616
- within 60 seconds, and only this server's secret makes one. It is printed
1622
+ within `web.login_link_ttl` (5 minutes unless set; 30s to 60m, as `90s`,
1623
+ `5m` or `1h`, in `config/local/user.yml` or the team config), and only
1624
+ this server's secret makes one. `web login` shows the time left while it
1625
+ waits, on a terminal. It is printed
1617
1626
  only on the one line you asked for (`web login`, or `web up` when it
1618
1627
  couldn't open the browser), never logged. Through the edge or a LAN name,
1619
1628
  the cookie belongs to that name: `web login --host acme.acme.localhost`
@@ -1853,7 +1862,8 @@ the account's id and login, never a token, and no other account gets in.
1853
1862
  last seen; "This device" on yours), each with **Revoke**, which ends its session at once (its
1854
1863
  open terminals included). **Pair a device** makes a one-time link and a
1855
1864
  QR code for an edge hostname you pick (`web login --host <name>` in the
1856
- browser): scan it on the other device within 60 seconds.
1865
+ browser): scan it on the other device before it expires
1866
+ (`web.login_link_ttl`; the page counts down).
1857
1867
  - **`web.github_client_id`** is the GitHub App's client id (public; no
1858
1868
  secret). The default is Den's own App, **Monster Orange Den**
1859
1869
  (`Iv23liKL12gnN6WvRV5b`), so Sign in with GitHub works out of the box. A
@@ -1868,6 +1878,61 @@ the account's id and login, never a token, and no other account gets in.
1868
1878
  it never prints the token. `GET /api/web/owner` reports it as
1869
1879
  `tokenExpiry: true`.
1870
1880
 
1881
+ ### A company's Den: every developer connects GitHub from the browser
1882
+
1883
+ The common shape: a company makes its own Den repo (`create`), pushes it to
1884
+ its GitHub org, and every developer clones it and runs their own instance
1885
+ on their own machine. Everyone opens the same URL (an edge name such as
1886
+ `https://den.local.<your zone>`, whose DNS says 127.0.0.1) and connects
1887
+ their GitHub account there, with no terminal and nothing to explain.
1888
+
1889
+ ```yaml
1890
+ # config/<name>.yml (the team's, committed): who may claim an instance
1891
+ web:
1892
+ allowed_github_org: acme
1893
+ ```
1894
+
1895
+ - **`create` writes it**: `--github-org <org>`, else the GitHub owner of the
1896
+ repo the new folder is in. Neither: a commented line and a next step.
1897
+ - **`web.owner_github`** (`config/local/user.yml`, per machine) is the
1898
+ GitHub login of whoever runs this instance. `web up`, `web restart` and
1899
+ `service install` record it from `gh` (`gh api user`) when `gh` is signed
1900
+ in, no owner is linked and it isn't set, and print it. Only the login is
1901
+ kept. This is the path most people take: org memberships are private
1902
+ by default. When `gh` isn't signed in, `web up` and `doctor` say so,
1903
+ with the fix (`gh auth login`, then `web restart`; or set
1904
+ `web.owner_github` yourself).
1905
+ - **The signed-out page leads with Connect your GitHub account** while the
1906
+ instance has no owner and one of the two is set. Who may claim it:
1907
+ - **A member of `allowed_github_org`, on this computer.** **This is my
1908
+ computer** navigates to `http://localhost:<port>/attest` and straight
1909
+ back: only a browser on the machine Den runs on reaches it there. It
1910
+ returns with a short-lived (2 minutes), single-use code, signed with
1911
+ `web.secret`, bound to the edge hostname, in the URL fragment (never
1912
+ sent to a server or logged), and only to one of this Den's own edge
1913
+ hostnames. The page then starts Sign in with GitHub with it; when
1914
+ GitHub says the account is a member of the org, it becomes the owner
1915
+ and the browser is signed in. This exists because Docker Desktop,
1916
+ OrbStack and a TCP passthrough show the edge a gateway's address, so
1917
+ Den can't tell a browser on its own machine from another one.
1918
+ - **`owner_github`'s login, from any browser** (a devbox you reach from a
1919
+ laptop).
1920
+ - **Anyone else is refused, with the reason**: not a member of the org;
1921
+ a member, but not on this computer; not the login `owner_github` names.
1922
+ The proof alone never signs a browser in: any program on the machine
1923
+ can reach localhost, so the GitHub approval is always the person's.
1924
+ - **Membership.** Den asks GitHub with the sign-in's own token: the
1925
+ account's own membership (`GET /user/memberships/orgs/<org>`, which needs
1926
+ the App to have the org's Members (read) permission), then the public
1927
+ member list. A private membership the App can't read is refused, saying
1928
+ so: make the membership public, give the App that permission, or set
1929
+ `web.owner_github`.
1930
+ - **Neither set**: the page names both settings and offers no claim.
1931
+ `doctor` shows who may claim the instance, and fails (✗) when neither is
1932
+ set and the edge is reachable from other machines.
1933
+ - Once claimed, it is an ordinary owner: Sign in with GitHub from any
1934
+ browser, as above.
1935
+
1871
1936
  ### The GitHub App (once, by whoever ships the Den)
1872
1937
 
1873
1938
  Den's default App is Monster Orange Den. A distribution that wants its own
@@ -1892,16 +1957,88 @@ Apps › New GitHub App):
1892
1957
 
1893
1958
  ## Running the web UI as a service (macOS)
1894
1959
 
1895
- Den ships a launchd installer that runs the web UI at login and restarts it if
1896
- it exits:
1960
+ Den ships a launchd installer that runs the web UI and the session host (every
1961
+ terminal and agent) as launchd jobs, restarted if they exit:
1897
1962
 
1898
1963
  ```bash
1899
- ./bin/acme service install # install (or refresh) and start it
1964
+ ./bin/acme service install # install (or refresh) and start them
1965
+ ./bin/acme service install --mode always # meant to keep them running across logout (opt-in, not yet proven)
1900
1966
  ./bin/acme service install --plan # what install would do; writes and loads nothing
1901
1967
  ./bin/acme service status # launchd's view: state, pid, last exit code
1902
- ./bin/acme service print # the LaunchAgent plist it would install; installs nothing
1903
- ./bin/acme service uninstall # stop it and remove the LaunchAgent
1904
- ```
1968
+ ./bin/acme service print # the web plist it would install; installs nothing
1969
+ ./bin/acme service uninstall # stop them and remove their plists
1970
+ ```
1971
+
1972
+ ### Login or always
1973
+
1974
+ | `--mode` | Jobs | At logout |
1975
+ | ----------------- | ----------------------------------------------------- | ------------------------------------ |
1976
+ | `login` (default) | LaunchAgents in the desktop session (`gui/<uid>`) | stop, with every agent |
1977
+ | `always` | LaunchDaemons that run as you (`UserName`), from boot | meant to keep running, and so agents |
1978
+
1979
+ `always` is opt-in and not yet proven across a logout: no logout has
1980
+ shown it keeping an agent on the same conversation yet. Use `login` unless
1981
+ you are testing it.
1982
+
1983
+ `always` takes your admin password once (sudo writes and loads the
1984
+ LaunchDaemons in `/Library/LaunchDaemons`); it is never asked for again.
1985
+ Without `--mode`, install keeps the mode already installed, so re-running it
1986
+ after an upgrade never changes it. Switching mode removes the other mode's
1987
+ jobs: never both. Use `always` on a machine you reach from elsewhere (ssh, a
1988
+ browser on another machine); `login` on a laptop.
1989
+
1990
+ A process outside the desktop session has no login keychain and no GUI. So
1991
+ under `always`, agents use:
1992
+
1993
+ - **1Password**: the service-account token (`secrets.onepassword.token`),
1994
+ not the desktop app. `doctor` warns when this process has none.
1995
+ - **Browsers**: headless Chromium and Firefox. WebKit on macOS needs a
1996
+ desktop session; run it from Playwright's Linux image instead
1997
+ (CONTRIBUTING.md, `DEN_PW_WS`).
1998
+ - **Protected folders** (other volumes, Desktop, Documents): Full Disk
1999
+ Access, granted once (below).
2000
+
2001
+ Each session records the launchd session its host ran in, and the Agents page
2002
+ tags it: **Stops at logout** (the desktop session; tap it for the fix) or
2003
+ **No GUI** (kept across logout). `web up` from an ssh shell (a Background
2004
+ session) says the same before it starts a server there.
2005
+
2006
+ ### The session host job and Full Disk Access
2007
+
2008
+ Both modes install the session host as its own job, `<label>` with `.web`
2009
+ replaced by `.sessions` (`local.acme.sessions`), so it runs in the mode's
2010
+ session whoever starts the first terminal; `<cli> sessions` and agents then
2011
+ wait for it rather than starting a host in the caller's session. It runs a
2012
+ copy of node Den owns, `<stateDir>/bin/<cli>-sessions-host` (0700), and
2013
+ macOS grants Full Disk Access to that path: never to the shared node, which
2014
+ would give it to every node program. To grant it: System Settings → Privacy &
2015
+ Security → Full Disk Access → + → that path (⌘⇧G to type it), then
2016
+ `<cli> sessions host --stop` (it restarts with the grant). Agents inherit it.
2017
+
2018
+ Install refreshes the copy when node changes. An official node build
2019
+ (nodejs.org, nvm) is signed by the Node.js project, so the grant holds across
2020
+ upgrades; install says when the signer changed and it must be granted again.
2021
+ An ad-hoc signed node needs a new grant after every change, and Homebrew's
2022
+ node loads libraries beside it and can't be copied: install then runs it
2023
+ directly and says so. `doctor` shows whether the host has Full Disk Access,
2024
+ and the path to grant.
2025
+
2026
+ ### What doctor shows
2027
+
2028
+ `doctor` ends with **where it runs**: the web server's and the session host's
2029
+ launchd session (a warning with the fix when it stops at logout), Full Disk
2030
+ Access, and the service: its mode, or `plist present, job not loaded:
2031
+ launchctl bootstrap <domain> <plist>` (`sudo` for a LaunchDaemon) when launchd
2032
+ has dropped a job whose plist is still there.
2033
+
2034
+ ### Running means the job's own process
2035
+
2036
+ A plain `web up` server of the same install on the port would answer for the
2037
+ job, which then fails to start on the taken port and restarts forever. So
2038
+ install stops a plain server of this install first (and says so), and says
2039
+ `running` only when the server answering is the job's own process (its pid is
2040
+ launchd's). Otherwise it says `port <N> is served by pid <P>, not the job`
2041
+ and exits 1; `status` says the same.
1905
2042
 
1906
2043
  The label and log directory come from the org config:
1907
2044
 
@@ -1946,12 +2083,14 @@ node_modules/@monster-orange/den/service/install.sh uninstall
1946
2083
  node_modules/@monster-orange/den/service/install.sh print # the plist only
1947
2084
  ```
1948
2085
 
1949
- | Variable | Default |
1950
- | ------------------- | ---------------------------------------- |
1951
- | `DEN_ROOT` | required in a distribution |
1952
- | `DEN_SERVICE_LABEL` | `local.<distribution dir name>.web` |
1953
- | `DEN_SERVICE_LOGS` | `~/Library/Logs/<distribution dir name>` |
1954
- | `DEN_SERVICE_NODE` | see below |
2086
+ | Variable | Default |
2087
+ | ---------------------- | ----------------------------------------------------------------------- |
2088
+ | `DEN_ROOT` | required in a distribution |
2089
+ | `DEN_SERVICE_LABEL` | `local.<distribution dir name>.web` |
2090
+ | `DEN_SERVICE_LOGS` | `~/Library/Logs/<distribution dir name>` |
2091
+ | `DEN_SERVICE_NODE` | see below |
2092
+ | `DEN_SERVICE_MODE` | `login` (or `always`) |
2093
+ | `DEN_SERVICE_HOST_EXE` | none: the host runs that node (`<cli> service install` sets Den's copy) |
1955
2094
 
1956
2095
  The service runs the engine's server, from the distribution root, with the
1957
2096
  `node` on your `PATH` at install time. A version manager's node (nvm, fnm,
@@ -2430,6 +2569,15 @@ server scoped to its workspace.
2430
2569
  start and resume answers). 0.4.12 sent the name shown as `sessionName`;
2431
2570
  read `sessionTitle` instead. Records are unchanged, so none written
2432
2571
  before needs migrating.
2572
+ - **Renaming.** `agents rename <id> <name>` (or `--clear`, back to its
2573
+ id) names an agent; so do **Rename…** in its pane's ⋯ menu and **Rename**
2574
+ on a stopped agent's row, by touch or keyboard, as well as a double-click
2575
+ on its tab. The id never changes.
2576
+ - **The name other sessions see.** Den names a Claude Code agent's session
2577
+ `<cli>-<ws>:<name, else id>` (`--name`) at every start and resume, so
2578
+ another session listing its peers tells Den's agents apart. A rename
2579
+ shows at its next start or resume. The record's `peerName` is the name
2580
+ last given, so reading it back is never taken for a `/rename`.
2433
2581
  - `list` shows agents only: sessions with an agent record. A session from
2434
2582
  `sessions start` is not an agent; `sessions list` shows it.
2435
2583
  - `stop` sends its session TERM and marks it stopped. `resume` works only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monster-orange/den",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
4
4
  "description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
5
5
  "type": "module",
6
6
  "bin": {