@monster-orange/den 0.4.2 → 0.4.4

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 (64) hide show
  1. package/CHANGELOG.md +273 -0
  2. package/README.md +9 -9
  3. package/docs/distributions.md +313 -50
  4. package/package.json +1 -1
  5. package/src/shared/agents/catalog-runtime.ts +178 -0
  6. package/src/shared/agents/catalog.ts +343 -0
  7. package/src/shared/agents/den-claude.ts +53 -12
  8. package/src/shared/agents/generic.ts +1 -0
  9. package/src/shared/agents/runtime-search.ts +543 -0
  10. package/src/shared/agents/runtime.ts +43 -1
  11. package/src/shared/agents/runtimes.ts +53 -4
  12. package/src/shared/auth/sudo-vault.ts +16 -0
  13. package/src/shared/den-config.ts +92 -2
  14. package/src/shared/edge/render.ts +31 -0
  15. package/src/shared/edge/status.ts +134 -0
  16. package/src/shared/environment-manager/health.ts +82 -8
  17. package/src/shared/environment-manager/workspaces.ts +54 -0
  18. package/src/shared/health/not-checked.ts +42 -0
  19. package/src/shared/health/rollup.ts +4 -0
  20. package/src/shared/process/spawn-streaming.ts +8 -3
  21. package/src/shared/sessions/client.ts +5 -1
  22. package/src/shared/sessions/host.ts +29 -1
  23. package/src/shared/sessions/protocol.ts +6 -1
  24. package/src/shared/sessions/types.ts +6 -0
  25. package/src/shared/stacks/status.ts +24 -0
  26. package/src/shared/strategies/registry.ts +5 -0
  27. package/src/shared/types/index.ts +6 -0
  28. package/src/ui/cli/agent-ops.ts +298 -19
  29. package/src/ui/cli/agents-command.ts +129 -4
  30. package/src/ui/cli/edge-command.ts +16 -3
  31. package/src/ui/cli/health-display.ts +4 -1
  32. package/src/ui/cli/sessions-command.ts +9 -4
  33. package/src/ui/cli/web-command.ts +53 -8
  34. package/src/ui/mcp/scope.ts +23 -3
  35. package/src/ui/mcp/server.ts +31 -7
  36. package/src/ui/mcp/tools.ts +327 -20
  37. package/src/ui/mcp/views.ts +450 -0
  38. package/src/ui/server/agent-routes.ts +123 -26
  39. package/src/ui/server/edge-exposure.ts +143 -0
  40. package/src/ui/server/index.ts +171 -25
  41. package/src/ui/server/public/agent-start.js +241 -0
  42. package/src/ui/server/public/agents-page.js +246 -45
  43. package/src/ui/server/public/agents-section.js +7 -21
  44. package/src/ui/server/public/agents.html +55 -1
  45. package/src/ui/server/public/graph-model.js +2 -0
  46. package/src/ui/server/public/header.js +2 -3
  47. package/src/ui/server/public/health-rollup.js +20 -8
  48. package/src/ui/server/public/i18n/es.js +26 -13
  49. package/src/ui/server/public/i18n/ja.js +25 -13
  50. package/src/ui/server/public/index.html +17 -4
  51. package/src/ui/server/public/local-dev.html +62 -22
  52. package/src/ui/server/public/services.html +21 -5
  53. package/src/ui/server/public/settings-page.js +3 -2
  54. package/src/ui/server/public/settings-registry.js +226 -1
  55. package/src/ui/server/public/settings.html +90 -1
  56. package/src/ui/server/public/shell.js +67 -22
  57. package/src/ui/server/public/topbar.js +217 -158
  58. package/src/ui/server/readonly-routes.ts +29 -5
  59. package/src/ui/server/request-guard.ts +123 -8
  60. package/src/ui/server/route-classes.ts +14 -2
  61. package/src/ui/server/session-routes.ts +9 -1
  62. package/src/ui/server/sudo-store.ts +170 -6
  63. package/src/ui/server/ui-assets.ts +2 -0
  64. package/src/ui/server/workspace-routes.ts +10 -3
@@ -352,7 +352,7 @@ where it is, under `secrets.refs`:
352
352
 
353
353
  | Ref | Feature |
354
354
  | -------------------- | ------------------------------------------------------------ |
355
- | `sudo` | The saved local-dev sudo password (auto-unlock, "Save to …") |
355
+ | `sudo` | The saved admin password, for sudo (auto-load, "Save to …") |
356
356
  | `journeys_agent_key` | The journeys agent's API key, when `CURSOR_API_KEY` is unset |
357
357
 
358
358
  ```yaml
@@ -377,12 +377,38 @@ secrets:
377
377
  and `env` need a `key` (or an env-shaped `field`). There are no defaults:
378
378
  a ref that is not set is **not configured**, and its feature says so
379
379
  ("not configured: set secrets.provider and secrets.refs.sudo in
380
- config/acme.yml") instead of running a CLI. The interactive unlock always
380
+ config/acme.yml") instead of running a CLI. Typing the password always
381
381
  works; only the saved password needs `refs.sudo`.
382
382
  `/api/sudo-password/vault-status` reports `backend`, `label`,
383
383
  `configured`, `backendAvailable`, `reason` and `hasStored` (`opAvailable`
384
384
  is a deprecated alias of `backendAvailable`, kept for one minor).
385
385
 
386
+ The admin password is for sudo, and nothing else (LK-0). It is no sign-in
387
+ and no access check: starting, stopping and resuming agents, sessions and
388
+ terminals, and a service whose commands run no sudo, never ask for it.
389
+ Den asks only when a command it runs is about to run sudo without it:
390
+ every service command (up, down, restart, setup, sync) gets a `sudo` shim
391
+ first on its `PATH`. With a password held, the shim gives it to sudo. With
392
+ none, sudo that needs no password (NOPASSWD, a warm cache) runs as it
393
+ would, and anything else never prompts: the shim prints
394
+ `<cli>: needs-sudo: sudo <command>` and fails, and the run's event stream
395
+ carries `{ "type": "needs-sudo", "command": "sudo <command>", "step":
396
+ "<service>" }` (sudo's own "a password is required" counts too, without
397
+ the command). Only then does the page ask, saying "`sudo <command>` needs
398
+ your admin password", with the 🔑 chip in the top bar while it waits. It
399
+ asks the server first (another tab or device may have set it), then, when
400
+ a password is stored, loads it from the vault (a page view alone never
401
+ wakes the vault), and only then shows the password field, always on top,
402
+ even over a dialog. After the password, the action runs again.
403
+
404
+ Settings › Command line › Admin password says whether it is saved ("Saved
405
+ in 1Password", "Saved for this session" or "Not saved"), with **Forget**:
406
+ `POST /api/sudo-password/forget` drops the password the server holds and,
407
+ when this server saved it to the vault or loaded it from there, clears the
408
+ vault's copy. `GET /api/sudo-password/status` answers `set`,
409
+ `passwordless`, `saved` (`vault`, `session` or null) and `vault` (`{
410
+ configured, label }`, read from the config alone).
411
+
386
412
  A `1password` distribution with no refs gets one warning, in the web
387
413
  server's startup log and in `<cli> validate`, with the YAML to add.
388
414
 
@@ -1182,10 +1208,12 @@ are the same directory.
1182
1208
  ## Signing in
1183
1209
 
1184
1210
  On localhost nothing asks you to sign in: every page, the agents and the
1185
- terminal work as they are, and the lock (R8) is the only prompt (for
1186
- privileged actions). Signing in is for when the server is reached beyond
1187
- this machine: through the edge hostname, a LAN address, or a bind that
1188
- isn't loopback (`DEN_BIND_HOST`).
1211
+ terminal work as they are, and the admin password (R8) is the only prompt,
1212
+ asked only when a command Den runs needs sudo. The edge counts as local too (0.4.3): opening
1213
+ `https://acme.acme.localhost` from this machine asks for nothing.
1214
+ Signing in is for when the server is reached from another machine: the
1215
+ edge for a client the edge says is elsewhere, a LAN address, or a bind
1216
+ that isn't loopback (`DEN_BIND_HOST`).
1189
1217
 
1190
1218
  There is no password. Reading `web.secret` (32 random bytes, mode 0600 in
1191
1219
  the state dir, made at the first `web up`) proves you are this computer's
@@ -1205,7 +1233,11 @@ user, and the CLI does that for you:
1205
1233
  within 60 seconds, and only this server's secret makes one. It is printed
1206
1234
  only on the one line you asked for (`web login`, or `web up` when it
1207
1235
  couldn't open the browser), never logged. Through the edge or a LAN name,
1208
- the cookie belongs to that name: `web login --origin https://acme.acme.localhost`
1236
+ the cookie belongs to that name: `web login --host acme.acme.localhost`
1237
+ mints the link for an edge hostname (any edge domain or alias, a VPN name
1238
+ among them; its URL comes from the edge config, and a name the edge
1239
+ doesn't route here is refused, saying what to add), and
1240
+ `web login --origin http://<lan name>:<port>` for any other allowed name
1209
1241
  (the signed-out page there shows the exact command).
1210
1242
  - **The cookie** (`den_session_<port>`) is HttpOnly, SameSite=Strict and
1211
1243
  Path=/, and Secure when the request came over HTTPS through the edge. The
@@ -1222,10 +1254,38 @@ user, and the CLI does that for you:
1222
1254
  open, and changes (an agent's start or resume and the terminal included)
1223
1255
  need only the page's own Origin and CSRF token, which the shell sends.
1224
1256
  The Host allowlist, the Origin check and the CSRF token are what stop
1225
- another site or a rebinding DNS name. Through the edge, or from another
1226
- machine, every page and call needs a signed-in browser (or a bearer). A
1227
- page there shows "Sign in to use <name>" with the command to run; it
1228
- never asks for a secret. `/api` answers 401 `signin_required`.
1257
+ another site or a rebinding DNS name. From another machine, every page
1258
+ and call needs a signed-in browser (or a bearer). A page there shows
1259
+ "Sign in to use <name>" with the command to run; it never asks for a
1260
+ secret. `/api` answers 401 `signin_required`.
1261
+ - **The edge counts as local.** A request for the edge hostname from the
1262
+ edge (an edge peer, below) is treated as loopback: no sign-in on any
1263
+ route, the agents and the terminal included. Host, Origin, CSRF and the
1264
+ agent-token rules still apply. Who the client is comes from the left-most
1265
+ `X-Forwarded-For` entry the edge sends:
1266
+ - loopback, or one of this machine's own interface addresses (a LAN
1267
+ address, or a VPN's such as a Tailscale `100.x`): local;
1268
+ - missing, or a container/VM network's address (the Docker ranges, or a
1269
+ subnet of one of this machine's Docker/OrbStack/VM bridges): Den can't
1270
+ tell (Docker Desktop and OrbStack on macOS hand the edge their
1271
+ gateway's address, and a TCP passthrough in front of the edge hides the
1272
+ client), so local;
1273
+ - any other address: another machine, which needs a sign-in.
1274
+
1275
+ With `DEN_BIND_HOST` beyond loopback (a deliberate exposure, other than
1276
+ the Linux edge's Docker bridge address) the edge needs a sign-in as
1277
+ before. With `forwardedHeaders.insecure` (the edge's default) Traefik
1278
+ keeps an `X-Forwarded-For` the client sent, so this is a convenience,
1279
+ not a boundary: whoever can reach the edge can use the terminal.
1280
+
1281
+ - **"Your edge is reachable from other machines".** When Den can tell, at
1282
+ start, that the edge is reachable beyond this machine (an edge hostname
1283
+ that resolves to an address that isn't loopback, such as a LAN or
1284
+ Tailscale address, or the edge container publishing a port on every
1285
+ interface, from `docker inspect`), it writes "Your edge is reachable from
1286
+ other machines; anyone who can reach it can use terminals. Auth is
1287
+ coming." once to `web.log`, and every page shows it as a banner (dismissed
1288
+ per browser).
1229
1289
  - **localhost is the canonical name.** A cookie set on `localhost` isn't
1230
1290
  sent to `127.0.0.1`, so the links Den prints use `localhost`.
1231
1291
  - **Browsers.** Chromium, Firefox and Safari 16.4 or later: a sign-in link
@@ -1406,19 +1466,40 @@ port.
1406
1466
  `--workspace <ws>` and `--actor agent:<id>` (both or neither) scope the server
1407
1467
  to one workspace for one agent:
1408
1468
 
1409
- - **Pinned.** A tool that takes a `workspace` gets `<ws>` when none is given.
1410
- A call naming another workspace is refused (`Refused: <tool>: this MCP
1411
- server is scoped to workspace "<ws>" (agent:<id>; no grants); "<other>" is
1412
- another workspace`; with grants, `grants: <a>, <b>`) before anything reaches the web server. A refused call is
1413
- recorded in the activity trail, read tools included.
1469
+ - **Pinned.** A tool that takes a `workspace` gets `<ws>`. The server
1470
+ registers each workspace tool with "This workspace only" in its
1471
+ description and without the `workspace` parameter (with grants it stays,
1472
+ a name whose description lists `<ws>` and the granted ones; any other
1473
+ name gets the refusal below, not a schema error). A call naming another
1474
+ workspace is refused
1475
+ (`Refused: <tool>: this MCP server is scoped to workspace "<ws>"
1476
+ (agent:<id>; no grants); "<other>" is another workspace`; with grants,
1477
+ `grants: <a>, <b>`) before anything reaches the web server. The refusal
1478
+ says how to get more: a person adds `<other>` to `agents.<id>.grants` in
1479
+ the org config and restarts the agent; an agent can't grant itself one. A
1480
+ refused call is recorded in the activity trail, read tools included.
1481
+ - **Says so.** Every scoped answer (`status`, `services`, `applications`,
1482
+ `graph`, `stacks`, `workspaces`) carries a `scope` block: the workspace
1483
+ shown, how many other workspaces there are and how much it leaves out,
1484
+ for example `hidden: { repos: 6 }`, and a note pointing at `org_summary`.
1485
+ A scoped `graph` is the workspace's own repos and the edges out of them
1486
+ (`map: "workspace"`, with `outside` naming the repos they depend on
1487
+ elsewhere). Unscoped with no `workspace` it is the org-wide map
1488
+ (`map: "org-wide"`: every active workspace's graph merged, `workspaces`
1489
+ naming them); with one, that workspace's (`map: "workspace"`).
1414
1490
  - **Filtered.** `workspaces` lists `<ws>` only. `logs_search` and `logs_tail`
1415
1491
  take only `<ws>`'s services, `manifest_validate` takes only an absolute path
1416
1492
  inside one of its repos (or the content), and `workspace_sync` syncs `<ws>`.
1417
1493
  Tests run against the focused workspace, so `tests_available` and
1418
1494
  `run_tests` are refused unless `<ws>` is the focused one.
1419
1495
  - **Not workspace data**, served as unscoped: `environments`,
1420
- `security_latest`, `release_status` and `edge_status` (the org's and the
1421
- distribution's, not a workspace's).
1496
+ `security_latest`, `release_status`, `edge_status` and `org_summary` (the
1497
+ org's and the distribution's, not a workspace's).
1498
+ - **The org at a glance.** `org_summary` gives every agent every workspace
1499
+ and repo with status counts, the graph, the edge and the environments
1500
+ (see [Tools](#tools)). Detail and actions stay scoped: for workspaces
1501
+ other than `<ws>` it carries names and counts only, never logs, env
1502
+ values, commands, URLs, paths or check output.
1422
1503
  - **Stamped.** Every web API call carries `x-den-mcp-agent: agent:<id>` and
1423
1504
  `x-den-mcp-workspace: <ws>` beside the usual audit headers. The activity
1424
1505
  entry gains `agent` and `workspace`, and the log line reads `· by
@@ -1437,7 +1518,9 @@ agent:<id> <client> <version> (workspace <ws>, mcp pid <pid>, session
1437
1518
  - `<ws>` must exist in this distribution; until it does, every tool says so.
1438
1519
  The id is letters, digits, dots, dashes and underscores (at most 64). A bad
1439
1520
  or half-given scope is a usage error, exit 2. `--list` and `--call` take
1440
- the scope too.
1521
+ the scope too: they list and validate against the pinned tools, as the
1522
+ server registers them, and `--call` names its scope on stderr
1523
+ (`acme mcp --call: scoped to <ws> as agent:<id>`).
1441
1524
 
1442
1525
  **It binds cooperating agents only.** The scope lives in the MCP server's
1443
1526
  process. The web server's HTTP API does not check it yet, so an agent that
@@ -1483,22 +1566,60 @@ guard off.
1483
1566
 
1484
1567
  Read tools (always registered):
1485
1568
 
1486
- | Tool | Web API | What |
1487
- | ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1488
- | `status` | `/api/health` | Service health; `url` (HTTP check) or `check` (shell, or `container <project>/<service>`); `ok` true up, false down, null not known (`pending: true` when a check has not answered) |
1489
- | `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands |
1490
- | `applications` | `/api/applications` | Org > Application > Repo > Services |
1491
- | `graph` | `/api/graph` | The derived dependency graph and startup order |
1492
- | `workspaces` | `/api/workspaces` | Workspaces, active and focused |
1493
- | `environments` | `/api/environments` | The enabled environment names (never their values) |
1494
- | `logs_search` | `/api/logs/search` | Search services' full log history |
1495
- | `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow) |
1496
- | `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from |
1497
- | `security_latest` | `/api/security/latest` | The newest security scan per mode |
1498
- | `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table |
1499
- | `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content |
1500
- | `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health |
1501
- | `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names |
1569
+ | Tool | Web API | What |
1570
+ | ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1571
+ | `status` | `/api/health` | Service health: `counts`, then each service's `state` (`up`, `down`, `redirect`, `pending`, `unknown`, `not-checked`) and, without a plain verdict, `why`; `url` (HTTP check) or `check` (shell, or `container <project>/<service>`); `ok` true up, false down, null not known |
1572
+ | `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands |
1573
+ | `applications` | `/api/applications` | Org > Application > Repo > Services |
1574
+ | `graph` | `/api/graph` | The derived dependency graph (`map: "org-wide"`, every active workspace's; with `workspace`, that one's and its startup order; scoped, the workspace's own) |
1575
+ | `workspaces` | `/api/workspaces` | Workspaces, active and focused |
1576
+ | `environments` | `/api/environments` | The enabled environment names (never their values) |
1577
+ | `logs_search` | `/api/logs/search` | Search services' full log history |
1578
+ | `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow) |
1579
+ | `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from |
1580
+ | `security_latest` | `/api/security/latest` | The newest security scan per mode |
1581
+ | `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table |
1582
+ | `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content |
1583
+ | `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health; an empty list says `why` |
1584
+ | `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names; `https` (TLS strategy, who supplies the cert, whether HTTPS is serving) |
1585
+ | `org_summary` | several (read only) | Every workspace and repo with status counts, the graph, the edge and the environments; scoped, `yours` adds your workspace's services |
1586
+
1587
+ **Health states.** A service with no verdict says why in `why`: no health
1588
+ check in the manifest for the env, remote with no check or `target.url`
1589
+ for its env, not deployed in the env (no commands and no check there), no
1590
+ manifest, or checked but no answer yet. A redirect (`302 → /login`) is
1591
+ `redirect` with its status and target (the query is dropped), not down:
1592
+ something answered. It is never green either, so an unexpected redirect
1593
+ (an ALB answering for a stopped service) is not taken for up; list the code
1594
+ in `target.expect` to count it as up. The web health row and `<cli> status`
1595
+ use the same rule: the web row shows a redirect muted (with `status` and `redirect`
1596
+ in `/api/health`), and `<cli> status` as `redirect 302 → /login`.
1597
+
1598
+ **`org_summary`** reads `/api/workspaces`, `/api/health` (local, every
1599
+ active workspace), `/api/graph`, `/api/edge` and `/api/environments`:
1600
+
1601
+ - `workspaces`: each workspace's name, whether it is active, focused or
1602
+ yours, its state (`running`: every checked service answers; `partial`:
1603
+ some are down; `stopped`: all checked ones are down; `unknown`: nothing
1604
+ checked; `inactive`: not polled), its counts (up, down, redirect,
1605
+ pending, not checked) and its repos, each with its rolled-up state.
1606
+ - `graph`: the org-wide map, every active workspace's repos merged (each
1607
+ workspace's graph from the web server; `workspaces` names them, and
1608
+ `unavailable` any whose graph failed), repo names and edge kinds only (no
1609
+ evidence, paths or URLs).
1610
+ - `edge`: up, state, domains, the route count and `https`.
1611
+ - `environments`: the enabled names, never their values.
1612
+ - scoped, `yours`: your workspace's services as `status` gives them.
1613
+
1614
+ **`edge_status.https`** says which TLS strategy is in use (`mkcert`,
1615
+ `files`, `none`), who supplies the cert (`den`, or `distribution` under
1616
+ `tls: files` and the legacy `issue_cert: false`) and whether HTTPS is
1617
+ serving: a TLS handshake on the edge's published https port (`serving`
1618
+ true or false, null when a proxy in front owns the port), with `detail`.
1619
+ `certIssuedByDen` replaces `certIssued`, which is kept for 0.4.4 with a
1620
+ note in `deprecated`: it said whether Den minted the cert, so it was
1621
+ `false` under `tls: files` while HTTPS worked. `edge status` prints the
1622
+ same `https` line.
1502
1623
 
1503
1624
  Action tools (only those named in `mcp.actions`): `service_up`,
1504
1625
  `service_down`, `service_restart` (through `/api/service/exec`),
@@ -1616,6 +1737,7 @@ server scoped to its workspace.
1616
1737
  <cli> agents stop <id> [-w <ws>]
1617
1738
  <cli> agents resume <id> [-w <ws>]
1618
1739
  <cli> agents personas [--json]
1740
+ <cli> agents doctor [-w <ws>] [--json] # can each runtime run here, and why not
1619
1741
  <cli> sessions attach <ws>:<id> # its terminal; ctrl-] detaches
1620
1742
  ```
1621
1743
 
@@ -1654,13 +1776,21 @@ started. The workspace header counts them ("2 agents").
1654
1776
  not running.
1655
1777
  - **+ Start agent** picks a persona (Den's pack, then
1656
1778
  `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.
1779
+ one; one that can't run here is listed, disabled), then Start. Under the
1780
+ runtime a line says what Start will run, with its version and path
1781
+ ("Starts Claude Code 2.1.0 from ~/.local/bin/claude"). When that runtime
1782
+ can't run here, Start is disabled and the line says why and how to fix
1783
+ it: where it looked, then "Install it, or set
1784
+ `agents.runtimes.<id>.bin` to its path in <your user.yml>, or run
1785
+ `<cli> agents doctor`" (see [Runtimes](#runtimes)). Nothing fails only
1786
+ after Start.
1658
1787
  - The page and the CLI call the same operations and read the same agent
1659
1788
  records, so an agent started in one is listed in the other. A workspace
1660
1789
  lists its own agents only.
1661
- - Start, Stop and Resume need the lock open (🔓, the same rule as a
1662
- service's Start), are capped by `agents.max_concurrent`, and are refused
1663
- for an agent (agents don't start agents from the page). Each one is in the
1790
+ - Start, Stop and Resume never ask for the admin password (it is for
1791
+ sudo, and no agent action runs sudo), are capped by
1792
+ `agents.max_concurrent`, and are refused for an agent (agents don't
1793
+ start agents from the page). Each one is in the
1664
1794
  server log's activity, with you as its actor. The agent's environment is
1665
1795
  the one below, built from the web server's environment.
1666
1796
  - **Open in Agents →** (the section header) opens the Agents tab for the
@@ -1670,7 +1800,7 @@ The routes: `GET /api/workspaces/<ws>/agents`, `POST
1670
1800
  /api/workspaces/<ws>/agents` (`{ persona, runtime?, model?, id? }`), `POST
1671
1801
  /api/workspaces/<ws>/agents/<id>/stop` and `…/resume`, and `GET
1672
1802
  /api/personas` (personas and runtimes). Errors are the usual
1673
- `{ ok: false, error, code, requestId }`: `locked` (423), `cap` (429),
1803
+ `{ ok: false, error, code, requestId }`: `cap` (429),
1674
1804
  `conflict` (409, e.g. already running, or no resume), `not_found` (404),
1675
1805
  `exited_at_once` (502, with the session's last lines).
1676
1806
 
@@ -1693,9 +1823,10 @@ agent of the workspaces in view, and their terminals in the browser.
1693
1823
  reload or Back reopens them.
1694
1824
  - **Close** (×) detaches, as leaving the page does: the session keeps
1695
1825
  running, and opening it again replays what you missed. **Stop** (an
1696
- agent only) is `agents stop`, behind the lock. **+ Start agent** (or
1826
+ agent only) is `agents stop`. **+ Start agent** (or
1697
1827
  `/agents?start=1`) starts one and opens its pane; under All it asks for
1698
- the workspace.
1828
+ the workspace. It says before Start whether the runtime can run, as the
1829
+ Workspaces page's form does. It never asks for the admin password.
1699
1830
  - ⌘K lists each as "Agents › <ws> / <name>", and "Start agent…".
1700
1831
  - Several browsers and `<cli> sessions attach` can attach to one session
1701
1832
  at once and each sees every byte; the terminal's size is the last
@@ -1721,7 +1852,8 @@ An agent's environment starts empty and gets, in order:
1721
1852
 
1722
1853
  1. `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM` (default
1723
1854
  `xterm-256color`), `COLORTERM`, `LANG`, `LC_*`, `TZ` and `TMPDIR` from
1724
- yours;
1855
+ yours (`PATH` with the login shell's and the runtimes' install
1856
+ directories added: [Runtimes](#runtimes));
1725
1857
  2. the variables its runtime names (`envPassthrough`; den-claude:
1726
1858
  `CLAUDE_CONFIG_DIR`);
1727
1859
  3. the runtime's launch env;
@@ -1759,8 +1891,9 @@ header (agent headers without the token are recorded as an unverified
1759
1891
  claim).
1760
1892
 
1761
1893
  An agent's token reaches only its own session's routes, and never a
1762
- shell-grade route, `/api/web/*`, sign-in or sign-out, or the lock: it can't
1763
- unlock, use the sudo password or start another agent (403
1894
+ shell-grade route, `/api/web/*`, sign-in or sign-out, or the admin
1895
+ password's routes: it can't set the admin password, use it for sudo or
1896
+ start another agent (403
1764
1897
  `agent_forbidden`). A person's credential never reaches a session:
1765
1898
  `DEN_PERSON_TOKEN`, a person token's value and a path to `web.secret` are
1766
1899
  always removed, and `sessions start --keep-env` of one is refused and logged.
@@ -1781,7 +1914,8 @@ it does not catch every spelling: a path relative to the agent's cwd
1781
1914
  (`cat ../.den/web.secret`, say), another program, a script or a copy under
1782
1915
  another name all get past it. The server's token rules (above)
1783
1916
  are the boundary: whatever an agent reads, its token reaches only its own
1784
- session's routes, never a shell-grade route or the lock. OS-user isolation
1917
+ session's routes, never a shell-grade route or the admin password's.
1918
+ OS-user isolation
1785
1919
  (a separate user, a container, a sandbox) is not in 0.4.x. `web logout
1786
1920
  --all` revokes every agent's token (above, under Signing in).
1787
1921
 
@@ -1830,10 +1964,12 @@ are errors naming the file or the key.
1830
1964
 
1831
1965
  ### Runtimes
1832
1966
 
1833
- `<cli> doctor` lists the runtime kind. Two are built in:
1967
+ `<cli> doctor` lists the runtime kind. Den knows the runtimes in its
1968
+ **catalog** (below); two are special:
1834
1969
 
1835
- - **`den-claude`** (the default) needs `claude` on PATH; doctor reads its
1836
- version from the install, without running it. It runs `claude --session-id
1970
+ - **`den-claude`** (the default) runs Claude Code's `claude`, found as
1971
+ below; `<cli> doctor` reads its version from the install, without running
1972
+ it. It runs `claude --session-id
1837
1973
  <id> --mcp-config <file> --strict-mcp-config --settings <file> [--model <m>]
1838
1974
  --append-system-prompt <role prompt>`, so Den's MCP server is the agent's
1839
1975
  only one, and resumes with `claude --resume <id>`. Permission prompts stay
@@ -1844,7 +1980,134 @@ are errors naming the file or the key.
1844
1980
  - **`generic`** runs the persona's `argv` template. `{model}` is the agent's
1845
1981
  model (an error when the template uses it and there is none);
1846
1982
  `{mcp_config}` is the path of the agent's MCP config, written only when the
1847
- template references it. It declares no resume.
1983
+ template references it. It declares no resume. It is startable only for
1984
+ a persona that has an `argv`.
1985
+
1986
+ **The runtime catalog.** Den ships, as data, every vendor CLI it knows how
1987
+ to find. Each entry was checked against the vendor's own docs or installer:
1988
+
1989
+ | id | Vendor | CLI | Found in (after PATH) | Source |
1990
+ | ------------ | --------- | --------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
1991
+ | `den-claude` | Anthropic | Claude Code, `claude` | `~/.local/bin`, `~/.claude/local`, system dirs | [setup](https://code.claude.com/docs/en/setup) |
1992
+ | `codex` | OpenAI | Codex CLI, `codex` | `$CODEX_INSTALL_DIR`, `~/.local/bin`, system dirs | [openai/codex](https://github.com/openai/codex), [installer](https://chatgpt.com/codex/install.sh) |
1993
+ | `gemini` | Google | Gemini CLI, `gemini` | system dirs | [google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli) |
1994
+ | `grok` | xAI | Grok Build, `grok` | `$GROK_BIN_DIR`, `~/.grok/bin`, `~/.local/bin`, `/usr/local/bin` | [Grok Build](https://docs.x.ai/build/overview), [installer](https://x.ai/cli/install.sh) |
1995
+
1996
+ "System dirs" are `/opt/homebrew/bin`, `/usr/local/bin` and npm's global
1997
+ bin. Each one's version is `<bin> --version`. Every vendor here publishes
1998
+ an official CLI, so none is left out; a vendor without one is left out
1999
+ rather than guessed at (community CLIs, such as third-party "grok" CLIs,
2000
+ are not xAI's and are not in the catalog).
2001
+
2002
+ Claude Code runs as `den-claude` (above). Every other installed runtime
2003
+ runs its own interactive CLI (`codex`, `gemini`, `grok`) in the
2004
+ workspace's directory; Den's tools (MCP) and permission modes are coming
2005
+ for them, and lists tag them **Den tools: coming**. A runtime is offered
2006
+ as startable only when it can start here: installed (or, for `generic`,
2007
+ with the persona's `argv`).
2008
+
2009
+ A distribution adds entries with `agents.runtime_catalog` in its config,
2010
+ and a person with the same key in `config/local/user.yml`. Both go
2011
+ through the validator the built-in entries pass; an entry can't reuse a
2012
+ catalog id (set a path instead, below):
2013
+
2014
+ ```yaml
2015
+ agents:
2016
+ runtime_catalog:
2017
+ - id: aider # lowercase, starts with a letter
2018
+ vendor: Aider
2019
+ name: Aider
2020
+ bins: [aider] # executable names, no paths
2021
+ version:
2022
+ args: [--version]
2023
+ identity: # at least one: how a path is known to be it
2024
+ output: "aider" # a pattern its version output matches
2025
+ # package: aider-chat (the npm package it resolves into)
2026
+ # realpath_under: [~/.aider] (where it really lives)
2027
+ dirs: [~/.local/bin, $AIDER_DIR, "{system}"]
2028
+ launch:
2029
+ argv: ["{bin}"] # "{bin}" first: the path found
2030
+ ```
2031
+
2032
+ **Where a runtime's CLI is found.** As your terminal would find it, not
2033
+ only on the web server's own PATH (a launchd service's PATH leaves out
2034
+ `~/.local/bin`, where Claude Code's native installer puts `claude`). The
2035
+ first match wins:
2036
+
2037
+ 1. **A workspace's own path**, `<ws>.agents.runtimes.<id>.bin` in
2038
+ `config/local/workspaces.yml`, for that workspace's agents. Checked the
2039
+ same way as the next one.
2040
+ 2. **`agents.runtimes.<id>.bin`** in `config/local/user.yml`: the absolute
2041
+ path of the runtime's CLI. Paths are per machine, so it is read from
2042
+ user.yml only, never from the distribution's config. A bad value (not
2043
+ absolute, missing, not an executable file) makes the runtime
2044
+ unavailable with that reason; Den doesn't fall back to a search.
2045
+
2046
+ ```yaml
2047
+ # config/local/user.yml (gitignored)
2048
+ agents:
2049
+ runtimes:
2050
+ den-claude:
2051
+ bin: /Users/me/.local/bin/claude
2052
+ ```
2053
+
2054
+ 3. **Your login shell's PATH**: the web server asks `$SHELL -lc` once at
2055
+ start, in the background, under a 3-second timeout. On a timeout or
2056
+ any failure it is left out.
2057
+ 4. **The server's own PATH.**
2058
+ 5. **The runtime's install directories** (its catalog entry's; for
2059
+ den-claude: `~/.local/bin`, `~/.claude/local`, `/opt/homebrew/bin`,
2060
+ `/usr/local/bin` and npm's global bin, `$npm_config_prefix/bin` and
2061
+ node's own directory). Den never runs npm to find it.
2062
+ 6. **Your interactive shell's PATH**, only while a catalog runtime is
2063
+ still not found: `$SHELL -ilc` once (stdin closed, 3-second timeout,
2064
+ the answer kept), for a PATH set only in an interactive startup file
2065
+ such as `~/.zshrc`. Its source is `interactive-shell`.
2066
+
2067
+ Paths are this machine's only: Den reads them from `config/local` files,
2068
+ never from the distribution's config. **Settings › Agents › Runtimes**
2069
+ shows each runtime's status, version, the path in use and its source.
2070
+ **Set path…** runs the runtime's version command and refuses a path that
2071
+ isn't that runtime (its output, npm package or install location must name
2072
+ it); **Clear** removes it. The **Paths for** selector switches between
2073
+ every workspace (`user.yml`) and one workspace (`workspaces.yml`). Only a
2074
+ person sets a path, never an agent (`POST /api/agents/runtimes/<id>/path`,
2075
+ audited).
2076
+
2077
+ Sessions and agents Den starts get the same directories on their PATH
2078
+ (and a configured CLI's own directory), so the runtime's own tools are
2079
+ found too. Two engine variables override the search for tests and
2080
+ diagnosis: `DEN_LOGIN_SHELL` (the shell to ask instead of `$SHELL`; `off`
2081
+ for none) and `DEN_RUNTIME_DIRS` (a PATH-style list that replaces every
2082
+ runtime's install directories; empty for none).
2083
+
2084
+ **`<cli> agents doctor`** prints, for each runtime (installed first), its
2085
+ status, the CLI it runs and where that came from (the workspace's or the
2086
+ user.yml `agents.runtimes` setting, the login shell's PATH, PATH, an
2087
+ install directory, or the interactive shell's PATH), its version (`claude
2088
+ --version`, run once under a timeout), the directories it searched (names
2089
+ only, never an environment value), and the fix when it's missing. It
2090
+ resolves the login shell's PATH first, as the web server does. It exits 1
2091
+ when the default runtime is unavailable; `-w <ws>` uses that workspace's
2092
+ paths; `--json` prints what the Start dialog gets. A search list the same
2093
+ as the one above it prints once.
2094
+
2095
+ ```
2096
+ den-claude (Claude Code, Anthropic) [default]
2097
+ status ✗ unavailable: claude not found (searched 5 directories)
2098
+ searched /usr/bin
2099
+ /bin
2100
+ …
2101
+ fix install Claude Code, or set agents.runtimes.den-claude.bin to its absolute path in /…/config/local/user.yml
2102
+ then run `<cli> agents doctor` again
2103
+ ```
2104
+
2105
+ `GET /api/personas` and `GET /api/agents/runtimes` (each with an optional
2106
+ `?workspace=<ws>`) give each runtime its `vendor`, `denTools`, `version`,
2107
+ `path`, `source` (`workspace`, `setting`, `login-shell`, `PATH`,
2108
+ `well-known` or `interactive-shell`), `searched`, `setting` (the user.yml
2109
+ key) and `workspaceSetting` (the workspaces.yml key), and `userConfig`,
2110
+ the user.yml to edit.
1848
2111
 
1849
2112
  ### Workspace scope and grants
1850
2113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monster-orange/den",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
5
5
  "type": "module",
6
6
  "bin": {