@monster-orange/den 0.2.8 → 0.3.0

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 (127) hide show
  1. package/CHANGELOG.md +1547 -0
  2. package/README.md +4 -2
  3. package/bin/den +2 -1
  4. package/docs/distributions.md +507 -4
  5. package/make/den.mk +19 -4
  6. package/make/journeys.mk +2 -1
  7. package/make/local_dev.mk +6 -13
  8. package/make/secrets-1password.mk +18 -0
  9. package/make/test.mk +1 -1
  10. package/package.json +9 -2
  11. package/schemas/den.plugin.v1.json +156 -0
  12. package/schemas/den.theme.v1.json +269 -0
  13. package/scripts/auth-configure.ts +16 -8
  14. package/scripts/heal-config.ts +0 -5
  15. package/scripts/setup-1password.ts +41 -19
  16. package/scripts/setup-secrets.ts +67 -101
  17. package/src/pillars/api/global-setup.ts +1 -1
  18. package/src/pillars/journeys/sandbox.ts +23 -18
  19. package/src/pillars/journeys/tools/browser-session.ts +2 -2
  20. package/src/scripts/setup-default-workspace.ts +2 -2
  21. package/src/shared/auth/headless-login.ts +1 -1
  22. package/src/shared/auth/sudo-vault.ts +93 -31
  23. package/src/shared/auth/token-providers/index.ts +7 -3
  24. package/src/shared/brand.ts +7 -0
  25. package/src/shared/config-error.ts +33 -3
  26. package/src/shared/constants.ts +3 -24
  27. package/src/shared/den-config.ts +141 -5
  28. package/src/shared/edge/config.ts +147 -21
  29. package/src/shared/edge/exec.ts +31 -0
  30. package/src/shared/edge/included.ts +169 -0
  31. package/src/shared/edge/render.ts +69 -14
  32. package/src/shared/edge/runtime.ts +128 -121
  33. package/src/shared/edge/status.ts +68 -8
  34. package/src/shared/edge/tls.ts +328 -0
  35. package/src/shared/edge/types.ts +25 -9
  36. package/src/shared/environment-manager/default-workspace.ts +16 -1
  37. package/src/shared/environment-manager/health.ts +73 -38
  38. package/src/shared/environment-manager/scope.ts +168 -0
  39. package/src/shared/environment-manager/service-runner.ts +789 -43
  40. package/src/shared/environment-manager/up-targets.ts +132 -0
  41. package/src/shared/health/rollup.ts +48 -0
  42. package/src/shared/open-url.ts +21 -2
  43. package/src/shared/org-sources.ts +78 -59
  44. package/src/shared/org.ts +53 -5
  45. package/src/shared/plugins/manifest.ts +406 -0
  46. package/src/shared/plugins/store.ts +358 -0
  47. package/src/shared/process/legacy-adopt.ts +285 -0
  48. package/src/shared/process/process-group.ts +202 -0
  49. package/src/shared/process/spawn-streaming.ts +7 -0
  50. package/src/shared/secrets/doppler.ts +1 -1
  51. package/src/shared/secrets/index.ts +279 -50
  52. package/src/shared/secrets/none.ts +56 -0
  53. package/src/shared/secrets/strategies.ts +202 -0
  54. package/src/shared/secrets/types.ts +1 -1
  55. package/src/shared/smoke/git-remote.ts +40 -0
  56. package/src/shared/stacks/manifest-commands.ts +1 -1
  57. package/src/shared/stacks/namespace.ts +178 -0
  58. package/src/shared/stacks/owners.ts +303 -0
  59. package/src/shared/stacks/render.ts +8 -3
  60. package/src/shared/stacks/runtime.ts +100 -16
  61. package/src/shared/stacks/status.ts +63 -12
  62. package/src/shared/strategies/registry.ts +89 -0
  63. package/src/shared/theme/contract.ts +538 -0
  64. package/src/shared/types/index.ts +12 -3
  65. package/src/ui/cli/create.ts +24 -7
  66. package/src/ui/cli/doctor-command.ts +131 -0
  67. package/src/ui/cli/edge-command.ts +56 -10
  68. package/src/ui/cli/flag-parser.ts +7 -2
  69. package/src/ui/cli/health-display.ts +25 -0
  70. package/src/ui/cli/index.ts +548 -60
  71. package/src/ui/cli/notice.ts +20 -0
  72. package/src/ui/cli/plugin-command.ts +849 -0
  73. package/src/ui/cli/stack-command.ts +63 -2
  74. package/src/ui/cli/test-runner.ts +11 -9
  75. package/src/ui/cli/theme-command.ts +136 -0
  76. package/src/ui/cli/web-command.ts +47 -23
  77. package/src/ui/mcp/api.ts +28 -2
  78. package/src/ui/mcp/scope.ts +22 -1
  79. package/src/ui/mcp/tools.ts +50 -7
  80. package/src/ui/server/appearance-prefs.ts +95 -0
  81. package/src/ui/server/daemon-reaper.ts +29 -1
  82. package/src/ui/server/engine-stamp.ts +83 -0
  83. package/src/ui/server/github-service.ts +23 -44
  84. package/src/ui/server/graph-page.ts +20 -0
  85. package/src/ui/server/graph-prefs.ts +44 -1
  86. package/src/ui/server/http-errors.ts +293 -0
  87. package/src/ui/server/http-helpers.ts +22 -3
  88. package/src/ui/server/index.ts +532 -51
  89. package/src/ui/server/public/appearance.js +531 -0
  90. package/src/ui/server/public/edge-page.js +248 -0
  91. package/src/ui/server/public/edge.html +251 -0
  92. package/src/ui/server/public/environments.html +65 -83
  93. package/src/ui/server/public/error-page.js +247 -0
  94. package/src/ui/server/public/error.html +260 -0
  95. package/src/ui/server/public/graph-model.js +332 -14
  96. package/src/ui/server/public/graph.css +141 -47
  97. package/src/ui/server/public/graph.js +223 -44
  98. package/src/ui/server/public/header.js +396 -600
  99. package/src/ui/server/public/health-fit.js +248 -0
  100. package/src/ui/server/public/health-rollup.js +128 -0
  101. package/src/ui/server/public/i18n/es.js +287 -0
  102. package/src/ui/server/public/i18n/ja.js +276 -0
  103. package/src/ui/server/public/index.html +139 -438
  104. package/src/ui/server/public/local-dev.html +3468 -4044
  105. package/src/ui/server/public/logs.html +71 -208
  106. package/src/ui/server/public/panel.js +14 -2
  107. package/src/ui/server/public/release.html +175 -389
  108. package/src/ui/server/public/services.html +596 -770
  109. package/src/ui/server/public/settings-page.js +759 -0
  110. package/src/ui/server/public/settings-registry.js +521 -0
  111. package/src/ui/server/public/settings.html +590 -0
  112. package/src/ui/server/public/shell.js +3869 -0
  113. package/src/ui/server/public/topbar.js +1413 -0
  114. package/src/ui/server/public/version-label.js +12 -0
  115. package/src/ui/server/public/workspace-picker.js +74 -34
  116. package/src/ui/server/readonly-routes.ts +12 -1
  117. package/src/ui/server/services-list.ts +94 -17
  118. package/src/ui/server/settings-distribution.ts +74 -0
  119. package/src/ui/server/shell-prefs.ts +192 -0
  120. package/src/ui/server/theme-routes.ts +292 -0
  121. package/src/ui/server/theme-runtime.ts +407 -0
  122. package/src/ui/server/ui-assets.ts +16 -1
  123. package/src/ui/server/ui-language.ts +50 -0
  124. package/src/ui/server/workspace-routes.ts +45 -3
  125. package/themes/den/den-theme.json +72 -0
  126. package/themes/den/favicon.ico +0 -0
  127. package/themes/den/mark.svg +6 -0
@@ -0,0 +1,521 @@
1
+ /**
2
+ * Den's own settings (R21, 0.3.0-dev.19): every setting Den has, as rows
3
+ * of the SETTINGS registry in shell.js (window.denShell.registerSetting).
4
+ * The Settings page (/settings, settings-page.js) draws only what the
5
+ * registry holds; no row is written anywhere else. A distribution or a
6
+ * plugin adds its own the same way (through window.denShellQueue).
7
+ *
8
+ * A category: { id, label, order, description?, from? }.
9
+ * A row: { id, category, label, description, keywords, scope, control,
10
+ * read(ctx), write?(value, ctx), keys? }
11
+ * scope "user" (config/local/user.yml), "workspace" (the workspace's
12
+ * entry in config/local/workspaces.yml) or "distribution" (the
13
+ * distribution's tracked config: read-only, so no write).
14
+ * control { type: seg | radio | select | number | toggle | langmap |
15
+ * theme | navlist | pins | workspace-editor | readonly |
16
+ * custom, … } (settings-page.js draws each)
17
+ * keys the /api/settings keys the row stores; every key POST
18
+ * /api/settings accepts has a row (settings-inventory.test.ts)
19
+ *
20
+ * ctx (settings-page.js): { data (GET /api/settings), appearance (the
21
+ * themes and your prefs), scope (GET /api/scope), workspace (the one the
22
+ * Workspace rows are about), wsSettings, shell (denShell), post(body),
23
+ * setAppearance(patch), saveWorkspace(body) }.
24
+ *
25
+ * Writes go only to per-user, gitignored state: POST /api/settings
26
+ * (config/local/user.yml) and POST /api/workspaces/settings
27
+ * (config/local/workspaces.yml). Nothing here writes the distribution's
28
+ * config.
29
+ */
30
+ import { themeNotice, themeOptions } from "./appearance.js";
31
+
32
+ export const DEN_SETTING_CATEGORIES = [
33
+ {
34
+ id: "appearance",
35
+ label: "Appearance",
36
+ order: 10,
37
+ description: "Mode follows your system unless you pick one.",
38
+ },
39
+ {
40
+ id: "editor",
41
+ label: "Editor & terminal",
42
+ order: 20,
43
+ description:
44
+ "Where ↗ opens a repository and >_ opens a terminal to a service.",
45
+ },
46
+ {
47
+ id: "navigation",
48
+ label: "Navigation",
49
+ order: 30,
50
+ description:
51
+ "Hide pages you never use and choose their order. Pins sit at the top of the rail.",
52
+ },
53
+ {
54
+ id: "graph",
55
+ label: "Graph",
56
+ order: 40,
57
+ description:
58
+ "How the graph is laid out, and how the Workspaces page shows its workspaces. The pages' own controls set these too.",
59
+ },
60
+ {
61
+ id: "workspace",
62
+ label: "Workspace",
63
+ order: 50,
64
+ description:
65
+ "Settings for one workspace. They follow the Workspace you view in the top bar.",
66
+ },
67
+ {
68
+ id: "cli",
69
+ label: "Command line",
70
+ order: 60,
71
+ description:
72
+ "Also yours (config/local/user.yml), set from the command line: __DEN_CLI__ config <key>=<value>.",
73
+ },
74
+ {
75
+ id: "distribution",
76
+ label: "Distribution",
77
+ order: 90,
78
+ description:
79
+ "What the distribution sets for everyone. Read-only here: it lives in the distribution's tracked config.",
80
+ },
81
+ ];
82
+
83
+ const seg = (options) => ({ type: "seg", options });
84
+ const graphRow = (key, label, description, keywords, control) => ({
85
+ id: `graph.${key}`,
86
+ category: "graph",
87
+ label,
88
+ description,
89
+ keywords,
90
+ scope: "user",
91
+ control,
92
+ keys: [`graph.${key}`],
93
+ read: (ctx) => ctx.data.graph?.[key],
94
+ write: (v, ctx) => ctx.post({ graph: { [key]: v } }),
95
+ });
96
+ const distRow = (id, label, description, keywords, value) => ({
97
+ id: `distribution.${id}`,
98
+ category: "distribution",
99
+ label,
100
+ description,
101
+ keywords: ["distribution", "org", ...keywords],
102
+ scope: "distribution",
103
+ control: { type: "readonly" },
104
+ read: (ctx) => value(ctx.data.distribution || {}),
105
+ });
106
+ const effective = (list, chosen) =>
107
+ chosen ?? (list || []).find((e) => e.available)?.key ?? null;
108
+
109
+ export const DEN_SETTINGS = [
110
+ // ── Appearance ─────────────────────────────────────────────────────────
111
+ {
112
+ id: "appearance.mode",
113
+ category: "appearance",
114
+ label: "Mode",
115
+ description: "System follows your computer's light or dark setting.",
116
+ keywords: ["dark", "light", "system", "color scheme", "theme"],
117
+ scope: "user",
118
+ control: seg([
119
+ { value: "system", label: "System" },
120
+ { value: "dark", label: "Dark" },
121
+ { value: "light", label: "Light" },
122
+ ]),
123
+ keys: ["appearance.mode"],
124
+ read: (ctx) => ctx.appearance.mode,
125
+ write: (v, ctx) => ctx.setAppearance({ mode: v }),
126
+ },
127
+ {
128
+ id: "appearance.theme",
129
+ category: "appearance",
130
+ label: "Theme",
131
+ description:
132
+ "Org default follows the distribution's theme. Picking a theme by name keeps it, even when the distribution changes its own.",
133
+ keywords: [
134
+ "theme",
135
+ "brand",
136
+ "colors",
137
+ "palette",
138
+ "swatches",
139
+ "org default",
140
+ ],
141
+ scope: "user",
142
+ control: {
143
+ type: "theme",
144
+ options: (ctx) =>
145
+ themeOptions(
146
+ ctx.appearance.themes,
147
+ ctx.appearance.prefsTheme,
148
+ ctx.appearance.orgDefault,
149
+ ),
150
+ notice: (ctx) =>
151
+ themeNotice(
152
+ ctx.appearance.themes,
153
+ ctx.appearance.prefsTheme,
154
+ ctx.appearance.active,
155
+ ),
156
+ },
157
+ keys: ["appearance.theme"],
158
+ read: (ctx) => ctx.appearance.prefsTheme || "",
159
+ write: (v, ctx) => ctx.setAppearance({ theme: v || null }),
160
+ },
161
+ // The UI's language (R19, R21; 0.3.0-dev.39): a user pref, `language`
162
+ // in user.yml. Each is named in itself. A change reloads the page, which
163
+ // the server then serves with that language's catalog.
164
+ {
165
+ id: "language",
166
+ category: "appearance",
167
+ label: "Language",
168
+ description:
169
+ "The language of Den's own words. Names, logs and command output stay as they are. Changing it reloads the page.",
170
+ keywords: [
171
+ "language",
172
+ "locale",
173
+ "translation",
174
+ "english",
175
+ "japanese",
176
+ "spanish",
177
+ "日本語",
178
+ "español",
179
+ ],
180
+ scope: "user",
181
+ control: {
182
+ type: "seg",
183
+ options: (ctx) =>
184
+ (ctx.shell?.languages?.() ?? []).map((l) => ({
185
+ value: l.id,
186
+ label: l.name,
187
+ })),
188
+ },
189
+ keys: ["language"],
190
+ read: (ctx) => ctx.data.language ?? ctx.shell?.lang?.() ?? "en",
191
+ write: (v, ctx) => ctx.shell.setLanguage(v),
192
+ },
193
+ {
194
+ id: "appearance.org-theme",
195
+ category: "appearance",
196
+ label: "Org default theme",
197
+ description: "The theme everyone gets until they pick one (brand.theme).",
198
+ keywords: ["theme", "brand", "org default", "distribution"],
199
+ scope: "distribution",
200
+ control: { type: "readonly" },
201
+ read: (ctx) => {
202
+ const d = ctx.data.distribution || {};
203
+ const org = ctx.appearance.orgDefault;
204
+ if (d.theme) return org ? `${org.name} (${d.theme})` : d.theme;
205
+ return org ? `${org.name} (not set: Den's own)` : "not set";
206
+ },
207
+ },
208
+
209
+ // ── Editor & terminal ──────────────────────────────────────────────────
210
+ {
211
+ id: "editor",
212
+ category: "editor",
213
+ label: "Default IDE",
214
+ description:
215
+ "__DEN_TITLE__ opens repositories in this editor when you click ↗.",
216
+ keywords: ["editor", "ide", "open", "code", "vscode", "cursor"],
217
+ scope: "user",
218
+ control: {
219
+ type: "radio",
220
+ options: (ctx) =>
221
+ (ctx.data.editors || []).map((e) => ({
222
+ value: e.key,
223
+ label: e.name,
224
+ disabled: !e.available,
225
+ tags: [
226
+ e.terminal ? "terminal" : null,
227
+ e.available ? null : "not found",
228
+ ],
229
+ })),
230
+ },
231
+ keys: ["editor"],
232
+ read: (ctx) => effective(ctx.data.editors, ctx.data.editor),
233
+ write: (v, ctx) => ctx.post({ editor: v || null }),
234
+ },
235
+ {
236
+ id: "editor_overrides",
237
+ category: "editor",
238
+ label: "Per-language IDE",
239
+ description:
240
+ "For repos whose stack.yml names a language, open this editor instead of the default.",
241
+ keywords: [
242
+ "language",
243
+ "override",
244
+ "editor",
245
+ "ide",
246
+ "go",
247
+ "python",
248
+ "stack",
249
+ ],
250
+ scope: "user",
251
+ control: {
252
+ type: "langmap",
253
+ languages: (ctx) => ctx.data.languages || [],
254
+ options: (ctx) =>
255
+ (ctx.data.editors || [])
256
+ .filter((e) => e.available)
257
+ .map((e) => ({ value: e.key, label: e.name })),
258
+ },
259
+ keys: ["editor_overrides"],
260
+ read: (ctx) => ctx.data.editor_overrides || {},
261
+ write: (map, ctx) =>
262
+ ctx.post({
263
+ editor_overrides: Object.keys(map).length > 0 ? map : null,
264
+ }),
265
+ },
266
+ {
267
+ id: "terminal",
268
+ category: "editor",
269
+ label: "Default terminal",
270
+ description:
271
+ "__DEN_TITLE__ opens a terminal to the service directory when you click >_.",
272
+ keywords: ["terminal", "shell", "iterm", "ghostty", "console"],
273
+ scope: "user",
274
+ control: {
275
+ type: "radio",
276
+ options: (ctx) =>
277
+ (ctx.data.terminals || []).map((t) => ({
278
+ value: t.key,
279
+ label: t.name,
280
+ disabled: !t.available,
281
+ tags: [t.available ? null : "not found"],
282
+ })),
283
+ },
284
+ keys: ["terminal"],
285
+ read: (ctx) => effective(ctx.data.terminals, ctx.data.terminal),
286
+ write: (v, ctx) => ctx.post({ terminal: v || null }),
287
+ },
288
+
289
+ // ── Navigation ─────────────────────────────────────────────────────────
290
+ {
291
+ id: "shell.pages",
292
+ category: "navigation",
293
+ label: "Pages",
294
+ description: "Show or hide each page, and move it within its group.",
295
+ keywords: ["rail", "sidebar", "hide", "order", "reorder", "pages", "menu"],
296
+ scope: "user",
297
+ control: { type: "navlist" },
298
+ keys: ["shell.hidden", "shell.order"],
299
+ read: (ctx) => {
300
+ const s = ctx.shell.getState();
301
+ return { hidden: s.hidden || [], order: s.order || [] };
302
+ },
303
+ write: (v, ctx) => ctx.shell.setNavPrefs(v),
304
+ },
305
+ {
306
+ id: "shell.pins",
307
+ category: "navigation",
308
+ label: "Pinned",
309
+ description:
310
+ "Services and repos at the top of the rail. Pin them from Services, the graph or the health row.",
311
+ keywords: ["pin", "pins", "pinned", "favorite", "rail"],
312
+ scope: "user",
313
+ control: { type: "pins" },
314
+ keys: ["shell.pins"],
315
+ read: (ctx) => ctx.shell.getState().pins || [],
316
+ write: (v, ctx) => ctx.shell.setPins(v),
317
+ },
318
+ {
319
+ id: "shell.collapsed",
320
+ category: "navigation",
321
+ label: "Collapse the rail",
322
+ description:
323
+ "Icons only. The rail's own button at the bottom does the same.",
324
+ keywords: ["rail", "collapse", "icons", "sidebar", "compact"],
325
+ scope: "user",
326
+ control: { type: "toggle" },
327
+ keys: ["shell.collapsed"],
328
+ read: (ctx) => ctx.shell.getState().collapsed === true,
329
+ write: (v, ctx) => ctx.shell.setCollapsed(v),
330
+ },
331
+ {
332
+ id: "shell.dock",
333
+ category: "navigation",
334
+ label: "Open the runs dock",
335
+ description:
336
+ "The output of runs, at the foot of every page. Off, it is just its bar: a run's tab appears and the Runs chip pulses, and you open it. Drag its top edge to resize it, up to half the window.",
337
+ keywords: ["runs", "dock", "output", "log", "panel", "console", "height"],
338
+ scope: "user",
339
+ control: { type: "toggle" },
340
+ keys: ["shell.dock", "shell.dockHeight"],
341
+ read: (ctx) => ctx.shell.getState().dock === true,
342
+ write: (v, ctx) => ctx.shell.setDock(v),
343
+ },
344
+
345
+ // ── Graph ──────────────────────────────────────────────────────────────
346
+ graphRow(
347
+ "workspaces",
348
+ "Workspace list",
349
+ "The Workspaces page's workspace list, open or collapsed to a strip so the table gets the width.",
350
+ ["workspaces", "list", "sidebar", "collapse", "strip", "local dev"],
351
+ seg([
352
+ { value: "open", label: "Open" },
353
+ { value: "collapsed", label: "Collapsed" },
354
+ ]),
355
+ ),
356
+ graphRow(
357
+ "groupBy",
358
+ "Group repos by",
359
+ "The Workspaces page's repo table: one list, or grouped by application (as __DEN_CLI__ apps) or by org.",
360
+ ["workspaces", "group", "application", "apps", "org", "table", "local dev"],
361
+ seg([
362
+ { value: "none", label: "None" },
363
+ { value: "app", label: "Application" },
364
+ { value: "org", label: "Org" },
365
+ ]),
366
+ ),
367
+ graphRow(
368
+ "direction",
369
+ "Direction",
370
+ "Lay the graph out top-down or left to right.",
371
+ ["layout", "direction", "orientation", "horizontal", "vertical"],
372
+ seg([
373
+ { value: "down", label: "Top-down" },
374
+ { value: "right", label: "Left to right" },
375
+ ]),
376
+ ),
377
+ graphRow(
378
+ "group",
379
+ "Frames",
380
+ "The outer frames: by org or by application.",
381
+ ["group", "frames", "org", "application", "cluster"],
382
+ seg([
383
+ { value: "org", label: "By org" },
384
+ { value: "app", label: "By application" },
385
+ ]),
386
+ ),
387
+ graphRow(
388
+ "density",
389
+ "Cards",
390
+ "Detailed cards show more; compact ones fit more on screen.",
391
+ ["density", "cards", "compact", "detailed", "size"],
392
+ seg([
393
+ { value: "detailed", label: "Detailed" },
394
+ { value: "compact", label: "Compact" },
395
+ ]),
396
+ ),
397
+ graphRow(
398
+ "panelWidth",
399
+ "Details panel",
400
+ "The width of the graph's details panel, in pixels (360 to 760).",
401
+ ["panel", "width", "details", "sidebar"],
402
+ { type: "number", min: 360, max: 760, step: 10, unit: "px" },
403
+ ),
404
+
405
+ // ── Workspace ──────────────────────────────────────────────────────────
406
+ {
407
+ id: "workspace.editor",
408
+ category: "workspace",
409
+ label: "IDE for this workspace",
410
+ description:
411
+ "Opening this workspace, or a repo in it without a per-language override, uses this editor instead of your default.",
412
+ keywords: ["workspace", "editor", "ide", "override"],
413
+ scope: "workspace",
414
+ control: { type: "workspace-editor" },
415
+ read: (ctx) => ctx.wsSettings?.editor ?? "",
416
+ write: (v, ctx) => ctx.saveWorkspace({ editor: v || null }),
417
+ },
418
+
419
+ // ── Command line ───────────────────────────────────────────────────────
420
+ {
421
+ id: "cli.local_dev",
422
+ category: "cli",
423
+ label: "Local environment",
424
+ description:
425
+ "Off hides Workspaces and the workspace APIs for you. __DEN_CLI__ config local_dev=false",
426
+ keywords: ["local", "local dev", "environment", "disable"],
427
+ scope: "user",
428
+ control: { type: "readonly" },
429
+ read: (ctx) => (ctx.data.local_dev === false ? "Off" : "On"),
430
+ },
431
+ {
432
+ id: "cli.cli_tabs",
433
+ category: "cli",
434
+ label: "Terminal tabs",
435
+ description:
436
+ "Whether up and restart open terminal tabs by default. __DEN_CLI__ config cli_tabs=true",
437
+ keywords: ["tabs", "terminal", "up", "restart", "cli"],
438
+ scope: "user",
439
+ control: { type: "readonly" },
440
+ read: (ctx) => (ctx.data.cli_tabs ? "On" : "Off"),
441
+ },
442
+ {
443
+ id: "cli.log_window_lines",
444
+ category: "cli",
445
+ label: "Log pane lines",
446
+ description:
447
+ "How many lines a Logs pane keeps loaded; full-history search is unaffected. __DEN_CLI__ config log_window_lines=50000",
448
+ keywords: ["logs", "lines", "window", "history", "buffer"],
449
+ scope: "user",
450
+ control: { type: "readonly" },
451
+ read: (ctx) =>
452
+ `${Number(ctx.data.log_window_lines ?? 20000).toLocaleString()} lines`,
453
+ },
454
+ {
455
+ id: "cli.web_port",
456
+ category: "cli",
457
+ label: "Your web port",
458
+ description:
459
+ "web.port in config/local/user.yml overrides the distribution's. It applies when the web server next starts.",
460
+ keywords: ["port", "web", "server", "url"],
461
+ scope: "user",
462
+ control: { type: "readonly" },
463
+ read: (ctx) =>
464
+ ctx.data.user_web_port != null
465
+ ? String(ctx.data.user_web_port)
466
+ : "Not set: the distribution's applies",
467
+ },
468
+
469
+ // ── Distribution (read-only) ───────────────────────────────────────────
470
+ distRow(
471
+ "brand",
472
+ "Product name",
473
+ "brand.name: the name on pages, the CLI banner and help.",
474
+ ["brand", "name", "product", "title"],
475
+ (d) => d.brand || "not set",
476
+ ),
477
+ distRow(
478
+ "github_org",
479
+ "GitHub org",
480
+ "github_org: the org whose repos Den clones and lists.",
481
+ ["github", "repos", "clone"],
482
+ (d) => d.github_org || "not set",
483
+ ),
484
+ distRow(
485
+ "orgs",
486
+ "Org labels",
487
+ "orgs: display names for the GitHub orgs repos belong to.",
488
+ ["orgs", "labels", "names"],
489
+ (d) => {
490
+ const e = Object.entries(d.orgs || {});
491
+ return e.length ? e.map(([id, l]) => `${id}: ${l}`).join(", ") : "none";
492
+ },
493
+ ),
494
+ distRow(
495
+ "web_port",
496
+ "Web port",
497
+ "web.port: the port the web UI listens on, unless yours overrides it.",
498
+ ["port", "web", "server", "url"],
499
+ (d) => (d.web_port != null ? String(d.web_port) : "not set (7777)"),
500
+ ),
501
+ distRow(
502
+ "environments",
503
+ "Environments",
504
+ "environments: the envs the Env menu offers.",
505
+ ["environments", "env", "local", "dev", "prod"],
506
+ (d) => (d.environments ? d.environments.join(", ") : "not set: all"),
507
+ ),
508
+ distRow(
509
+ "domain",
510
+ "Edge domain",
511
+ "edge.domain: services are served at https://<service>.<domain>.",
512
+ ["edge", "domain", "https", "routes"],
513
+ (d) => d.domain || "not set",
514
+ ),
515
+ ];
516
+
517
+ /** Register Den's categories and rows with the shell's SETTINGS registry. */
518
+ export function registerDenSettings(shell) {
519
+ for (const c of DEN_SETTING_CATEGORIES) shell.registerSettingsCategory(c);
520
+ for (const r of DEN_SETTINGS) shell.registerSetting(r);
521
+ }