@monster-orange/den 0.4.3 → 0.4.5
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 +348 -0
- package/README.md +12 -11
- package/docs/distributions.md +438 -91
- package/docs/reference.md +8 -3
- package/package.json +1 -1
- package/src/shared/agents/catalog-runtime.ts +178 -0
- package/src/shared/agents/catalog.ts +343 -0
- package/src/shared/agents/den-claude.ts +14 -18
- package/src/shared/agents/runtime-search.ts +142 -15
- package/src/shared/agents/runtime.ts +18 -1
- package/src/shared/agents/runtimes.ts +23 -1
- package/src/shared/auth/sudo-vault.ts +131 -11
- package/src/shared/cli-command.ts +186 -0
- package/src/shared/confirm-code.ts +34 -0
- package/src/shared/den-config.ts +64 -3
- package/src/shared/dev-metrics/emit.ts +31 -1
- package/src/shared/edge/config.ts +39 -0
- package/src/shared/edge/render.ts +92 -18
- package/src/shared/edge/runtime.ts +57 -1
- package/src/shared/edge/status.ts +134 -0
- package/src/shared/edge/types.ts +11 -1
- package/src/shared/environment-manager/health.ts +82 -8
- package/src/shared/environment-manager/workspaces.ts +54 -0
- package/src/shared/github-app.ts +94 -0
- package/src/shared/github-device.ts +286 -0
- package/src/shared/health/not-checked.ts +42 -0
- package/src/shared/health/rollup.ts +4 -0
- package/src/shared/process/spawn-streaming.ts +8 -3
- package/src/shared/secrets/onepassword.ts +27 -4
- package/src/shared/sessions/client.ts +5 -1
- package/src/shared/sessions/host.ts +29 -1
- package/src/shared/sessions/protocol.ts +6 -1
- package/src/shared/sessions/types.ts +6 -0
- package/src/shared/stacks/status.ts +24 -0
- package/src/shared/strategies/registry.ts +5 -0
- package/src/shared/types/index.ts +6 -0
- package/src/shared/web-owner.ts +91 -0
- package/src/shared/web-session.ts +135 -1
- package/src/ui/cli/agent-ops.ts +210 -19
- package/src/ui/cli/agents-command.ts +50 -21
- package/src/ui/cli/edge-command.ts +16 -3
- package/src/ui/cli/flag-parser.ts +1 -1
- package/src/ui/cli/health-display.ts +4 -1
- package/src/ui/cli/web-command.ts +512 -33
- package/src/ui/mcp/scope.ts +23 -3
- package/src/ui/mcp/server.ts +31 -7
- package/src/ui/mcp/tools.ts +327 -20
- package/src/ui/mcp/views.ts +450 -0
- package/src/ui/server/agent-routes.ts +118 -28
- package/src/ui/server/device-flows.ts +274 -0
- package/src/ui/server/edge-exposure.ts +16 -4
- package/src/ui/server/github-routes.ts +321 -0
- package/src/ui/server/http-errors.ts +10 -0
- package/src/ui/server/index.ts +540 -33
- package/src/ui/server/login-waits.ts +203 -0
- package/src/ui/server/pair-requests.ts +237 -0
- package/src/ui/server/pair-routes.ts +196 -0
- package/src/ui/server/public/agent-start.js +46 -7
- package/src/ui/server/public/agents-page.js +232 -97
- package/src/ui/server/public/agents-section.js +3 -5
- package/src/ui/server/public/agents.html +12 -28
- package/src/ui/server/public/error-page.js +452 -40
- package/src/ui/server/public/error.html +59 -0
- package/src/ui/server/public/graph-model.js +2 -0
- package/src/ui/server/public/header.js +2 -3
- package/src/ui/server/public/health-rollup.js +20 -8
- package/src/ui/server/public/i18n/es.js +89 -21
- package/src/ui/server/public/i18n/ja.js +88 -21
- package/src/ui/server/public/index.html +17 -4
- package/src/ui/server/public/local-dev.html +44 -21
- package/src/ui/server/public/qr.js +317 -0
- package/src/ui/server/public/services.html +21 -5
- package/src/ui/server/public/settings-page.js +10 -2
- package/src/ui/server/public/settings-registry.js +617 -1
- package/src/ui/server/public/settings.html +90 -1
- package/src/ui/server/public/shell.js +151 -33
- package/src/ui/server/public/topbar.js +170 -206
- package/src/ui/server/public/workspace-picker.js +1 -1
- package/src/ui/server/readonly-routes.ts +29 -5
- package/src/ui/server/request-guard.ts +103 -36
- package/src/ui/server/route-classes.ts +116 -4
- package/src/ui/server/session-routes.ts +9 -1
- package/src/ui/server/sudo-store.ts +170 -6
- package/src/ui/server/ui-assets.ts +2 -0
- package/src/ui/server/workspace-routes.ts +10 -3
package/docs/distributions.md
CHANGED
|
@@ -12,6 +12,7 @@ Den's own checkout is itself a distribution, named `den`.
|
|
|
12
12
|
- [The bin shim](#the-bin-shim)
|
|
13
13
|
- [make: den.mk](#make-denmk)
|
|
14
14
|
- [Two roots](#two-roots)
|
|
15
|
+
- [Dev metrics: local unless you opt in](#dev-metrics-local-unless-you-opt-in)
|
|
15
16
|
- [Pinning and upgrading](#pinning-and-upgrading)
|
|
16
17
|
- [Developing Den against a distribution](#developing-den-against-a-distribution)
|
|
17
18
|
|
|
@@ -352,7 +353,7 @@ where it is, under `secrets.refs`:
|
|
|
352
353
|
|
|
353
354
|
| Ref | Feature |
|
|
354
355
|
| -------------------- | ------------------------------------------------------------ |
|
|
355
|
-
| `sudo` | The saved
|
|
356
|
+
| `sudo` | The saved admin password, for sudo (auto-load, "Save to …") |
|
|
356
357
|
| `journeys_agent_key` | The journeys agent's API key, when `CURSOR_API_KEY` is unset |
|
|
357
358
|
|
|
358
359
|
```yaml
|
|
@@ -377,19 +378,51 @@ secrets:
|
|
|
377
378
|
and `env` need a `key` (or an env-shaped `field`). There are no defaults:
|
|
378
379
|
a ref that is not set is **not configured**, and its feature says so
|
|
379
380
|
("not configured: set secrets.provider and secrets.refs.sudo in
|
|
380
|
-
config/acme.yml") instead of running a CLI.
|
|
381
|
+
config/acme.yml") instead of running a CLI. Typing the password always
|
|
381
382
|
works; only the saved password needs `refs.sudo`.
|
|
382
|
-
`/api/sudo-password/vault-status` reports `backend`, `label`,
|
|
383
|
-
`configured`, `backendAvailable`, `reason` and `hasStored` (`opAvailable`
|
|
384
|
-
is a deprecated alias of `backendAvailable`, kept for one minor).
|
|
385
383
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
384
|
+
**Saving writes `refs.sudo` for you** (LK-4, 0.4.5). When `secrets.provider`
|
|
385
|
+
is `1password` or `doppler`, its CLI is available, and no `refs.sudo` is
|
|
386
|
+
set, the admin password prompt (shown when a command is about to run sudo)
|
|
387
|
+
still offers "Save to 1Password". Saving creates the item
|
|
388
|
+
(1Password: a Password item "<Product> admin password", field `password`,
|
|
389
|
+
in your default vault; Doppler: the key `<PREFIX>_LOCAL_ADMIN_PWD`) and
|
|
390
|
+
then writes that ref to `config/local/user.yml`, because the password is
|
|
391
|
+
the person's, not the distribution's. From then on it survives restarts.
|
|
392
|
+
A failed save writes nothing, and its error never carries the password.
|
|
393
|
+
`env` keeps nothing past the process, so it offers no save. A `refs.sudo`
|
|
394
|
+
in the distribution's config always wins over the one in user.yml.
|
|
395
|
+
|
|
396
|
+
`/api/sudo-password/vault-status` reports `backend`, `label`,
|
|
397
|
+
`configured`, `canSave` (saving works with no YAML to edit),
|
|
398
|
+
`backendAvailable`, `reason` and `hasStored` (`opAvailable` is a
|
|
399
|
+
deprecated alias of `backendAvailable`, kept for one minor).
|
|
400
|
+
|
|
401
|
+
The admin password is for sudo, and nothing else (LK-0). It is no sign-in
|
|
402
|
+
and no access check: starting, stopping and resuming agents, sessions and
|
|
403
|
+
terminals, and a service whose commands run no sudo, never ask for it.
|
|
404
|
+
Den asks only when a command it runs is about to run sudo without it:
|
|
405
|
+
every service command (up, down, restart, setup, sync) gets a `sudo` shim
|
|
406
|
+
first on its `PATH`. With a password held, the shim gives it to sudo. With
|
|
407
|
+
none, sudo that needs no password (NOPASSWD, a warm cache) runs as it
|
|
408
|
+
would, and anything else never prompts: the shim prints
|
|
409
|
+
`<cli>: needs-sudo: sudo <command>` and fails, and the run's event stream
|
|
410
|
+
carries `{ "type": "needs-sudo", "command": "sudo <command>", "step":
|
|
411
|
+
"<service>" }` (sudo's own "a password is required" counts too, without
|
|
412
|
+
the command). Only then does the page ask, saying "`sudo <command>` needs
|
|
413
|
+
your admin password", with the 🔑 chip in the top bar while it waits. It
|
|
414
|
+
asks the server first (another tab or device may have set it), then, when
|
|
415
|
+
a password is stored, loads it from the vault (a page view alone never
|
|
416
|
+
wakes the vault), and only then shows the password field, always on top,
|
|
417
|
+
even over a dialog. After the password, the action runs again.
|
|
418
|
+
|
|
419
|
+
Settings › Command line › Admin password says whether it is saved ("Saved
|
|
420
|
+
in 1Password", "Saved for this session" or "Not saved"), with **Forget**:
|
|
421
|
+
`POST /api/sudo-password/forget` drops the password the server holds and,
|
|
422
|
+
when this server saved it to the vault or loaded it from there, clears the
|
|
423
|
+
vault's copy. `GET /api/sudo-password/status` answers `set`,
|
|
424
|
+
`passwordless`, `saved` (`vault`, `session` or null) and `vault` (`{
|
|
425
|
+
configured, label }`, read from the config alone).
|
|
393
426
|
|
|
394
427
|
A `1password` distribution with no refs gets one warning, in the web
|
|
395
428
|
server's startup log and in `<cli> validate`, with the YAML to add.
|
|
@@ -1190,12 +1223,14 @@ are the same directory.
|
|
|
1190
1223
|
## Signing in
|
|
1191
1224
|
|
|
1192
1225
|
On localhost nothing asks you to sign in: every page, the agents and the
|
|
1193
|
-
terminal work as they are, and the
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1226
|
+
terminal work as they are, and the admin password (R8) is the only prompt,
|
|
1227
|
+
asked only when a command Den runs needs sudo. Through the edge, a browser
|
|
1228
|
+
the edge shows is on this machine asks for nothing either. Signing in is
|
|
1229
|
+
for everything else: the edge for a client it can't place here (0.4.5), a
|
|
1230
|
+
LAN address, or a bind that isn't loopback (`DEN_BIND_HOST`). From another
|
|
1231
|
+
device, sign in with GitHub as the Den's owner, approve the computer with
|
|
1232
|
+
the code its page shows, or open a one-time link (below, and
|
|
1233
|
+
[Signing in with GitHub](#signing-in-with-github)).
|
|
1199
1234
|
|
|
1200
1235
|
There is no password. Reading `web.secret` (32 random bytes, mode 0600 in
|
|
1201
1236
|
the state dir, made at the first `web up`) proves you are this computer's
|
|
@@ -1203,7 +1238,10 @@ user, and the CLI does that for you:
|
|
|
1203
1238
|
|
|
1204
1239
|
```bash
|
|
1205
1240
|
./bin/acme web open # start if needed, and sign this browser in (a new tab)
|
|
1206
|
-
./bin/acme web login #
|
|
1241
|
+
./bin/acme web login # a one-time sign-in link and its code; waits until it's used
|
|
1242
|
+
./bin/acme web login --no-wait # the link alone (asks for no code), and return
|
|
1243
|
+
./bin/acme web approve ABC-123 # approve the computer showing this code
|
|
1244
|
+
./bin/acme web approve --list # the computers waiting with a code
|
|
1207
1245
|
./bin/acme web sessions # the signed-in browsers: id, created, last seen, host, user agent
|
|
1208
1246
|
./bin/acme web logout <id> # sign out one browser (an id prefix from `web sessions`)
|
|
1209
1247
|
./bin/acme web logout # sign out every browser
|
|
@@ -1220,7 +1258,19 @@ user, and the CLI does that for you:
|
|
|
1220
1258
|
among them; its URL comes from the edge config, and a name the edge
|
|
1221
1259
|
doesn't route here is refused, saying what to add), and
|
|
1222
1260
|
`web login --origin http://<lan name>:<port>` for any other allowed name
|
|
1223
|
-
(the signed-out page there shows the exact command).
|
|
1261
|
+
(the signed-out page there shows the exact command, under "Other ways").
|
|
1262
|
+
- **`web login` waits** (0.4.5). Besides the link it prints an
|
|
1263
|
+
8-character code ("It asks for this code: ABCD-EFGH") and blocks until
|
|
1264
|
+
the link is used, then says who: "Signed in: Safari on macOS
|
|
1265
|
+
(100.64.0.7) at 10:42". Nobody within the minute: "Expired: nobody used
|
|
1266
|
+
the link", exit 1. The link's page asks for the code before it signs in,
|
|
1267
|
+
so the link alone is useless; once opened it waits 5 minutes for the
|
|
1268
|
+
code. A wrong code is refused; five burn the link ("Refused: too many
|
|
1269
|
+
wrong codes", exit 1), more than 20 wrong a minute (any link) are slowed,
|
|
1270
|
+
and the link still works once. The CLI asks the running server through
|
|
1271
|
+
its bearer and never logs the code. `--no-wait` prints the link alone and
|
|
1272
|
+
returns, as before (no code); with no server running `web login` does
|
|
1273
|
+
that too, and says so.
|
|
1224
1274
|
- **The cookie** (`den_session_<port>`) is HttpOnly, SameSite=Strict and
|
|
1225
1275
|
Path=/, and Secure when the request came over HTTPS through the edge. The
|
|
1226
1276
|
session lives server-side (`web-sessions.json`, 0600, in the state dir)
|
|
@@ -1238,36 +1288,111 @@ user, and the CLI does that for you:
|
|
|
1238
1288
|
The Host allowlist, the Origin check and the CSRF token are what stop
|
|
1239
1289
|
another site or a rebinding DNS name. From another machine, every page
|
|
1240
1290
|
and call needs a signed-in browser (or a bearer). A page there shows
|
|
1241
|
-
"Sign in to
|
|
1291
|
+
"Sign in to <name>" and the ways to sign in (below); it never asks for a
|
|
1242
1292
|
secret. `/api` answers 401 `signin_required`.
|
|
1243
|
-
- **The
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
`
|
|
1293
|
+
- **The signed-out page says where to act.** It names the machine Den runs
|
|
1294
|
+
on ("You're opening Den from another computer. Den runs on
|
|
1295
|
+
**studio**, so it needs to know this is you."), then:
|
|
1296
|
+
- **Sign in with GitHub**, only when a client id is set (below);
|
|
1297
|
+
- **Approve this computer**: a code (`XXX-XXX`) and the command that
|
|
1298
|
+
approves it, to run on that machine (`<cli> web approve <code>`), or
|
|
1299
|
+
Settings › Access › Waiting devices there. The page signs itself in
|
|
1300
|
+
when it's approved and goes to the page you asked for; it counts down
|
|
1301
|
+
the 10 minutes, and an expired code is a click away from a new one.
|
|
1302
|
+
From a computer with SSH access to it, the page gives the one-liner
|
|
1303
|
+
(`ssh <host> '<cli> web approve <code>'`) and, when the host is a name,
|
|
1304
|
+
an Open Terminal (`ssh://`) link;
|
|
1305
|
+
- **Other ways**, collapsed: the one-time link with
|
|
1306
|
+
`web login --origin`.
|
|
1307
|
+
|
|
1308
|
+
Every command the page prints names the machine it runs on and has a
|
|
1309
|
+
copy button. It is `<cli>` when the first `<cli>` on that machine's PATH
|
|
1310
|
+
is this distribution's, else the absolute path of its bin.
|
|
1311
|
+
|
|
1312
|
+
- **The machine's name** on the page is `web.machine_name` from
|
|
1313
|
+
`config/local/user.yml` (Settings › Access › Machine name), else the short
|
|
1314
|
+
host name (up to the first dot). It is never taken from the browser's
|
|
1315
|
+
Host. The ssh one-liner's target is the Host the browser used, without
|
|
1316
|
+
the port (the machine's host name if it had none).
|
|
1317
|
+
- **Codes.** About 30 bits (Crockford base32: no I, L, O or U; typed in any
|
|
1318
|
+
case, the dash optional); the page polls with a separate 128-bit id. A
|
|
1319
|
+
code expires after 10 minutes and works once; at most five wait at once
|
|
1320
|
+
(a sixth drops the oldest). They live in the server's memory only.
|
|
1321
|
+
Approving is rate-limited (10 a minute) and recorded in the activity log
|
|
1322
|
+
with who approved, never the code. The new session is "approved with a
|
|
1323
|
+
code". **Deny** (Settings › Access, or `web deny` through the API) tells
|
|
1324
|
+
the page it was refused.
|
|
1325
|
+
- **Seeing that you're signed in.** The chip at the foot of the rail says
|
|
1326
|
+
"Local" (this machine), "Signed in · this device" (a code or a one-time
|
|
1327
|
+
link) or "@<login>" (GitHub), and opens Settings › Access. The first page
|
|
1328
|
+
after a sign-in says "This device is signed in to Den on <machine>",
|
|
1329
|
+
once. Linked devices lists every session (GitHub, a code, a one-time
|
|
1330
|
+
link): how it signed in, when, from where (the address and the browser
|
|
1331
|
+
and OS), last seen, and **Revoke**; the one you're using says "This
|
|
1332
|
+
device".
|
|
1333
|
+
- **The edge counts as local only when it shows the client is.** A
|
|
1334
|
+
request for the edge hostname from the edge (an edge peer, below) is
|
|
1335
|
+
treated as loopback (no sign-in on any route, the agents and the
|
|
1336
|
+
terminal included) when the client the edge reports is this machine.
|
|
1337
|
+
Host, Origin, CSRF and the agent-token rules still apply. Who the client
|
|
1338
|
+
is comes from the right-most `X-Forwarded-For` entry, the one the edge
|
|
1339
|
+
itself appended (anything left of it came from the client, and is never
|
|
1340
|
+
believed):
|
|
1248
1341
|
- 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;
|
|
1342
|
+
address, or a VPN's such as a Tailscale `100.x`): local, no sign-in;
|
|
1250
1343
|
- missing, or a container/VM network's address (the Docker ranges, or a
|
|
1251
1344
|
subnet of one of this machine's Docker/OrbStack/VM bridges): Den can't
|
|
1252
1345
|
tell (Docker Desktop and OrbStack on macOS hand the edge their
|
|
1253
1346
|
gateway's address, and a TCP passthrough in front of the edge hides the
|
|
1254
|
-
client), so
|
|
1255
|
-
- any other address: another machine, which
|
|
1256
|
-
|
|
1347
|
+
client), so it signs in: `web.edge_unattested`, below;
|
|
1348
|
+
- any other address: another machine, which signs in.
|
|
1349
|
+
|
|
1350
|
+
`web.edge_unattested: local` in `config/local/user.yml` counts a client
|
|
1351
|
+
Den can't place as local again (0.4.4's behaviour). It is read only from
|
|
1352
|
+
that file (a choice for that machine); the distribution config's key is
|
|
1353
|
+
ignored with a warning. The default is `signin`. A request straight to
|
|
1354
|
+
the server's port on localhost (loopback Host and peer) never signs in.
|
|
1257
1355
|
With `DEN_BIND_HOST` beyond loopback (a deliberate exposure, other than
|
|
1258
|
-
the Linux edge's Docker bridge address) the edge
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1356
|
+
the Linux edge's Docker bridge address) the edge always signs in.
|
|
1357
|
+
|
|
1358
|
+
- **The edge believes forwarded headers only from its own networks.** Den
|
|
1359
|
+
renders each entrypoint with `forwardedHeaders.trustedIPs` set to
|
|
1360
|
+
loopback and the Docker ranges (`127.0.0.1/32`, `::1/128`,
|
|
1361
|
+
`172.16.0.0/12`, `192.168.65.0/24`), never `forwardedHeaders.insecure`,
|
|
1362
|
+
so a client can't hand the edge an `X-Forwarded-For` of its own. An
|
|
1363
|
+
`edge.args` entry that sets `forwardedHeaders` or `proxyProtocol` is
|
|
1364
|
+
dropped, and `edge up` says so.
|
|
1365
|
+
- **A TCP passthrough in front of the edge** (a gateway that publishes
|
|
1366
|
+
`:443` and passes TLS through) hides the client: the edge sees the
|
|
1367
|
+
gateway's address. Have the gateway send the PROXY protocol, and list it
|
|
1368
|
+
in `edge.proxy_protocol.trusted_ips` (empty by default; IP addresses or
|
|
1369
|
+
CIDRs; `config/local/user.yml`'s list replaces the team's):
|
|
1370
|
+
|
|
1371
|
+
```yaml
|
|
1372
|
+
edge:
|
|
1373
|
+
proxy_protocol:
|
|
1374
|
+
trusted_ips: [172.18.0.5]
|
|
1375
|
+
```
|
|
1376
|
+
|
|
1377
|
+
Every entrypoint then takes the PROXY protocol from those addresses, and
|
|
1378
|
+
the client it carries is what Den sees. Configuring the gateway is the
|
|
1379
|
+
gateway's job.
|
|
1380
|
+
|
|
1381
|
+
- **`edge up` recreates the edge when its command changed.** It compares
|
|
1382
|
+
the running edge's command with the one it renders and says why ("acme-edge's
|
|
1383
|
+
settings changed (forwarded-header trust): recreating it once. Routes
|
|
1384
|
+
drop only while it restarts."). Upgrading to 0.4.5 does this once.
|
|
1262
1385
|
|
|
1263
1386
|
- **"Your edge is reachable from other machines".** When Den can tell, at
|
|
1264
1387
|
start, that the edge is reachable beyond this machine (an edge hostname
|
|
1265
1388
|
that resolves to an address that isn't loopback, such as a LAN or
|
|
1266
1389
|
Tailscale address, or the edge container publishing a port on every
|
|
1267
1390
|
interface, from `docker inspect`), it writes "Your edge is reachable from
|
|
1268
|
-
other machines
|
|
1269
|
-
|
|
1270
|
-
per browser).
|
|
1391
|
+
other machines. A browser it can't place as this one signs in first
|
|
1392
|
+
(Sign in with GitHub, or a pairing link)." once to `web.log`, and every
|
|
1393
|
+
page shows it as a banner (dismissed per browser). With
|
|
1394
|
+
`web.edge_unattested: local` it says instead that anyone it can't place
|
|
1395
|
+
can use terminals, and to remove the key to have them sign in.
|
|
1271
1396
|
- **localhost is the canonical name.** A cookie set on `localhost` isn't
|
|
1272
1397
|
sent to `127.0.0.1`, so the links Den prints use `localhost`.
|
|
1273
1398
|
- **Browsers.** Chromium, Firefox and Safari 16.4 or later: a sign-in link
|
|
@@ -1301,6 +1426,76 @@ logout --all` the old token is refused; the CLI reads the new one.
|
|
|
1301
1426
|
421 `misdirected`, so a LAN client can't send the edge's name straight to
|
|
1302
1427
|
the server's port.
|
|
1303
1428
|
|
|
1429
|
+
## Signing in with GitHub
|
|
1430
|
+
|
|
1431
|
+
A Den has one owner: a GitHub account. Once it is linked, that account
|
|
1432
|
+
signs in from any device (a phone, another laptop, a browser through the
|
|
1433
|
+
edge) with **Sign in with GitHub** on the signed-out page. Den stores only
|
|
1434
|
+
the account's id and login, never a token, and no other account gets in.
|
|
1435
|
+
|
|
1436
|
+
```bash
|
|
1437
|
+
./bin/acme web owner link # link the owner: GitHub's device flow, in the terminal
|
|
1438
|
+
./bin/acme web owner # show who it is (also: web owner show, --json)
|
|
1439
|
+
./bin/acme web owner unlink # forget the owner (signed-in browsers stay signed in)
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
- **`web owner link`** prints `https://github.com/login/device` and a code.
|
|
1443
|
+
Enter the code there and approve; Den asks GitHub who approved, stores
|
|
1444
|
+
`owner: github:<id>` and the login in `web-owner.json` (0600, in the
|
|
1445
|
+
state dir) and throws the token away. A second link is refused until
|
|
1446
|
+
`web owner unlink`. Settings › Access does the same in the browser
|
|
1447
|
+
(Link your GitHub account, Unlink): shell-grade actions, for a signed-in
|
|
1448
|
+
browser and never an agent's token.
|
|
1449
|
+
- **Sign in with GitHub** on the signed-out page shows a code and
|
|
1450
|
+
`github.com/login/device`. The server runs the flow, and when the owner
|
|
1451
|
+
approves it sets the same never-expiring session cookie a one-time link
|
|
1452
|
+
sets, and the page reloads signed in. Another account sees "@<login>
|
|
1453
|
+
isn't allowed on this Den". The token is thrown away either way. At most
|
|
1454
|
+
three sign-ins wait for GitHub at once; a code expires as GitHub says
|
|
1455
|
+
(15 minutes). With no owner linked the page says to run
|
|
1456
|
+
`web owner link`.
|
|
1457
|
+
- **Settings › Access** lists the linked devices (the browser, how it
|
|
1458
|
+
signed in, the client address when the edge vouched for one, created and
|
|
1459
|
+
last seen; "This device" on yours), each with **Revoke**, which ends its session at once (its
|
|
1460
|
+
open terminals included). **Pair a device** makes a one-time link and a
|
|
1461
|
+
QR code for an edge hostname you pick (`web login --host <name>` in the
|
|
1462
|
+
browser): scan it on the other device within 60 seconds.
|
|
1463
|
+
- **`web.github_client_id`** is the GitHub App's client id (public; no
|
|
1464
|
+
secret). The default is Den's own App, **Monster Orange Den**
|
|
1465
|
+
(`Iv23liKL12gnN6WvRV5b`), so Sign in with GitHub works out of the box. A
|
|
1466
|
+
distribution that ships its own App sets its client id here (below);
|
|
1467
|
+
`config/local/user.yml`'s wins over the team's. An empty value
|
|
1468
|
+
(`github_client_id: ""`) turns Sign in with GitHub off: the page then
|
|
1469
|
+
offers the code and the one-time link only.
|
|
1470
|
+
- **Expiring tokens.** If the App hands out expiring tokens (GitHub's
|
|
1471
|
+
answer carries `expires_in` or a `refresh_token`), sign-in still
|
|
1472
|
+
completes, and Den says once, in the log and on stderr, to switch off
|
|
1473
|
+
token expiry in the App's settings ("Expire user authorization tokens");
|
|
1474
|
+
it never prints the token. `GET /api/web/owner` reports it as
|
|
1475
|
+
`tokenExpiry: true`.
|
|
1476
|
+
|
|
1477
|
+
### The GitHub App (once, by whoever ships the Den)
|
|
1478
|
+
|
|
1479
|
+
Den's default App is Monster Orange Den. A distribution that wants its own
|
|
1480
|
+
(its name on GitHub's approval page) needs a GitHub App with the device
|
|
1481
|
+
flow on. Create it once (organization or account › Settings › Developer settings › GitHub
|
|
1482
|
+
Apps › New GitHub App):
|
|
1483
|
+
|
|
1484
|
+
1. No callback URL; untick "Request user authorization during
|
|
1485
|
+
installation". Webhook off.
|
|
1486
|
+
2. **Enable Device Flow**: ticked. The device flow needs only the client
|
|
1487
|
+
id.
|
|
1488
|
+
3. **Expire user authorization tokens**: off. Den never asks for
|
|
1489
|
+
`offline_access`, and keeps no token either way.
|
|
1490
|
+
4. Account permissions: none. Den reads only `GET /user` (the id and the
|
|
1491
|
+
login), which needs none. Repository permissions are only for features
|
|
1492
|
+
that act on a repository (Den's default App has Issues for feedback).
|
|
1493
|
+
5. **Where can this GitHub App be installed**: Any account. GitHub lets only
|
|
1494
|
+
members of the owning organization authorize a private App, so anyone
|
|
1495
|
+
else couldn't sign in.
|
|
1496
|
+
6. Put the **Client ID** in `web.github_client_id`. Never generate or
|
|
1497
|
+
share a client secret or a private key for Den: it never uses one.
|
|
1498
|
+
|
|
1304
1499
|
## Running the web UI as a service (macOS)
|
|
1305
1500
|
|
|
1306
1501
|
Den ships a launchd installer that runs the web UI at login and restarts it if
|
|
@@ -1448,19 +1643,40 @@ port.
|
|
|
1448
1643
|
`--workspace <ws>` and `--actor agent:<id>` (both or neither) scope the server
|
|
1449
1644
|
to one workspace for one agent:
|
|
1450
1645
|
|
|
1451
|
-
- **Pinned.** A tool that takes a `workspace` gets `<ws
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1646
|
+
- **Pinned.** A tool that takes a `workspace` gets `<ws>`. The server
|
|
1647
|
+
registers each workspace tool with "This workspace only" in its
|
|
1648
|
+
description and without the `workspace` parameter (with grants it stays,
|
|
1649
|
+
a name whose description lists `<ws>` and the granted ones; any other
|
|
1650
|
+
name gets the refusal below, not a schema error). A call naming another
|
|
1651
|
+
workspace is refused
|
|
1652
|
+
(`Refused: <tool>: this MCP server is scoped to workspace "<ws>"
|
|
1653
|
+
(agent:<id>; no grants); "<other>" is another workspace`; with grants,
|
|
1654
|
+
`grants: <a>, <b>`) before anything reaches the web server. The refusal
|
|
1655
|
+
says how to get more: a person adds `<other>` to `agents.<id>.grants` in
|
|
1656
|
+
the org config and restarts the agent; an agent can't grant itself one. A
|
|
1657
|
+
refused call is recorded in the activity trail, read tools included.
|
|
1658
|
+
- **Says so.** Every scoped answer (`status`, `services`, `applications`,
|
|
1659
|
+
`graph`, `stacks`, `workspaces`) carries a `scope` block: the workspace
|
|
1660
|
+
shown, how many other workspaces there are and how much it leaves out,
|
|
1661
|
+
for example `hidden: { repos: 6 }`, and a note pointing at `org_summary`.
|
|
1662
|
+
A scoped `graph` is the workspace's own repos and the edges out of them
|
|
1663
|
+
(`map: "workspace"`, with `outside` naming the repos they depend on
|
|
1664
|
+
elsewhere). Unscoped with no `workspace` it is the org-wide map
|
|
1665
|
+
(`map: "org-wide"`: every active workspace's graph merged, `workspaces`
|
|
1666
|
+
naming them); with one, that workspace's (`map: "workspace"`).
|
|
1456
1667
|
- **Filtered.** `workspaces` lists `<ws>` only. `logs_search` and `logs_tail`
|
|
1457
1668
|
take only `<ws>`'s services, `manifest_validate` takes only an absolute path
|
|
1458
1669
|
inside one of its repos (or the content), and `workspace_sync` syncs `<ws>`.
|
|
1459
1670
|
Tests run against the focused workspace, so `tests_available` and
|
|
1460
1671
|
`run_tests` are refused unless `<ws>` is the focused one.
|
|
1461
1672
|
- **Not workspace data**, served as unscoped: `environments`,
|
|
1462
|
-
`security_latest`, `release_status` and `
|
|
1463
|
-
distribution's, not a workspace's).
|
|
1673
|
+
`security_latest`, `release_status`, `edge_status` and `org_summary` (the
|
|
1674
|
+
org's and the distribution's, not a workspace's).
|
|
1675
|
+
- **The org at a glance.** `org_summary` gives every agent every workspace
|
|
1676
|
+
and repo with status counts, the graph, the edge and the environments
|
|
1677
|
+
(see [Tools](#tools)). Detail and actions stay scoped: for workspaces
|
|
1678
|
+
other than `<ws>` it carries names and counts only, never logs, env
|
|
1679
|
+
values, commands, URLs, paths or check output.
|
|
1464
1680
|
- **Stamped.** Every web API call carries `x-den-mcp-agent: agent:<id>` and
|
|
1465
1681
|
`x-den-mcp-workspace: <ws>` beside the usual audit headers. The activity
|
|
1466
1682
|
entry gains `agent` and `workspace`, and the log line reads `· by
|
|
@@ -1479,7 +1695,9 @@ agent:<id> <client> <version> (workspace <ws>, mcp pid <pid>, session
|
|
|
1479
1695
|
- `<ws>` must exist in this distribution; until it does, every tool says so.
|
|
1480
1696
|
The id is letters, digits, dots, dashes and underscores (at most 64). A bad
|
|
1481
1697
|
or half-given scope is a usage error, exit 2. `--list` and `--call` take
|
|
1482
|
-
the scope too
|
|
1698
|
+
the scope too: they list and validate against the pinned tools, as the
|
|
1699
|
+
server registers them, and `--call` names its scope on stderr
|
|
1700
|
+
(`acme mcp --call: scoped to <ws> as agent:<id>`).
|
|
1483
1701
|
|
|
1484
1702
|
**It binds cooperating agents only.** The scope lives in the MCP server's
|
|
1485
1703
|
process. The web server's HTTP API does not check it yet, so an agent that
|
|
@@ -1525,22 +1743,60 @@ guard off.
|
|
|
1525
1743
|
|
|
1526
1744
|
Read tools (always registered):
|
|
1527
1745
|
|
|
1528
|
-
| Tool | Web API | What
|
|
1529
|
-
| ------------------- | ------------------------------------------ |
|
|
1530
|
-
| `status` | `/api/health` | Service health
|
|
1531
|
-
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands
|
|
1532
|
-
| `applications` | `/api/applications` | Org > Application > Repo > Services
|
|
1533
|
-
| `graph` | `/api/graph` | The derived dependency graph and startup order
|
|
1534
|
-
| `workspaces` | `/api/workspaces` | Workspaces, active and focused
|
|
1535
|
-
| `environments` | `/api/environments` | The enabled environment names (never their values)
|
|
1536
|
-
| `logs_search` | `/api/logs/search` | Search services' full log history
|
|
1537
|
-
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow)
|
|
1538
|
-
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from
|
|
1539
|
-
| `security_latest` | `/api/security/latest` | The newest security scan per mode
|
|
1540
|
-
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table
|
|
1541
|
-
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content
|
|
1542
|
-
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health
|
|
1543
|
-
| `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names
|
|
1746
|
+
| Tool | Web API | What |
|
|
1747
|
+
| ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1748
|
+
| `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 |
|
|
1749
|
+
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands |
|
|
1750
|
+
| `applications` | `/api/applications` | Org > Application > Repo > Services |
|
|
1751
|
+
| `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) |
|
|
1752
|
+
| `workspaces` | `/api/workspaces` | Workspaces, active and focused |
|
|
1753
|
+
| `environments` | `/api/environments` | The enabled environment names (never their values) |
|
|
1754
|
+
| `logs_search` | `/api/logs/search` | Search services' full log history |
|
|
1755
|
+
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow) |
|
|
1756
|
+
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from |
|
|
1757
|
+
| `security_latest` | `/api/security/latest` | The newest security scan per mode |
|
|
1758
|
+
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table |
|
|
1759
|
+
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content |
|
|
1760
|
+
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health; an empty list says `why` |
|
|
1761
|
+
| `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) |
|
|
1762
|
+
| `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 |
|
|
1763
|
+
|
|
1764
|
+
**Health states.** A service with no verdict says why in `why`: no health
|
|
1765
|
+
check in the manifest for the env, remote with no check or `target.url`
|
|
1766
|
+
for its env, not deployed in the env (no commands and no check there), no
|
|
1767
|
+
manifest, or checked but no answer yet. A redirect (`302 → /login`) is
|
|
1768
|
+
`redirect` with its status and target (the query is dropped), not down:
|
|
1769
|
+
something answered. It is never green either, so an unexpected redirect
|
|
1770
|
+
(an ALB answering for a stopped service) is not taken for up; list the code
|
|
1771
|
+
in `target.expect` to count it as up. The web health row and `<cli> status`
|
|
1772
|
+
use the same rule: the web row shows a redirect muted (with `status` and `redirect`
|
|
1773
|
+
in `/api/health`), and `<cli> status` as `redirect 302 → /login`.
|
|
1774
|
+
|
|
1775
|
+
**`org_summary`** reads `/api/workspaces`, `/api/health` (local, every
|
|
1776
|
+
active workspace), `/api/graph`, `/api/edge` and `/api/environments`:
|
|
1777
|
+
|
|
1778
|
+
- `workspaces`: each workspace's name, whether it is active, focused or
|
|
1779
|
+
yours, its state (`running`: every checked service answers; `partial`:
|
|
1780
|
+
some are down; `stopped`: all checked ones are down; `unknown`: nothing
|
|
1781
|
+
checked; `inactive`: not polled), its counts (up, down, redirect,
|
|
1782
|
+
pending, not checked) and its repos, each with its rolled-up state.
|
|
1783
|
+
- `graph`: the org-wide map, every active workspace's repos merged (each
|
|
1784
|
+
workspace's graph from the web server; `workspaces` names them, and
|
|
1785
|
+
`unavailable` any whose graph failed), repo names and edge kinds only (no
|
|
1786
|
+
evidence, paths or URLs).
|
|
1787
|
+
- `edge`: up, state, domains, the route count and `https`.
|
|
1788
|
+
- `environments`: the enabled names, never their values.
|
|
1789
|
+
- scoped, `yours`: your workspace's services as `status` gives them.
|
|
1790
|
+
|
|
1791
|
+
**`edge_status.https`** says which TLS strategy is in use (`mkcert`,
|
|
1792
|
+
`files`, `none`), who supplies the cert (`den`, or `distribution` under
|
|
1793
|
+
`tls: files` and the legacy `issue_cert: false`) and whether HTTPS is
|
|
1794
|
+
serving: a TLS handshake on the edge's published https port (`serving`
|
|
1795
|
+
true or false, null when a proxy in front owns the port), with `detail`.
|
|
1796
|
+
`certIssuedByDen` replaces `certIssued`, which is kept for 0.4.4 with a
|
|
1797
|
+
note in `deprecated`: it said whether Den minted the cert, so it was
|
|
1798
|
+
`false` under `tls: files` while HTTPS worked. `edge status` prints the
|
|
1799
|
+
same `https` line.
|
|
1544
1800
|
|
|
1545
1801
|
Action tools (only those named in `mcp.actions`): `service_up`,
|
|
1546
1802
|
`service_down`, `service_restart` (through `/api/service/exec`),
|
|
@@ -1658,7 +1914,7 @@ server scoped to its workspace.
|
|
|
1658
1914
|
<cli> agents stop <id> [-w <ws>]
|
|
1659
1915
|
<cli> agents resume <id> [-w <ws>]
|
|
1660
1916
|
<cli> agents personas [--json]
|
|
1661
|
-
<cli> agents doctor [--json]
|
|
1917
|
+
<cli> agents doctor [-w <ws>] [--json] # can each runtime run here, and why not
|
|
1662
1918
|
<cli> sessions attach <ws>:<id> # its terminal; ctrl-] detaches
|
|
1663
1919
|
```
|
|
1664
1920
|
|
|
@@ -1708,9 +1964,10 @@ started. The workspace header counts them ("2 agents").
|
|
|
1708
1964
|
- The page and the CLI call the same operations and read the same agent
|
|
1709
1965
|
records, so an agent started in one is listed in the other. A workspace
|
|
1710
1966
|
lists its own agents only.
|
|
1711
|
-
- Start, Stop and Resume
|
|
1712
|
-
|
|
1713
|
-
for an agent (agents don't
|
|
1967
|
+
- Start, Stop and Resume never ask for the admin password (it is for
|
|
1968
|
+
sudo, and no agent action runs sudo), are capped by
|
|
1969
|
+
`agents.max_concurrent`, and are refused for an agent (agents don't
|
|
1970
|
+
start agents from the page). Each one is in the
|
|
1714
1971
|
server log's activity, with you as its actor. The agent's environment is
|
|
1715
1972
|
the one below, built from the web server's environment.
|
|
1716
1973
|
- **Open in Agents →** (the section header) opens the Agents tab for the
|
|
@@ -1720,7 +1977,7 @@ The routes: `GET /api/workspaces/<ws>/agents`, `POST
|
|
|
1720
1977
|
/api/workspaces/<ws>/agents` (`{ persona, runtime?, model?, id? }`), `POST
|
|
1721
1978
|
/api/workspaces/<ws>/agents/<id>/stop` and `…/resume`, and `GET
|
|
1722
1979
|
/api/personas` (personas and runtimes). Errors are the usual
|
|
1723
|
-
`{ ok: false, error, code, requestId }`: `
|
|
1980
|
+
`{ ok: false, error, code, requestId }`: `cap` (429),
|
|
1724
1981
|
`conflict` (409, e.g. already running, or no resume), `not_found` (404),
|
|
1725
1982
|
`exited_at_once` (502, with the session's last lines).
|
|
1726
1983
|
|
|
@@ -1743,12 +2000,10 @@ agent of the workspaces in view, and their terminals in the browser.
|
|
|
1743
2000
|
reload or Back reopens them.
|
|
1744
2001
|
- **Close** (×) detaches, as leaving the page does: the session keeps
|
|
1745
2002
|
running, and opening it again replays what you missed. **Stop** (an
|
|
1746
|
-
agent only) is `agents stop
|
|
2003
|
+
agent only) is `agents stop`. **+ Start agent** (or
|
|
1747
2004
|
`/agents?start=1`) starts one and opens its pane; under All it asks for
|
|
1748
2005
|
the workspace. It says before Start whether the runtime can run, as the
|
|
1749
|
-
Workspaces page's form does.
|
|
1750
|
-
for the admin password at its top, and Start unlocks, then starts, without
|
|
1751
|
-
closing anything.
|
|
2006
|
+
Workspaces page's form does. It never asks for the admin password.
|
|
1752
2007
|
- ⌘K lists each as "Agents › <ws> / <name>", and "Start agent…".
|
|
1753
2008
|
- Several browsers and `<cli> sessions attach` can attach to one session
|
|
1754
2009
|
at once and each sees every byte; the terminal's size is the last
|
|
@@ -1813,8 +2068,9 @@ header (agent headers without the token are recorded as an unverified
|
|
|
1813
2068
|
claim).
|
|
1814
2069
|
|
|
1815
2070
|
An agent's token reaches only its own session's routes, and never a
|
|
1816
|
-
shell-grade route, `/api/web/*`, sign-in or sign-out, or the
|
|
1817
|
-
|
|
2071
|
+
shell-grade route, `/api/web/*`, sign-in or sign-out, or the admin
|
|
2072
|
+
password's routes: it can't set the admin password, use it for sudo or
|
|
2073
|
+
start another agent (403
|
|
1818
2074
|
`agent_forbidden`). A person's credential never reaches a session:
|
|
1819
2075
|
`DEN_PERSON_TOKEN`, a person token's value and a path to `web.secret` are
|
|
1820
2076
|
always removed, and `sessions start --keep-env` of one is refused and logged.
|
|
@@ -1835,7 +2091,8 @@ it does not catch every spelling: a path relative to the agent's cwd
|
|
|
1835
2091
|
(`cat ../.den/web.secret`, say), another program, a script or a copy under
|
|
1836
2092
|
another name all get past it. The server's token rules (above)
|
|
1837
2093
|
are the boundary: whatever an agent reads, its token reaches only its own
|
|
1838
|
-
session's routes, never a shell-grade route or the
|
|
2094
|
+
session's routes, never a shell-grade route or the admin password's.
|
|
2095
|
+
OS-user isolation
|
|
1839
2096
|
(a separate user, a container, a sandbox) is not in 0.4.x. `web logout
|
|
1840
2097
|
--all` revokes every agent's token (above, under Signing in).
|
|
1841
2098
|
|
|
@@ -1884,7 +2141,8 @@ are errors naming the file or the key.
|
|
|
1884
2141
|
|
|
1885
2142
|
### Runtimes
|
|
1886
2143
|
|
|
1887
|
-
`<cli> doctor` lists the runtime kind.
|
|
2144
|
+
`<cli> doctor` lists the runtime kind. Den knows the runtimes in its
|
|
2145
|
+
**catalog** (below); two are special:
|
|
1888
2146
|
|
|
1889
2147
|
- **`den-claude`** (the default) runs Claude Code's `claude`, found as
|
|
1890
2148
|
below; `<cli> doctor` reads its version from the install, without running
|
|
@@ -1899,14 +2157,64 @@ are errors naming the file or the key.
|
|
|
1899
2157
|
- **`generic`** runs the persona's `argv` template. `{model}` is the agent's
|
|
1900
2158
|
model (an error when the template uses it and there is none);
|
|
1901
2159
|
`{mcp_config}` is the path of the agent's MCP config, written only when the
|
|
1902
|
-
template references it. It declares no resume.
|
|
2160
|
+
template references it. It declares no resume. It is startable only for
|
|
2161
|
+
a persona that has an `argv`.
|
|
2162
|
+
|
|
2163
|
+
**The runtime catalog.** Den ships, as data, every vendor CLI it knows how
|
|
2164
|
+
to find. Each entry was checked against the vendor's own docs or installer:
|
|
2165
|
+
|
|
2166
|
+
| id | Vendor | CLI | Found in (after PATH) | Source |
|
|
2167
|
+
| ------------ | --------- | --------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
2168
|
+
| `den-claude` | Anthropic | Claude Code, `claude` | `~/.local/bin`, `~/.claude/local`, system dirs | [setup](https://code.claude.com/docs/en/setup) |
|
|
2169
|
+
| `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) |
|
|
2170
|
+
| `gemini` | Google | Gemini CLI, `gemini` | system dirs | [google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli) |
|
|
2171
|
+
| `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) |
|
|
2172
|
+
|
|
2173
|
+
"System dirs" are `/opt/homebrew/bin`, `/usr/local/bin` and npm's global
|
|
2174
|
+
bin. Each one's version is `<bin> --version`. Every vendor here publishes
|
|
2175
|
+
an official CLI, so none is left out; a vendor without one is left out
|
|
2176
|
+
rather than guessed at (community CLIs, such as third-party "grok" CLIs,
|
|
2177
|
+
are not xAI's and are not in the catalog).
|
|
2178
|
+
|
|
2179
|
+
Claude Code runs as `den-claude` (above). Every other installed runtime
|
|
2180
|
+
runs its own interactive CLI (`codex`, `gemini`, `grok`) in the
|
|
2181
|
+
workspace's directory; Den's tools (MCP) and permission modes are coming
|
|
2182
|
+
for them, and lists tag them **Den tools: coming**. A runtime is offered
|
|
2183
|
+
as startable only when it can start here: installed (or, for `generic`,
|
|
2184
|
+
with the persona's `argv`).
|
|
2185
|
+
|
|
2186
|
+
A distribution adds entries with `agents.runtime_catalog` in its config,
|
|
2187
|
+
and a person with the same key in `config/local/user.yml`. Both go
|
|
2188
|
+
through the validator the built-in entries pass; an entry can't reuse a
|
|
2189
|
+
catalog id (set a path instead, below):
|
|
2190
|
+
|
|
2191
|
+
```yaml
|
|
2192
|
+
agents:
|
|
2193
|
+
runtime_catalog:
|
|
2194
|
+
- id: aider # lowercase, starts with a letter
|
|
2195
|
+
vendor: Aider
|
|
2196
|
+
name: Aider
|
|
2197
|
+
bins: [aider] # executable names, no paths
|
|
2198
|
+
version:
|
|
2199
|
+
args: [--version]
|
|
2200
|
+
identity: # at least one: how a path is known to be it
|
|
2201
|
+
output: "aider" # a pattern its version output matches
|
|
2202
|
+
# package: aider-chat (the npm package it resolves into)
|
|
2203
|
+
# realpath_under: [~/.aider] (where it really lives)
|
|
2204
|
+
dirs: [~/.local/bin, $AIDER_DIR, "{system}"]
|
|
2205
|
+
launch:
|
|
2206
|
+
argv: ["{bin}"] # "{bin}" first: the path found
|
|
2207
|
+
```
|
|
1903
2208
|
|
|
1904
2209
|
**Where a runtime's CLI is found.** As your terminal would find it, not
|
|
1905
2210
|
only on the web server's own PATH (a launchd service's PATH leaves out
|
|
1906
2211
|
`~/.local/bin`, where Claude Code's native installer puts `claude`). The
|
|
1907
2212
|
first match wins:
|
|
1908
2213
|
|
|
1909
|
-
1.
|
|
2214
|
+
1. **A workspace's own path**, `<ws>.agents.runtimes.<id>.bin` in
|
|
2215
|
+
`config/local/workspaces.yml`, for that workspace's agents. Checked the
|
|
2216
|
+
same way as the next one.
|
|
2217
|
+
2. **`agents.runtimes.<id>.bin`** in `config/local/user.yml`: the absolute
|
|
1910
2218
|
path of the runtime's CLI. Paths are per machine, so it is read from
|
|
1911
2219
|
user.yml only, never from the distribution's config. A bad value (not
|
|
1912
2220
|
absolute, missing, not an executable file) makes the runtime
|
|
@@ -1920,14 +2228,28 @@ first match wins:
|
|
|
1920
2228
|
bin: /Users/me/.local/bin/claude
|
|
1921
2229
|
```
|
|
1922
2230
|
|
|
1923
|
-
|
|
2231
|
+
3. **Your login shell's PATH**: the web server asks `$SHELL -lc` once at
|
|
1924
2232
|
start, in the background, under a 3-second timeout. On a timeout or
|
|
1925
2233
|
any failure it is left out.
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
`~/.claude/local`, `/opt/homebrew/bin`,
|
|
1929
|
-
global bin
|
|
1930
|
-
never runs npm to find it.
|
|
2234
|
+
4. **The server's own PATH.**
|
|
2235
|
+
5. **The runtime's install directories** (its catalog entry's; for
|
|
2236
|
+
den-claude: `~/.local/bin`, `~/.claude/local`, `/opt/homebrew/bin`,
|
|
2237
|
+
`/usr/local/bin` and npm's global bin, `$npm_config_prefix/bin` and
|
|
2238
|
+
node's own directory). Den never runs npm to find it.
|
|
2239
|
+
6. **Your interactive shell's PATH**, only while a catalog runtime is
|
|
2240
|
+
still not found: `$SHELL -ilc` once (stdin closed, 3-second timeout,
|
|
2241
|
+
the answer kept), for a PATH set only in an interactive startup file
|
|
2242
|
+
such as `~/.zshrc`. Its source is `interactive-shell`.
|
|
2243
|
+
|
|
2244
|
+
Paths are this machine's only: Den reads them from `config/local` files,
|
|
2245
|
+
never from the distribution's config. **Settings › Agents › Runtimes**
|
|
2246
|
+
shows each runtime's status, version, the path in use and its source.
|
|
2247
|
+
**Set path…** runs the runtime's version command and refuses a path that
|
|
2248
|
+
isn't that runtime (its output, npm package or install location must name
|
|
2249
|
+
it); **Clear** removes it. The **Paths for** selector switches between
|
|
2250
|
+
every workspace (`user.yml`) and one workspace (`workspaces.yml`). Only a
|
|
2251
|
+
person sets a path, never an agent (`POST /api/agents/runtimes/<id>/path`,
|
|
2252
|
+
audited).
|
|
1931
2253
|
|
|
1932
2254
|
Sessions and agents Den starts get the same directories on their PATH
|
|
1933
2255
|
(and a configured CLI's own directory), so the runtime's own tools are
|
|
@@ -1936,17 +2258,19 @@ diagnosis: `DEN_LOGIN_SHELL` (the shell to ask instead of `$SHELL`; `off`
|
|
|
1936
2258
|
for none) and `DEN_RUNTIME_DIRS` (a PATH-style list that replaces every
|
|
1937
2259
|
runtime's install directories; empty for none).
|
|
1938
2260
|
|
|
1939
|
-
**`<cli> agents doctor`** prints, for each runtime
|
|
1940
|
-
runs and where that came from (the
|
|
1941
|
-
shell's PATH, PATH,
|
|
2261
|
+
**`<cli> agents doctor`** prints, for each runtime (installed first), its
|
|
2262
|
+
status, the CLI it runs and where that came from (the workspace's or the
|
|
2263
|
+
user.yml `agents.runtimes` setting, the login shell's PATH, PATH, an
|
|
2264
|
+
install directory, or the interactive shell's PATH), its version (`claude
|
|
1942
2265
|
--version`, run once under a timeout), the directories it searched (names
|
|
1943
2266
|
only, never an environment value), and the fix when it's missing. It
|
|
1944
2267
|
resolves the login shell's PATH first, as the web server does. It exits 1
|
|
1945
|
-
when the default runtime is unavailable;
|
|
1946
|
-
dialog gets.
|
|
2268
|
+
when the default runtime is unavailable; `-w <ws>` uses that workspace's
|
|
2269
|
+
paths; `--json` prints what the Start dialog gets. A search list the same
|
|
2270
|
+
as the one above it prints once.
|
|
1947
2271
|
|
|
1948
2272
|
```
|
|
1949
|
-
den-claude (Claude Code) [default]
|
|
2273
|
+
den-claude (Claude Code, Anthropic) [default]
|
|
1950
2274
|
status ✗ unavailable: claude not found (searched 5 directories)
|
|
1951
2275
|
searched /usr/bin
|
|
1952
2276
|
/bin
|
|
@@ -1955,9 +2279,12 @@ den-claude (Claude Code) [default]
|
|
|
1955
2279
|
then run `<cli> agents doctor` again
|
|
1956
2280
|
```
|
|
1957
2281
|
|
|
1958
|
-
`GET /api/personas`
|
|
1959
|
-
|
|
1960
|
-
`
|
|
2282
|
+
`GET /api/personas` and `GET /api/agents/runtimes` (each with an optional
|
|
2283
|
+
`?workspace=<ws>`) give each runtime its `vendor`, `denTools`, `version`,
|
|
2284
|
+
`path`, `source` (`workspace`, `setting`, `login-shell`, `PATH`,
|
|
2285
|
+
`well-known` or `interactive-shell`), `searched`, `setting` (the user.yml
|
|
2286
|
+
key) and `workspaceSetting` (the workspaces.yml key), and `userConfig`,
|
|
2287
|
+
the user.yml to edit.
|
|
1961
2288
|
|
|
1962
2289
|
### Workspace scope and grants
|
|
1963
2290
|
|
|
@@ -1979,6 +2306,26 @@ with both workspaces named. Any key of `agents:` other than `max_concurrent`
|
|
|
1979
2306
|
and `persona_packs` is an agent id, and its only key is `grants`. A grant
|
|
1980
2307
|
naming a workspace that doesn't exist stops `start`, naming the key.
|
|
1981
2308
|
|
|
2309
|
+
## Dev metrics: local unless you opt in
|
|
2310
|
+
|
|
2311
|
+
Den records each command it runs (its name, surface, duration and exit
|
|
2312
|
+
code, never arguments or values) as one JSON line in
|
|
2313
|
+
`.dev-metrics/events.jsonl` under the distribution root. That file stays
|
|
2314
|
+
on the machine.
|
|
2315
|
+
|
|
2316
|
+
Nothing is sent anywhere unless the engineer opts in, per machine:
|
|
2317
|
+
|
|
2318
|
+
```yaml
|
|
2319
|
+
# config/local/user.yml (gitignored)
|
|
2320
|
+
metrics:
|
|
2321
|
+
share: true
|
|
2322
|
+
```
|
|
2323
|
+
|
|
2324
|
+
Only the boolean `true` counts. With it, and a Sentry DSN in
|
|
2325
|
+
`DEV_METRICS_SENTRY_DSN` (or a synced `dev-metrics.config`), each line is
|
|
2326
|
+
also sent, best effort. A DSN on its own never sends. `CI=true` and
|
|
2327
|
+
`DEV_METRICS_DISABLED=1` stop sending even when opted in.
|
|
2328
|
+
|
|
1982
2329
|
## Pinning and upgrading
|
|
1983
2330
|
|
|
1984
2331
|
`package.json` pins an exact Den version (`create` writes the version that
|