@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +348 -0
  2. package/README.md +12 -11
  3. package/docs/distributions.md +438 -91
  4. package/docs/reference.md +8 -3
  5. package/package.json +1 -1
  6. package/src/shared/agents/catalog-runtime.ts +178 -0
  7. package/src/shared/agents/catalog.ts +343 -0
  8. package/src/shared/agents/den-claude.ts +14 -18
  9. package/src/shared/agents/runtime-search.ts +142 -15
  10. package/src/shared/agents/runtime.ts +18 -1
  11. package/src/shared/agents/runtimes.ts +23 -1
  12. package/src/shared/auth/sudo-vault.ts +131 -11
  13. package/src/shared/cli-command.ts +186 -0
  14. package/src/shared/confirm-code.ts +34 -0
  15. package/src/shared/den-config.ts +64 -3
  16. package/src/shared/dev-metrics/emit.ts +31 -1
  17. package/src/shared/edge/config.ts +39 -0
  18. package/src/shared/edge/render.ts +92 -18
  19. package/src/shared/edge/runtime.ts +57 -1
  20. package/src/shared/edge/status.ts +134 -0
  21. package/src/shared/edge/types.ts +11 -1
  22. package/src/shared/environment-manager/health.ts +82 -8
  23. package/src/shared/environment-manager/workspaces.ts +54 -0
  24. package/src/shared/github-app.ts +94 -0
  25. package/src/shared/github-device.ts +286 -0
  26. package/src/shared/health/not-checked.ts +42 -0
  27. package/src/shared/health/rollup.ts +4 -0
  28. package/src/shared/process/spawn-streaming.ts +8 -3
  29. package/src/shared/secrets/onepassword.ts +27 -4
  30. package/src/shared/sessions/client.ts +5 -1
  31. package/src/shared/sessions/host.ts +29 -1
  32. package/src/shared/sessions/protocol.ts +6 -1
  33. package/src/shared/sessions/types.ts +6 -0
  34. package/src/shared/stacks/status.ts +24 -0
  35. package/src/shared/strategies/registry.ts +5 -0
  36. package/src/shared/types/index.ts +6 -0
  37. package/src/shared/web-owner.ts +91 -0
  38. package/src/shared/web-session.ts +135 -1
  39. package/src/ui/cli/agent-ops.ts +210 -19
  40. package/src/ui/cli/agents-command.ts +50 -21
  41. package/src/ui/cli/edge-command.ts +16 -3
  42. package/src/ui/cli/flag-parser.ts +1 -1
  43. package/src/ui/cli/health-display.ts +4 -1
  44. package/src/ui/cli/web-command.ts +512 -33
  45. package/src/ui/mcp/scope.ts +23 -3
  46. package/src/ui/mcp/server.ts +31 -7
  47. package/src/ui/mcp/tools.ts +327 -20
  48. package/src/ui/mcp/views.ts +450 -0
  49. package/src/ui/server/agent-routes.ts +118 -28
  50. package/src/ui/server/device-flows.ts +274 -0
  51. package/src/ui/server/edge-exposure.ts +16 -4
  52. package/src/ui/server/github-routes.ts +321 -0
  53. package/src/ui/server/http-errors.ts +10 -0
  54. package/src/ui/server/index.ts +540 -33
  55. package/src/ui/server/login-waits.ts +203 -0
  56. package/src/ui/server/pair-requests.ts +237 -0
  57. package/src/ui/server/pair-routes.ts +196 -0
  58. package/src/ui/server/public/agent-start.js +46 -7
  59. package/src/ui/server/public/agents-page.js +232 -97
  60. package/src/ui/server/public/agents-section.js +3 -5
  61. package/src/ui/server/public/agents.html +12 -28
  62. package/src/ui/server/public/error-page.js +452 -40
  63. package/src/ui/server/public/error.html +59 -0
  64. package/src/ui/server/public/graph-model.js +2 -0
  65. package/src/ui/server/public/header.js +2 -3
  66. package/src/ui/server/public/health-rollup.js +20 -8
  67. package/src/ui/server/public/i18n/es.js +89 -21
  68. package/src/ui/server/public/i18n/ja.js +88 -21
  69. package/src/ui/server/public/index.html +17 -4
  70. package/src/ui/server/public/local-dev.html +44 -21
  71. package/src/ui/server/public/qr.js +317 -0
  72. package/src/ui/server/public/services.html +21 -5
  73. package/src/ui/server/public/settings-page.js +10 -2
  74. package/src/ui/server/public/settings-registry.js +617 -1
  75. package/src/ui/server/public/settings.html +90 -1
  76. package/src/ui/server/public/shell.js +151 -33
  77. package/src/ui/server/public/topbar.js +170 -206
  78. package/src/ui/server/public/workspace-picker.js +1 -1
  79. package/src/ui/server/readonly-routes.ts +29 -5
  80. package/src/ui/server/request-guard.ts +103 -36
  81. package/src/ui/server/route-classes.ts +116 -4
  82. package/src/ui/server/session-routes.ts +9 -1
  83. package/src/ui/server/sudo-store.ts +170 -6
  84. package/src/ui/server/ui-assets.ts +2 -0
  85. package/src/ui/server/workspace-routes.ts +10 -3
@@ -24,8 +24,10 @@
24
24
  * running: a new session, in the same cwd, continuing the last run;
25
25
  * when that session was never prompted, a fresh start under the
26
26
  * same conversation id (there is nothing to continue).
27
- * doctor (AG-0.3) each runtime: its status, the CLI it runs and where
28
- * that came from (setting, login-shell, PATH, well-known), its
27
+ * doctor (AG-0.3, AG-0.5) each runtime of the catalog, installed
28
+ * first: its status, the CLI it runs and where that came from
29
+ * (workspace or user.yml setting, login shell, PATH, install
30
+ * directory, interactive shell), its
29
31
  * version (its version command, once, under a timeout), the
30
32
  * directories searched (names only, never an env value), and the
31
33
  * fix when it is missing. Exit 1 when the default runtime is
@@ -88,16 +90,20 @@ export const AGENTS_HELP = {
88
90
  " a session never prompted starts fresh under the same conversation id",
89
91
  "{cli} agents personas [--json]",
90
92
  " Den's default pack, then agents.persona_packs (a later pack wins by id)",
91
- "{cli} agents doctor [--json]",
92
- " each runtime: status, the CLI it runs and where it came from, its version,",
93
- " the directories searched, and the fix; exit 1 when the default is missing",
93
+ "{cli} agents doctor [-w <ws>] [--json]",
94
+ " each runtime, installed first: status, the CLI it runs and where it came",
95
+ " from, its version, the directories searched, and the fix; exit 1 when the",
96
+ " default is missing. -w: as that workspace's agents find them",
94
97
  "",
95
- "Runtimes: den-claude (default; Claude Code's claude) and generic (the",
96
- "persona's argv template). Attach with `{cli} sessions attach <ws>:<id>`.",
97
- "A runtime's CLI is found as your terminal finds it: agents.runtimes.<id>.bin",
98
- "in config/local/user.yml (an absolute path) wins, then the login shell's PATH,",
99
- "this PATH, and its install directories (~/.local/bin, ~/.claude/local,",
100
- "/opt/homebrew/bin, /usr/local/bin, npm's global bin).",
98
+ "Runtimes: den-claude (default; Claude Code's claude), codex (OpenAI's Codex",
99
+ "CLI), gemini (Google's Gemini CLI), grok (xAI's Grok Build), and generic (the",
100
+ "persona's argv template). codex, gemini and grok run as their own CLI in the",
101
+ "workspace, without Den's tools yet. Attach with `{cli} sessions attach <ws>:<id>`.",
102
+ "A runtime's CLI is found as your terminal finds it: <ws>.agents.runtimes.<id>.bin",
103
+ "in config/local/workspaces.yml, then agents.runtimes.<id>.bin in",
104
+ "config/local/user.yml (absolute paths), then the login shell's PATH, this PATH,",
105
+ "its install directories (~/.local/bin, /opt/homebrew/bin, /usr/local/bin, npm's",
106
+ "global bin, the vendor's own), and last the interactive shell's PATH.",
101
107
  "An agent's env is PATH, HOME, USER, LOGNAME, SHELL, TERM, COLORTERM, LANG,",
102
108
  "LC_*, TZ and TMPDIR, plus what its runtime declares: nothing else of yours.",
103
109
  ],
@@ -273,10 +279,12 @@ export interface AgentsDoctorDeps {
273
279
  }
274
280
 
275
281
  const SOURCE_WORD: Record<string, string> = {
282
+ workspace: "the workspace's agents.runtimes setting",
276
283
  setting: "agents.runtimes setting",
277
284
  "login-shell": "login shell PATH",
278
285
  PATH: "PATH",
279
286
  "well-known": "install directory",
287
+ "interactive-shell": "interactive shell PATH",
280
288
  };
281
289
 
282
290
  /** `agents doctor`'s lines for one runtime (names and paths, never env values). */
@@ -284,9 +292,11 @@ export function doctorLines(
284
292
  r: RuntimeChoice,
285
293
  cli: string,
286
294
  userConfig: string,
295
+ /** Directory lists already printed (a repeat is counted, not listed). */
296
+ printed: string[] = [],
287
297
  ): string[] {
288
298
  const lines = [
289
- `${r.id}${r.label ? ` (${r.label})` : ""}${r.default ? " [default]" : ""}`,
299
+ `${r.id}${r.label ? ` (${r.label}${r.vendor ? `, ${r.vendor}` : ""})` : ""}${r.default ? " [default]" : ""}${r.denTools === false ? " [Den tools: coming]" : ""}`,
290
300
  r.available
291
301
  ? " status ✓ available"
292
302
  : ` status ✗ unavailable: ${r.why ?? "unknown"}`,
@@ -298,11 +308,19 @@ export function doctorLines(
298
308
  if (r.available && r.setting)
299
309
  lines.push(` version ${r.version ?? "unknown"}`);
300
310
  if (!r.setting) lines.push(" path none to find (the persona's argv)");
301
- else if (r.searched?.length)
302
- r.searched.forEach((dir, i) =>
303
- lines.push(` ${i === 0 ? "searched " : " "}${dir}`),
304
- );
305
- else if (r.source !== "setting") lines.push(" searched (no directories)");
311
+ else if (r.searched?.length) {
312
+ const key = r.searched.join("\0");
313
+ if (printed.includes(key))
314
+ lines.push(
315
+ ` searched the same ${r.searched.length} ${r.searched.length === 1 ? "directory" : "directories"} as above`,
316
+ );
317
+ else {
318
+ printed.push(key);
319
+ r.searched.forEach((dir, i) =>
320
+ lines.push(` ${i === 0 ? "searched " : " "}${dir}`),
321
+ );
322
+ }
323
+ } else if (r.source !== "setting") lines.push(" searched (no directories)");
306
324
  if (!r.available && r.setting)
307
325
  lines.push(
308
326
  ` fix install ${r.label ?? r.id}, or set ${r.setting} to its absolute path in ${userConfig}`,
@@ -317,25 +335,34 @@ async function doctor(
317
335
  deps: AgentsDoctorDeps,
318
336
  ): Promise<number> {
319
337
  const where = "agents doctor";
320
- const p = parseArgs(where, args, { "--json": false });
338
+ const p = parseArgs(where, args, { "--workspace": true, "--json": false });
321
339
  noExtra(where, p);
340
+ const ws = workspaceFlag(where, p);
341
+ if (ws && !d.workspaceExists(ws))
342
+ throw new UsageError(`${where}: workspace "${ws}" does not exist`);
322
343
  await (deps.loginPath ?? (() => initLoginPath({ env: d.env })))();
323
- const rts = await listRuntimes(d);
344
+ const rts = await listRuntimes(d, ws);
324
345
  const def = rts.find((r) => r.default);
325
346
  const code = def?.available ? 0 : 1;
326
347
  if (p.flags["--json"]) {
327
348
  d.out(
328
349
  JSON.stringify(
329
- { runtimes: rts, userConfig: d.userConfigFile() },
350
+ {
351
+ runtimes: rts,
352
+ ...(ws ? { workspace: ws } : {}),
353
+ userConfig: d.userConfigFile(),
354
+ },
330
355
  null,
331
356
  2,
332
357
  ),
333
358
  );
334
359
  return code;
335
360
  }
361
+ const printed: string[] = [];
336
362
  rts.forEach((r, i) => {
337
363
  if (i) d.out("");
338
- for (const l of doctorLines(r, d.cli, d.userConfigFile())) d.out(l);
364
+ for (const l of doctorLines(r, d.cli, d.userConfigFile(), printed))
365
+ d.out(l);
339
366
  });
340
367
  d.out("");
341
368
  d.out(
@@ -369,6 +396,8 @@ export async function cmdAgents(
369
396
  case "personas":
370
397
  return await listPersonas(rest, d);
371
398
  case "doctor":
399
+ // -w checks the workspace exists, and reads its runtime paths.
400
+ await loadAgentWorkspaces(deps);
372
401
  return await doctor(rest, d, deps);
373
402
  default:
374
403
  throw new UsageError(`unknown agents command "${sub}"`);
@@ -25,7 +25,11 @@ import {
25
25
  routerUrls,
26
26
  type IncludedRouter,
27
27
  } from "../../shared/edge/included.js";
28
- import { edgeRoutes, edgeStatus } from "../../shared/edge/status.js";
28
+ import {
29
+ edgeRoutes,
30
+ edgeStatus,
31
+ withHttpsServing,
32
+ } from "../../shared/edge/status.js";
29
33
  import { tlsYaml } from "../../shared/edge/tls.js";
30
34
  import { rejoinRunningStacks } from "../../shared/stacks/status.js";
31
35
 
@@ -102,7 +106,7 @@ export async function cmdEdge(args: string[]): Promise<void> {
102
106
  return;
103
107
  }
104
108
  case "status": {
105
- const st = edgeStatus(cfg);
109
+ const st = await withHttpsServing(edgeStatus(cfg), cfg);
106
110
  console.log(chalk.bold(`${readBrand().name} edge — ${st.org}`));
107
111
  console.log(` container ${st.container}: ${st.state}`);
108
112
  console.log(` ports ${published}`);
@@ -116,7 +120,16 @@ export async function cmdEdge(args: string[]): Promise<void> {
116
120
  // browser trust (BOOT-1), so this machine's trust is moot.
117
121
  ` CA ${caDir(cfg)} — ${!st.ports ? "not needed: fronted" : st.caTrusted === null ? "unknown" : st.caTrusted ? "trusted" : "NOT trusted"}`,
118
122
  );
119
- if (st.certIssued) console.log(` cert ${st.certNames.join(", ")}`);
123
+ if (st.certIssuedByDen)
124
+ console.log(` cert ${st.certNames.join(", ")}`);
125
+ else if (st.https.certs === "distribution")
126
+ console.log(
127
+ " cert the distribution's (tls: files): not minted by Den",
128
+ );
129
+ // MCP-2.5: whether HTTPS is actually serving, not only configured.
130
+ console.log(
131
+ ` https ${st.https.serving === null ? "not probed" : st.https.serving ? "serving" : "NOT serving"} — ${st.https.detail}`,
132
+ );
120
133
  return;
121
134
  }
122
135
  default:
@@ -179,7 +179,7 @@ function validateFlagScopes(parsed: ParsedCliArgs): void {
179
179
  */
180
180
  export const SUBCOMMAND_OWN_HELP: Readonly<Record<string, readonly string[]>> =
181
181
  {
182
- web: ["token", "login", "sessions", "logout"],
182
+ web: ["token", "login", "sessions", "logout", "owner", "approve"],
183
183
  mcp: ["connect", "disconnect", "clients", "config"],
184
184
  };
185
185
 
@@ -77,11 +77,14 @@ export function formatRollupLabel(r: RepoRollup): string {
77
77
 
78
78
  /**
79
79
  * Why, for a repo row: the down services ("mailpit down"), and how many
80
- * services aren't checked ("25 not checked"). Unchecked never means down.
80
+ * services aren't checked ("25 not checked"), and how many answered with a
81
+ * redirect ("1 redirect": answering, so neither down nor unchecked).
82
+ * Unchecked never means down.
81
83
  */
82
84
  export function rollupReasonText(r: RepoRollup): string {
83
85
  return [
84
86
  r.down.length ? `${r.down.join(", ")} down` : "",
87
+ r.redirected ? `${r.redirected} redirect` : "",
85
88
  r.unchecked ? `${r.unchecked} not checked` : "",
86
89
  ]
87
90
  .filter(Boolean)