@monster-orange/den 0.4.2 → 0.4.3
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/CHANGELOG.md +94 -0
- package/README.md +7 -7
- package/docs/distributions.md +126 -13
- package/package.json +1 -1
- package/src/shared/agents/den-claude.ts +57 -12
- package/src/shared/agents/generic.ts +1 -0
- package/src/shared/agents/runtime-search.ts +416 -0
- package/src/shared/agents/runtime.ts +26 -1
- package/src/shared/agents/runtimes.ts +30 -3
- package/src/shared/den-config.ts +36 -0
- package/src/shared/edge/render.ts +31 -0
- package/src/ui/cli/agent-ops.ts +107 -19
- package/src/ui/cli/agents-command.ts +99 -3
- package/src/ui/cli/sessions-command.ts +9 -4
- package/src/ui/cli/web-command.ts +53 -8
- package/src/ui/server/agent-routes.ts +9 -2
- package/src/ui/server/edge-exposure.ts +143 -0
- package/src/ui/server/index.ts +94 -19
- package/src/ui/server/public/agent-start.js +202 -0
- package/src/ui/server/public/agents-page.js +83 -17
- package/src/ui/server/public/agents-section.js +6 -18
- package/src/ui/server/public/agents.html +70 -0
- package/src/ui/server/public/i18n/es.js +11 -0
- package/src/ui/server/public/i18n/ja.js +11 -0
- package/src/ui/server/public/local-dev.html +43 -14
- package/src/ui/server/public/settings-registry.js +1 -1
- package/src/ui/server/public/shell.js +40 -0
- package/src/ui/server/public/topbar.js +127 -28
- package/src/ui/server/request-guard.ts +122 -7
- package/src/ui/server/ui-assets.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,99 @@ 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.3] - 2026-11-06
|
|
8
|
+
|
|
9
|
+
Agents start, and the edge counts as local.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **The Start agent dialog can be submitted (AG-10.0).** A persona's whole
|
|
14
|
+
role sentence was in its option, so the select grew past the dialog and
|
|
15
|
+
pushed Cancel and Start out of sight. A persona option now shows its id,
|
|
16
|
+
with its role on a help line under the select that follows the pick. The
|
|
17
|
+
runtime's first option names what it resolves to ("Claude Code (persona
|
|
18
|
+
default)"), a runtime that can't run here says why, and Enter starts
|
|
19
|
+
from any field. The Workspaces page's agents form gets the same choices.
|
|
20
|
+
|
|
21
|
+
- **Agents start on a Den run as a service (AG-0.1).** Starting any agent
|
|
22
|
+
failed with "den-claude is unavailable: claude is not on PATH" when the
|
|
23
|
+
web server ran under launchd: its PATH leaves out `~/.local/bin`, where
|
|
24
|
+
Claude Code's native installer puts `claude`, and Den searched only that
|
|
25
|
+
PATH. Den now finds a runtime's CLI as your terminal would:
|
|
26
|
+
`agents.runtimes.<id>.bin` in `config/local/user.yml` wins (an absolute
|
|
27
|
+
path, checked; a bad one is refused with its reason), then the login
|
|
28
|
+
shell's PATH (`$SHELL -lc`, asked once at server start in the
|
|
29
|
+
background, 3-second timeout, falling back to the current PATH), the
|
|
30
|
+
server's PATH, and the runtime's install directories (`~/.local/bin`,
|
|
31
|
+
`~/.claude/local`, `/opt/homebrew/bin`, `/usr/local/bin`, npm's global
|
|
32
|
+
bin). Agents and plain sessions get the same directories on their PATH.
|
|
33
|
+
- **The lock is always reachable (LK-1 to LK-3).** Start agent opened its
|
|
34
|
+
dialog and only then asked for the lock, whose popover sat under the
|
|
35
|
+
dialog where nothing could reach it. The lock is now checked before the
|
|
36
|
+
dialog opens, and the dialog asks for the admin password at its top:
|
|
37
|
+
Start unlocks, then starts. The lock popover is in the top layer, so no
|
|
38
|
+
dialog covers it. A page that thinks it's locked asks the server before
|
|
39
|
+
asking you (another tab or device may have unlocked it), and, when a
|
|
40
|
+
password is stored in the vault, the first action that needs the lock
|
|
41
|
+
loads it from there on every page, not only Local Dev. A page view still
|
|
42
|
+
never wakes the vault.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **The edge from this machine needs no sign-in.** A request for the edge
|
|
47
|
+
hostname from an edge peer is treated as loopback: no sign-in on any
|
|
48
|
+
route, agents and the terminal included (0.4.2 asked for one on every
|
|
49
|
+
route). Host, Origin, CSRF and the agent-token rules stay. The client is
|
|
50
|
+
the left-most `X-Forwarded-For` entry: loopback or one of this machine's
|
|
51
|
+
own interface addresses (a Tailscale `100.x` included) is local; missing,
|
|
52
|
+
or a Docker/VM network's address (what Docker Desktop and OrbStack hand
|
|
53
|
+
the edge; the subnets of the edge's own Docker networks, `<org>-edge`
|
|
54
|
+
and those it joins, read once at start with `docker network inspect`;
|
|
55
|
+
OrbStack's host address `0.250.250.254`), is "can't tell", also local, so
|
|
56
|
+
a TCP-passthrough proxy container on the edge's network is no other
|
|
57
|
+
machine; any other address is another
|
|
58
|
+
machine and needs a sign-in. A non-loopback `DEN_BIND_HOST` bind (other
|
|
59
|
+
than the Linux edge's Docker bridge) and `web.allow_plain_http_remote`'s
|
|
60
|
+
LAN names still need one. See docs/distributions.md, "Signing in".
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- **The Start dialog says before Start whether the agent can run
|
|
65
|
+
(AG-0.2).** Under the runtime a line names what Start will run, with its
|
|
66
|
+
version and path ("Starts Claude Code 2.1.0 from …"); the runtime's
|
|
67
|
+
option shows its version. When the runtime can't run here, Start is
|
|
68
|
+
disabled and the line says why and how to fix it: where Den looked, then
|
|
69
|
+
install it, or set `agents.runtimes.<id>.bin` in the user.yml it names,
|
|
70
|
+
or run `<cli> agents doctor`. The Workspaces page's agents form does the
|
|
71
|
+
same. `GET /api/personas` gives each runtime `version` (its version
|
|
72
|
+
command, run once under a timeout and kept), `path`, `source`
|
|
73
|
+
(`setting`, `login-shell`, `well-known` or `PATH`), `searched` and
|
|
74
|
+
`setting`, and the response a `userConfig` path.
|
|
75
|
+
- **`<cli> agents doctor [--json]` (AG-0.3)**: each runtime's status, the
|
|
76
|
+
CLI it runs and where it came from, its version, the directories
|
|
77
|
+
searched (names only, never an environment value), and the fix when it
|
|
78
|
+
is missing. It exits 1 when the default runtime is unavailable.
|
|
79
|
+
|
|
80
|
+
- **"Your edge is reachable from other machines".** When an edge hostname
|
|
81
|
+
resolves to an address that isn't loopback, or the edge container
|
|
82
|
+
publishes a port on every interface (`docker inspect`), Den says so once
|
|
83
|
+
in `web.log` at start and in a banner on every page (dismissed per
|
|
84
|
+
browser): "Your edge is reachable from other machines; anyone who can
|
|
85
|
+
reach it can use terminals. Auth is coming."
|
|
86
|
+
- **`<cli> web login --host <name>`** mints the one-time link for an edge
|
|
87
|
+
hostname (an edge domain or alias, a VPN name among them) at its edge
|
|
88
|
+
URL, instead of localhost. A name the edge doesn't route to this server
|
|
89
|
+
is refused, naming the hostnames it does and what to add. `--origin`
|
|
90
|
+
stays.
|
|
91
|
+
|
|
92
|
+
### Migrations
|
|
93
|
+
|
|
94
|
+
- **A Den run as a service whose agents' CLI is somewhere unusual**: set
|
|
95
|
+
`agents.runtimes.<id>.bin` (an absolute path) in `config/local/user.yml`.
|
|
96
|
+
Nothing else changes: a runtime on the server's PATH is found as before.
|
|
97
|
+
- **A page script that called `requireUnlock()`** (header.js): it now
|
|
98
|
+
returns a promise of the answer; `await` it.
|
|
99
|
+
|
|
7
100
|
## [0.4.2] - 2026-11-06
|
|
8
101
|
|
|
9
102
|
The 0.4.1 carry-overs.
|
|
@@ -43,6 +136,7 @@ The 0.4.1 carry-overs.
|
|
|
43
136
|
that…"); there are two spaces now. `down`, `restart` and `setup --help`
|
|
44
137
|
were already spaced.
|
|
45
138
|
|
|
139
|
+
|
|
46
140
|
### Migrations
|
|
47
141
|
|
|
48
142
|
- **More than 4 plain sessions at once**, or a script that relied on
|
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.
|
|
14
|
-
<img src="docs/images/0.4.
|
|
13
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.3/hero-dark.png">
|
|
14
|
+
<img src="docs/images/0.4.3/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
|
## What Den is
|
|
@@ -24,7 +24,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
|
|
|
24
24
|
yours. Den connects them and turns on good practice from the start.
|
|
25
25
|
|
|
26
26
|
<p align="center">
|
|
27
|
-
<img src="docs/images/0.4.
|
|
27
|
+
<img src="docs/images/0.4.3/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
28
|
</p>
|
|
29
29
|
|
|
30
30
|
## How it helps
|
|
@@ -53,10 +53,10 @@ yours. Den connects them and turns on good practice from the start.
|
|
|
53
53
|
lives in your own repo.
|
|
54
54
|
|
|
55
55
|
<p align="center">
|
|
56
|
-
<img src="docs/images/0.4.
|
|
57
|
-
<img src="docs/images/0.4.
|
|
58
|
-
<img src="docs/images/0.4.
|
|
59
|
-
<img src="docs/images/0.4.
|
|
56
|
+
<img src="docs/images/0.4.3/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.3/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.3/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.3/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
60
|
</p>
|
|
61
61
|
|
|
62
62
|
## Get started
|
package/docs/distributions.md
CHANGED
|
@@ -383,6 +383,14 @@ works; only the saved password needs `refs.sudo`.
|
|
|
383
383
|
`configured`, `backendAvailable`, `reason` and `hasStored` (`opAvailable`
|
|
384
384
|
is a deprecated alias of `backendAvailable`, kept for one minor).
|
|
385
385
|
|
|
386
|
+
The lock (🔒 in the top bar) is asked for only when an action needs it,
|
|
387
|
+
on any page: a page that thinks it's locked asks the server again first
|
|
388
|
+
(another tab or device may have unlocked it), then, when a password is
|
|
389
|
+
stored, loads it from the vault (once a page; a page view alone never
|
|
390
|
+
wakes the vault), and only then shows the password field. Its popover is
|
|
391
|
+
always on top, even over a dialog, and a dialog that needs the lock (Start
|
|
392
|
+
agent) asks for the password itself.
|
|
393
|
+
|
|
386
394
|
A `1password` distribution with no refs gets one warning, in the web
|
|
387
395
|
server's startup log and in `<cli> validate`, with the YAML to add.
|
|
388
396
|
|
|
@@ -1183,9 +1191,11 @@ are the same directory.
|
|
|
1183
1191
|
|
|
1184
1192
|
On localhost nothing asks you to sign in: every page, the agents and the
|
|
1185
1193
|
terminal work as they are, and the lock (R8) is the only prompt (for
|
|
1186
|
-
privileged actions).
|
|
1187
|
-
this machine
|
|
1188
|
-
|
|
1194
|
+
privileged actions). The edge counts as local too (0.4.3): opening
|
|
1195
|
+
`https://acme.acme.localhost` from this machine asks for nothing.
|
|
1196
|
+
Signing in is for when the server is reached from another machine: the
|
|
1197
|
+
edge for a client the edge says is elsewhere, a LAN address, or a bind
|
|
1198
|
+
that isn't loopback (`DEN_BIND_HOST`).
|
|
1189
1199
|
|
|
1190
1200
|
There is no password. Reading `web.secret` (32 random bytes, mode 0600 in
|
|
1191
1201
|
the state dir, made at the first `web up`) proves you are this computer's
|
|
@@ -1205,7 +1215,11 @@ user, and the CLI does that for you:
|
|
|
1205
1215
|
within 60 seconds, and only this server's secret makes one. It is printed
|
|
1206
1216
|
only on the one line you asked for (`web login`, or `web up` when it
|
|
1207
1217
|
couldn't open the browser), never logged. Through the edge or a LAN name,
|
|
1208
|
-
the cookie belongs to that name: `web login --
|
|
1218
|
+
the cookie belongs to that name: `web login --host acme.acme.localhost`
|
|
1219
|
+
mints the link for an edge hostname (any edge domain or alias, a VPN name
|
|
1220
|
+
among them; its URL comes from the edge config, and a name the edge
|
|
1221
|
+
doesn't route here is refused, saying what to add), and
|
|
1222
|
+
`web login --origin http://<lan name>:<port>` for any other allowed name
|
|
1209
1223
|
(the signed-out page there shows the exact command).
|
|
1210
1224
|
- **The cookie** (`den_session_<port>`) is HttpOnly, SameSite=Strict and
|
|
1211
1225
|
Path=/, and Secure when the request came over HTTPS through the edge. The
|
|
@@ -1222,10 +1236,38 @@ user, and the CLI does that for you:
|
|
|
1222
1236
|
open, and changes (an agent's start or resume and the terminal included)
|
|
1223
1237
|
need only the page's own Origin and CSRF token, which the shell sends.
|
|
1224
1238
|
The Host allowlist, the Origin check and the CSRF token are what stop
|
|
1225
|
-
another site or a rebinding DNS name.
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1239
|
+
another site or a rebinding DNS name. From another machine, every page
|
|
1240
|
+
and call needs a signed-in browser (or a bearer). A page there shows
|
|
1241
|
+
"Sign in to use <name>" with the command to run; it never asks for a
|
|
1242
|
+
secret. `/api` answers 401 `signin_required`.
|
|
1243
|
+
- **The edge counts as local.** A request for the edge hostname from the
|
|
1244
|
+
edge (an edge peer, below) is treated as loopback: no sign-in on any
|
|
1245
|
+
route, the agents and the terminal included. Host, Origin, CSRF and the
|
|
1246
|
+
agent-token rules still apply. Who the client is comes from the left-most
|
|
1247
|
+
`X-Forwarded-For` entry the edge sends:
|
|
1248
|
+
- loopback, or one of this machine's own interface addresses (a LAN
|
|
1249
|
+
address, or a VPN's such as a Tailscale `100.x`): local;
|
|
1250
|
+
- missing, or a container/VM network's address (the Docker ranges, or a
|
|
1251
|
+
subnet of one of this machine's Docker/OrbStack/VM bridges): Den can't
|
|
1252
|
+
tell (Docker Desktop and OrbStack on macOS hand the edge their
|
|
1253
|
+
gateway's address, and a TCP passthrough in front of the edge hides the
|
|
1254
|
+
client), so local;
|
|
1255
|
+
- any other address: another machine, which needs a sign-in.
|
|
1256
|
+
|
|
1257
|
+
With `DEN_BIND_HOST` beyond loopback (a deliberate exposure, other than
|
|
1258
|
+
the Linux edge's Docker bridge address) the edge needs a sign-in as
|
|
1259
|
+
before. With `forwardedHeaders.insecure` (the edge's default) Traefik
|
|
1260
|
+
keeps an `X-Forwarded-For` the client sent, so this is a convenience,
|
|
1261
|
+
not a boundary: whoever can reach the edge can use the terminal.
|
|
1262
|
+
|
|
1263
|
+
- **"Your edge is reachable from other machines".** When Den can tell, at
|
|
1264
|
+
start, that the edge is reachable beyond this machine (an edge hostname
|
|
1265
|
+
that resolves to an address that isn't loopback, such as a LAN or
|
|
1266
|
+
Tailscale address, or the edge container publishing a port on every
|
|
1267
|
+
interface, from `docker inspect`), it writes "Your edge is reachable from
|
|
1268
|
+
other machines; anyone who can reach it can use terminals. Auth is
|
|
1269
|
+
coming." once to `web.log`, and every page shows it as a banner (dismissed
|
|
1270
|
+
per browser).
|
|
1229
1271
|
- **localhost is the canonical name.** A cookie set on `localhost` isn't
|
|
1230
1272
|
sent to `127.0.0.1`, so the links Den prints use `localhost`.
|
|
1231
1273
|
- **Browsers.** Chromium, Firefox and Safari 16.4 or later: a sign-in link
|
|
@@ -1616,6 +1658,7 @@ server scoped to its workspace.
|
|
|
1616
1658
|
<cli> agents stop <id> [-w <ws>]
|
|
1617
1659
|
<cli> agents resume <id> [-w <ws>]
|
|
1618
1660
|
<cli> agents personas [--json]
|
|
1661
|
+
<cli> agents doctor [--json] # can each runtime run here, and why not
|
|
1619
1662
|
<cli> sessions attach <ws>:<id> # its terminal; ctrl-] detaches
|
|
1620
1663
|
```
|
|
1621
1664
|
|
|
@@ -1654,7 +1697,14 @@ started. The workspace header counts them ("2 agents").
|
|
|
1654
1697
|
not running.
|
|
1655
1698
|
- **+ Start agent** picks a persona (Den's pack, then
|
|
1656
1699
|
`agents.persona_packs`) and a runtime (the persona's own unless you pick
|
|
1657
|
-
one; one that can't run here is listed, disabled), then Start.
|
|
1700
|
+
one; one that can't run here is listed, disabled), then Start. Under the
|
|
1701
|
+
runtime a line says what Start will run, with its version and path
|
|
1702
|
+
("Starts Claude Code 2.1.0 from ~/.local/bin/claude"). When that runtime
|
|
1703
|
+
can't run here, Start is disabled and the line says why and how to fix
|
|
1704
|
+
it: where it looked, then "Install it, or set
|
|
1705
|
+
`agents.runtimes.<id>.bin` to its path in <your user.yml>, or run
|
|
1706
|
+
`<cli> agents doctor`" (see [Runtimes](#runtimes)). Nothing fails only
|
|
1707
|
+
after Start.
|
|
1658
1708
|
- The page and the CLI call the same operations and read the same agent
|
|
1659
1709
|
records, so an agent started in one is listed in the other. A workspace
|
|
1660
1710
|
lists its own agents only.
|
|
@@ -1695,7 +1745,10 @@ agent of the workspaces in view, and their terminals in the browser.
|
|
|
1695
1745
|
running, and opening it again replays what you missed. **Stop** (an
|
|
1696
1746
|
agent only) is `agents stop`, behind the lock. **+ Start agent** (or
|
|
1697
1747
|
`/agents?start=1`) starts one and opens its pane; under All it asks for
|
|
1698
|
-
the workspace.
|
|
1748
|
+
the workspace. It says before Start whether the runtime can run, as the
|
|
1749
|
+
Workspaces page's form does. While the lock is closed, the dialog asks
|
|
1750
|
+
for the admin password at its top, and Start unlocks, then starts, without
|
|
1751
|
+
closing anything.
|
|
1699
1752
|
- ⌘K lists each as "Agents › <ws> / <name>", and "Start agent…".
|
|
1700
1753
|
- Several browsers and `<cli> sessions attach` can attach to one session
|
|
1701
1754
|
at once and each sees every byte; the terminal's size is the last
|
|
@@ -1721,7 +1774,8 @@ An agent's environment starts empty and gets, in order:
|
|
|
1721
1774
|
|
|
1722
1775
|
1. `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM` (default
|
|
1723
1776
|
`xterm-256color`), `COLORTERM`, `LANG`, `LC_*`, `TZ` and `TMPDIR` from
|
|
1724
|
-
yours
|
|
1777
|
+
yours (`PATH` with the login shell's and the runtimes' install
|
|
1778
|
+
directories added: [Runtimes](#runtimes));
|
|
1725
1779
|
2. the variables its runtime names (`envPassthrough`; den-claude:
|
|
1726
1780
|
`CLAUDE_CONFIG_DIR`);
|
|
1727
1781
|
3. the runtime's launch env;
|
|
@@ -1832,8 +1886,9 @@ are errors naming the file or the key.
|
|
|
1832
1886
|
|
|
1833
1887
|
`<cli> doctor` lists the runtime kind. Two are built in:
|
|
1834
1888
|
|
|
1835
|
-
- **`den-claude`** (the default)
|
|
1836
|
-
version from the install, without running
|
|
1889
|
+
- **`den-claude`** (the default) runs Claude Code's `claude`, found as
|
|
1890
|
+
below; `<cli> doctor` reads its version from the install, without running
|
|
1891
|
+
it. It runs `claude --session-id
|
|
1837
1892
|
<id> --mcp-config <file> --strict-mcp-config --settings <file> [--model <m>]
|
|
1838
1893
|
--append-system-prompt <role prompt>`, so Den's MCP server is the agent's
|
|
1839
1894
|
only one, and resumes with `claude --resume <id>`. Permission prompts stay
|
|
@@ -1846,6 +1901,64 @@ are errors naming the file or the key.
|
|
|
1846
1901
|
`{mcp_config}` is the path of the agent's MCP config, written only when the
|
|
1847
1902
|
template references it. It declares no resume.
|
|
1848
1903
|
|
|
1904
|
+
**Where a runtime's CLI is found.** As your terminal would find it, not
|
|
1905
|
+
only on the web server's own PATH (a launchd service's PATH leaves out
|
|
1906
|
+
`~/.local/bin`, where Claude Code's native installer puts `claude`). The
|
|
1907
|
+
first match wins:
|
|
1908
|
+
|
|
1909
|
+
1. **`agents.runtimes.<id>.bin`** in `config/local/user.yml`: the absolute
|
|
1910
|
+
path of the runtime's CLI. Paths are per machine, so it is read from
|
|
1911
|
+
user.yml only, never from the distribution's config. A bad value (not
|
|
1912
|
+
absolute, missing, not an executable file) makes the runtime
|
|
1913
|
+
unavailable with that reason; Den doesn't fall back to a search.
|
|
1914
|
+
|
|
1915
|
+
```yaml
|
|
1916
|
+
# config/local/user.yml (gitignored)
|
|
1917
|
+
agents:
|
|
1918
|
+
runtimes:
|
|
1919
|
+
den-claude:
|
|
1920
|
+
bin: /Users/me/.local/bin/claude
|
|
1921
|
+
```
|
|
1922
|
+
|
|
1923
|
+
2. **Your login shell's PATH**: the web server asks `$SHELL -lc` once at
|
|
1924
|
+
start, in the background, under a 3-second timeout. On a timeout or
|
|
1925
|
+
any failure it is left out.
|
|
1926
|
+
3. **The server's own PATH.**
|
|
1927
|
+
4. **The runtime's install directories.** For den-claude: `~/.local/bin`,
|
|
1928
|
+
`~/.claude/local`, `/opt/homebrew/bin`, `/usr/local/bin` and npm's
|
|
1929
|
+
global bin (`$npm_config_prefix/bin`, and node's own directory). Den
|
|
1930
|
+
never runs npm to find it.
|
|
1931
|
+
|
|
1932
|
+
Sessions and agents Den starts get the same directories on their PATH
|
|
1933
|
+
(and a configured CLI's own directory), so the runtime's own tools are
|
|
1934
|
+
found too. Two engine variables override the search for tests and
|
|
1935
|
+
diagnosis: `DEN_LOGIN_SHELL` (the shell to ask instead of `$SHELL`; `off`
|
|
1936
|
+
for none) and `DEN_RUNTIME_DIRS` (a PATH-style list that replaces every
|
|
1937
|
+
runtime's install directories; empty for none).
|
|
1938
|
+
|
|
1939
|
+
**`<cli> agents doctor`** prints, for each runtime, its status, the CLI it
|
|
1940
|
+
runs and where that came from (the `agents.runtimes` setting, the login
|
|
1941
|
+
shell's PATH, PATH, or an install directory), its version (`claude
|
|
1942
|
+
--version`, run once under a timeout), the directories it searched (names
|
|
1943
|
+
only, never an environment value), and the fix when it's missing. It
|
|
1944
|
+
resolves the login shell's PATH first, as the web server does. It exits 1
|
|
1945
|
+
when the default runtime is unavailable; `--json` prints what the Start
|
|
1946
|
+
dialog gets.
|
|
1947
|
+
|
|
1948
|
+
```
|
|
1949
|
+
den-claude (Claude Code) [default]
|
|
1950
|
+
status ✗ unavailable: claude not found (searched 5 directories)
|
|
1951
|
+
searched /usr/bin
|
|
1952
|
+
/bin
|
|
1953
|
+
…
|
|
1954
|
+
fix install Claude Code, or set agents.runtimes.den-claude.bin to its absolute path in /…/config/local/user.yml
|
|
1955
|
+
then run `<cli> agents doctor` again
|
|
1956
|
+
```
|
|
1957
|
+
|
|
1958
|
+
`GET /api/personas` gives each runtime its `version`, `path`, `source`
|
|
1959
|
+
(`setting`, `login-shell`, `well-known` or `PATH`), `searched` and
|
|
1960
|
+
`setting` (the user.yml key), and `userConfig`, the user.yml to edit.
|
|
1961
|
+
|
|
1849
1962
|
### Workspace scope and grants
|
|
1850
1963
|
|
|
1851
1964
|
An agent's MCP config, `<stateDir>/agent-files/<id>/mcp.json` (0600), runs
|
package/package.json
CHANGED
|
@@ -28,27 +28,50 @@
|
|
|
28
28
|
* session Den starts (an agent is not a child of the session that
|
|
29
29
|
* started it).
|
|
30
30
|
*
|
|
31
|
-
* detect runs nothing
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* detect runs nothing (AG-0.1, runtime-search.ts): agents.runtimes.den-claude.bin
|
|
32
|
+
* in user.yml when set, else `claude` on the login shell's PATH, the
|
|
33
|
+
* caller's PATH, then its install directories (~/.local/bin, the native
|
|
34
|
+
* installer's; ~/.claude/local; /opt/homebrew/bin; /usr/local/bin; npm's
|
|
35
|
+
* global bin). Its version is read from where it is installed (a native
|
|
36
|
+
* install's versions/<version>, or the npm package's package.json); the
|
|
37
|
+
* Start dialog and `agents doctor` ask `claude --version` once as well.
|
|
34
38
|
*/
|
|
35
39
|
import { randomUUID } from "crypto";
|
|
36
40
|
import * as fs from "fs";
|
|
37
41
|
import * as path from "path";
|
|
42
|
+
import { configuredRuntimeBin } from "../den-config.js";
|
|
38
43
|
import { rolePrompt } from "./personas.js";
|
|
39
44
|
import {
|
|
40
45
|
LaunchError,
|
|
41
46
|
mcpConfigJson,
|
|
42
47
|
mcpConfigPath,
|
|
43
|
-
|
|
48
|
+
type DetectOptions,
|
|
44
49
|
type LaunchInput,
|
|
45
50
|
type LaunchOutput,
|
|
46
51
|
type Runtime,
|
|
47
52
|
type RuntimeDetect,
|
|
48
53
|
} from "./runtime.js";
|
|
54
|
+
import {
|
|
55
|
+
locateRuntime,
|
|
56
|
+
systemBinDirs,
|
|
57
|
+
type RuntimeFind,
|
|
58
|
+
} from "./runtime-search.js";
|
|
49
59
|
|
|
50
60
|
export const DEN_CLAUDE_ID = "den-claude";
|
|
51
61
|
const BIN = "claude";
|
|
62
|
+
|
|
63
|
+
/** Where Claude Code is found, and how its version is asked. */
|
|
64
|
+
export const DEN_CLAUDE_FIND: RuntimeFind = {
|
|
65
|
+
bins: [BIN],
|
|
66
|
+
dirs: (home, env) => [
|
|
67
|
+
// The native installer's, then the older local install's.
|
|
68
|
+
...(home
|
|
69
|
+
? [path.join(home, ".local", "bin"), path.join(home, ".claude", "local")]
|
|
70
|
+
: []),
|
|
71
|
+
...systemBinDirs(env),
|
|
72
|
+
],
|
|
73
|
+
versionArgs: ["--version"],
|
|
74
|
+
};
|
|
52
75
|
const PACKAGE = "@anthropic-ai/claude-code";
|
|
53
76
|
const SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
54
77
|
|
|
@@ -97,11 +120,29 @@ export function installedVersion(bin: string): string | undefined {
|
|
|
97
120
|
return undefined;
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
function detect(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
function detect(
|
|
124
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
125
|
+
opts: DetectOptions = {},
|
|
126
|
+
): RuntimeDetect {
|
|
127
|
+
const setting =
|
|
128
|
+
opts.bin === undefined ? configuredRuntimeBin(DEN_CLAUDE_ID) : opts.bin;
|
|
129
|
+
const found = locateRuntime(DEN_CLAUDE_ID, DEN_CLAUDE_FIND, env, setting);
|
|
130
|
+
if (!found.available || !found.path)
|
|
131
|
+
return {
|
|
132
|
+
available: false,
|
|
133
|
+
why: found.why ?? `${BIN} not found`,
|
|
134
|
+
// "setting": a bad agents.runtimes.<id>.bin is the reason, not the search.
|
|
135
|
+
...(found.source ? { source: found.source } : {}),
|
|
136
|
+
searched: found.searched,
|
|
137
|
+
};
|
|
138
|
+
const version = installedVersion(found.path);
|
|
139
|
+
return {
|
|
140
|
+
available: true,
|
|
141
|
+
...(version ? { version } : {}),
|
|
142
|
+
path: found.path,
|
|
143
|
+
...(found.source ? { source: found.source } : {}),
|
|
144
|
+
searched: found.searched,
|
|
145
|
+
};
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
/**
|
|
@@ -231,13 +272,17 @@ function common(input: LaunchInput): {
|
|
|
231
272
|
}
|
|
232
273
|
|
|
233
274
|
function binFor(input: LaunchInput): string {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
275
|
+
if (input.bin) return input.bin;
|
|
276
|
+
const d = detect(input.env);
|
|
277
|
+
if (!d.available || !d.path)
|
|
278
|
+
throw new LaunchError(`${DEN_CLAUDE_ID}: ${d.why ?? `${BIN} not found`}`);
|
|
279
|
+
return d.path;
|
|
237
280
|
}
|
|
238
281
|
|
|
239
282
|
export const denClaude: Runtime = {
|
|
240
283
|
id: DEN_CLAUDE_ID,
|
|
284
|
+
label: "Claude Code",
|
|
285
|
+
find: DEN_CLAUDE_FIND,
|
|
241
286
|
detect,
|
|
242
287
|
launch(input): LaunchOutput {
|
|
243
288
|
const bin = binFor(input);
|