@monster-orange/den 0.3.0 → 0.4.1
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 +593 -0
- package/docs/distributions.md +429 -18
- package/package.json +6 -1
- package/src/shared/actor-token.ts +275 -0
- package/src/shared/agent-actor.ts +12 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +271 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +199 -0
- package/src/shared/agents/runtimes.ts +54 -0
- package/src/shared/den-config.ts +116 -0
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/scope.ts +22 -0
- package/src/shared/environment-manager/service-runner.ts +94 -2
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/profile.ts +15 -4
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/sessions/client.ts +284 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +89 -0
- package/src/shared/sessions/host.ts +690 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/web-secret.ts +181 -0
- package/src/shared/web-session.ts +491 -0
- package/src/ui/cli/agent-ops.ts +843 -0
- package/src/ui/cli/agents-command.ts +294 -0
- package/src/ui/cli/doctor-command.ts +138 -9
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +32 -3
- package/src/ui/cli/index.ts +128 -12
- package/src/ui/cli/sessions-command.ts +750 -0
- package/src/ui/cli/target-workspace.ts +105 -0
- package/src/ui/cli/web-command.ts +536 -14
- package/src/ui/mcp/api.ts +7 -0
- package/src/ui/mcp/scope.ts +123 -19
- package/src/ui/mcp/server.ts +100 -7
- package/src/ui/mcp/tools.ts +44 -5
- package/src/ui/server/agent-routes.ts +323 -0
- package/src/ui/server/audit.ts +111 -22
- package/src/ui/server/http-errors.ts +21 -3
- package/src/ui/server/index.ts +820 -65
- package/src/ui/server/public/agents-page.js +648 -0
- package/src/ui/server/public/agents-section.js +299 -0
- package/src/ui/server/public/agents.html +439 -0
- package/src/ui/server/public/appearance.js +3 -0
- package/src/ui/server/public/error-page.js +103 -0
- package/src/ui/server/public/header.js +45 -11
- package/src/ui/server/public/i18n/es.js +90 -1
- package/src/ui/server/public/i18n/ja.js +86 -1
- package/src/ui/server/public/local-dev.html +53 -0
- package/src/ui/server/public/logs.html +3 -0
- package/src/ui/server/public/panel.js +1 -1
- package/src/ui/server/public/release.html +8 -0
- package/src/ui/server/public/settings-page.js +5 -0
- package/src/ui/server/public/settings-registry.js +33 -0
- package/src/ui/server/public/shell.js +248 -5
- package/src/ui/server/public/topbar.js +51 -2
- package/src/ui/server/readonly-routes.ts +25 -2
- package/src/ui/server/request-guard.ts +670 -0
- package/src/ui/server/route-classes.ts +317 -0
- package/src/ui/server/session-routes.ts +445 -0
- package/src/ui/server/shutdown-guard.ts +91 -0
- package/src/ui/server/sudo-store.ts +29 -3
- package/src/ui/server/ui-assets.ts +10 -1
- package/src/ui/server/websocket.ts +326 -0
- package/src/ui/server/workspace-routes.ts +16 -22
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<script>
|
|
7
|
+
// Mode before first paint: yours (the server fills it in below),
|
|
8
|
+
// else the system's, else the theme's default, else dark.
|
|
9
|
+
(function () {
|
|
10
|
+
var m = "@@DEN_MODE@@";
|
|
11
|
+
if (m !== "dark" && m !== "light") {
|
|
12
|
+
var q = function (s) {
|
|
13
|
+
return (
|
|
14
|
+
window.matchMedia &&
|
|
15
|
+
matchMedia("(prefers-color-scheme: " + s + ")").matches
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
m = q("light")
|
|
19
|
+
? "light"
|
|
20
|
+
: q("dark")
|
|
21
|
+
? "dark"
|
|
22
|
+
: "@@DEN_DEFAULT_MODE@@";
|
|
23
|
+
}
|
|
24
|
+
document.documentElement.dataset.mode =
|
|
25
|
+
m === "light" ? "light" : "dark";
|
|
26
|
+
})();
|
|
27
|
+
</script>
|
|
28
|
+
<link rel="stylesheet" href="/theme.css" id="theme-css" />
|
|
29
|
+
<link rel="stylesheet" href="/vendor/xterm.css" />
|
|
30
|
+
<title>__DEN_TITLE__ · Agents</title>
|
|
31
|
+
<style>
|
|
32
|
+
/* The Agents tab (AG-4a-term, AG spec §5d): the prototype's
|
|
33
|
+
(den-shell.html) page header (.ph), list column (.agl, 300px) and
|
|
34
|
+
panes (.agp: 1, 2 side by side, 3-4 as 2x2), ported as they are;
|
|
35
|
+
--r is --radius, --r-btn is --radius-button. */
|
|
36
|
+
*,
|
|
37
|
+
*::before,
|
|
38
|
+
*::after {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
}
|
|
41
|
+
html,
|
|
42
|
+
body {
|
|
43
|
+
height: 100%;
|
|
44
|
+
margin: 0;
|
|
45
|
+
background: var(--bg);
|
|
46
|
+
color: var(--text);
|
|
47
|
+
}
|
|
48
|
+
body {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
html body.den-has-shell > #den-content {
|
|
54
|
+
overflow: auto;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
padding: 20px 24px 32px 24px;
|
|
57
|
+
font: 14px/1.45 var(--sans);
|
|
58
|
+
color: var(--text);
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
#den-content :focus-visible {
|
|
62
|
+
outline: 2px solid var(--accent);
|
|
63
|
+
outline-offset: 2px;
|
|
64
|
+
}
|
|
65
|
+
.ph {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: flex-end;
|
|
68
|
+
gap: 16px;
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
margin-bottom: 16px;
|
|
71
|
+
}
|
|
72
|
+
.ph h1 {
|
|
73
|
+
font: 600 22px/1.2 var(--display);
|
|
74
|
+
margin: 0;
|
|
75
|
+
text-wrap: balance;
|
|
76
|
+
}
|
|
77
|
+
.ph .sub {
|
|
78
|
+
color: var(--muted);
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
margin-top: 4px;
|
|
81
|
+
overflow-wrap: anywhere;
|
|
82
|
+
}
|
|
83
|
+
.ph .act {
|
|
84
|
+
margin-left: auto;
|
|
85
|
+
display: flex;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
}
|
|
88
|
+
.mono {
|
|
89
|
+
font-family: var(--mono);
|
|
90
|
+
}
|
|
91
|
+
.agx {
|
|
92
|
+
display: grid;
|
|
93
|
+
grid-template-columns: 300px minmax(0, 1fr);
|
|
94
|
+
gap: 14px;
|
|
95
|
+
min-height: 420px;
|
|
96
|
+
height: calc(100vh - 300px);
|
|
97
|
+
}
|
|
98
|
+
.agl {
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: var(--radius);
|
|
101
|
+
background: var(--surface);
|
|
102
|
+
overflow: auto;
|
|
103
|
+
min-height: 0;
|
|
104
|
+
}
|
|
105
|
+
.agl .wsh {
|
|
106
|
+
font: 11px var(--mono);
|
|
107
|
+
text-transform: uppercase;
|
|
108
|
+
letter-spacing: 0.07em;
|
|
109
|
+
color: var(--faint);
|
|
110
|
+
padding: 10px 12px 4px;
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
}
|
|
114
|
+
.agr {
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
117
|
+
gap: 2px 8px;
|
|
118
|
+
align-items: center;
|
|
119
|
+
width: 100%;
|
|
120
|
+
text-align: left;
|
|
121
|
+
border: 0;
|
|
122
|
+
border-left: 2px solid transparent;
|
|
123
|
+
background: none;
|
|
124
|
+
padding: 7px 12px;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
color: var(--text);
|
|
127
|
+
font: inherit;
|
|
128
|
+
}
|
|
129
|
+
.agr:hover {
|
|
130
|
+
background: var(--sunken);
|
|
131
|
+
}
|
|
132
|
+
.agr[aria-pressed="true"] {
|
|
133
|
+
background: var(--sunken);
|
|
134
|
+
border-left-color: var(--accent);
|
|
135
|
+
}
|
|
136
|
+
.agr .nm {
|
|
137
|
+
font-weight: 500;
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
text-overflow: ellipsis;
|
|
141
|
+
}
|
|
142
|
+
.agr .nm .id {
|
|
143
|
+
font-weight: 400;
|
|
144
|
+
color: var(--faint);
|
|
145
|
+
}
|
|
146
|
+
.agr .st {
|
|
147
|
+
font: 12px var(--mono);
|
|
148
|
+
color: var(--faint);
|
|
149
|
+
}
|
|
150
|
+
.agr .agtags {
|
|
151
|
+
grid-column: 2 / 4;
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-wrap: wrap;
|
|
154
|
+
gap: 4px;
|
|
155
|
+
align-items: center;
|
|
156
|
+
margin-top: 2px;
|
|
157
|
+
}
|
|
158
|
+
.agr .meta {
|
|
159
|
+
grid-column: 2 / 4;
|
|
160
|
+
font: 11.5px var(--mono);
|
|
161
|
+
color: var(--faint);
|
|
162
|
+
white-space: nowrap;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
text-overflow: ellipsis;
|
|
165
|
+
}
|
|
166
|
+
.wbdg {
|
|
167
|
+
font: 10.5px var(--mono);
|
|
168
|
+
color: var(--warn);
|
|
169
|
+
border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
|
|
170
|
+
border-radius: 999px;
|
|
171
|
+
padding: 0 6px;
|
|
172
|
+
}
|
|
173
|
+
.sbdg,
|
|
174
|
+
.tagx {
|
|
175
|
+
font: 10.5px var(--mono);
|
|
176
|
+
color: var(--faint);
|
|
177
|
+
border: 1px dashed var(--border-strong);
|
|
178
|
+
border-radius: 4px;
|
|
179
|
+
padding: 0 4px;
|
|
180
|
+
font-weight: 400;
|
|
181
|
+
}
|
|
182
|
+
.tagx {
|
|
183
|
+
font-size: 10px;
|
|
184
|
+
}
|
|
185
|
+
.tagx.warn {
|
|
186
|
+
color: var(--warn);
|
|
187
|
+
border-color: color-mix(in srgb, var(--warn) 55%, transparent);
|
|
188
|
+
}
|
|
189
|
+
.agp {
|
|
190
|
+
display: grid;
|
|
191
|
+
gap: 10px;
|
|
192
|
+
min-height: 0;
|
|
193
|
+
}
|
|
194
|
+
.agp.n1 {
|
|
195
|
+
grid-template-columns: minmax(0, 1fr);
|
|
196
|
+
grid-template-rows: minmax(0, 1fr);
|
|
197
|
+
}
|
|
198
|
+
.agp.n2 {
|
|
199
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
200
|
+
grid-template-rows: minmax(0, 1fr);
|
|
201
|
+
}
|
|
202
|
+
.agp.n3,
|
|
203
|
+
.agp.n4 {
|
|
204
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
205
|
+
grid-auto-rows: minmax(0, 1fr);
|
|
206
|
+
}
|
|
207
|
+
.pane {
|
|
208
|
+
display: grid;
|
|
209
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
210
|
+
border: 1px solid var(--border);
|
|
211
|
+
border-radius: var(--radius);
|
|
212
|
+
background: var(--sunken);
|
|
213
|
+
min-height: 0;
|
|
214
|
+
min-width: 0;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
}
|
|
217
|
+
.pane .bar2 {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 8px;
|
|
221
|
+
padding: 6px 10px;
|
|
222
|
+
border-bottom: 1px solid var(--border);
|
|
223
|
+
background: var(--surface);
|
|
224
|
+
font-size: 13px;
|
|
225
|
+
min-width: 0;
|
|
226
|
+
}
|
|
227
|
+
.pane .bar2 b {
|
|
228
|
+
white-space: nowrap;
|
|
229
|
+
overflow: hidden;
|
|
230
|
+
text-overflow: ellipsis;
|
|
231
|
+
min-width: 0;
|
|
232
|
+
flex: 0 0 auto;
|
|
233
|
+
max-width: 45%;
|
|
234
|
+
}
|
|
235
|
+
.pane .bar2 .tagx {
|
|
236
|
+
flex: none;
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
}
|
|
239
|
+
.pane .bar2 .where {
|
|
240
|
+
flex: 0 1 auto;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
color: var(--faint);
|
|
243
|
+
white-space: nowrap;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
text-overflow: ellipsis;
|
|
246
|
+
font-size: 12.5px;
|
|
247
|
+
}
|
|
248
|
+
.pane .bar2 .x {
|
|
249
|
+
margin-left: auto;
|
|
250
|
+
display: flex;
|
|
251
|
+
gap: 6px;
|
|
252
|
+
flex: none;
|
|
253
|
+
}
|
|
254
|
+
/* The body: mono 12px/1.5 (xterm draws inside it). */
|
|
255
|
+
.pane .term {
|
|
256
|
+
min-height: 0;
|
|
257
|
+
min-width: 0;
|
|
258
|
+
padding: 6px 4px 4px 8px;
|
|
259
|
+
font: 12px/1.5 var(--mono);
|
|
260
|
+
color: var(--text);
|
|
261
|
+
overflow: hidden;
|
|
262
|
+
}
|
|
263
|
+
.pane .term .xterm {
|
|
264
|
+
height: 100%;
|
|
265
|
+
}
|
|
266
|
+
/* xterm.css paints its viewport black: the pane's colour instead. */
|
|
267
|
+
.pane .term .xterm .xterm-viewport {
|
|
268
|
+
background-color: var(--sunken);
|
|
269
|
+
}
|
|
270
|
+
.pane .note {
|
|
271
|
+
margin: 0;
|
|
272
|
+
padding: 10px 12px;
|
|
273
|
+
font: 12px/1.5 var(--mono);
|
|
274
|
+
color: var(--muted);
|
|
275
|
+
white-space: pre-wrap;
|
|
276
|
+
}
|
|
277
|
+
.agempty {
|
|
278
|
+
border: 1px dashed var(--border-strong);
|
|
279
|
+
border-radius: var(--radius);
|
|
280
|
+
display: grid;
|
|
281
|
+
place-items: center;
|
|
282
|
+
color: var(--muted);
|
|
283
|
+
padding: 28px;
|
|
284
|
+
text-align: center;
|
|
285
|
+
}
|
|
286
|
+
.signin,
|
|
287
|
+
.agnote {
|
|
288
|
+
margin: 8px 12px;
|
|
289
|
+
padding: 8px 10px;
|
|
290
|
+
border: 1px dashed var(--border-strong);
|
|
291
|
+
border-radius: var(--radius-button);
|
|
292
|
+
font-size: 12.5px;
|
|
293
|
+
color: var(--muted);
|
|
294
|
+
}
|
|
295
|
+
.agl .none {
|
|
296
|
+
padding: 12px;
|
|
297
|
+
color: var(--muted);
|
|
298
|
+
font-size: 12.5px;
|
|
299
|
+
}
|
|
300
|
+
dialog.ag-dlg {
|
|
301
|
+
width: min(440px, calc(100vw - 32px));
|
|
302
|
+
background: var(--raised);
|
|
303
|
+
color: var(--text);
|
|
304
|
+
border: 1px solid var(--border-strong);
|
|
305
|
+
border-radius: 12px;
|
|
306
|
+
box-shadow: var(--shadow);
|
|
307
|
+
padding: 18px 18px 14px;
|
|
308
|
+
}
|
|
309
|
+
dialog.ag-dlg::backdrop {
|
|
310
|
+
background: rgb(0 0 0 / 0.35);
|
|
311
|
+
}
|
|
312
|
+
.ag-dlg form {
|
|
313
|
+
display: grid;
|
|
314
|
+
gap: 12px;
|
|
315
|
+
}
|
|
316
|
+
.ag-dlg h2 {
|
|
317
|
+
margin: 0;
|
|
318
|
+
font: 600 17px var(--display);
|
|
319
|
+
}
|
|
320
|
+
.ag-dlg label {
|
|
321
|
+
display: grid;
|
|
322
|
+
gap: 6px;
|
|
323
|
+
font: 11px var(--mono);
|
|
324
|
+
color: var(--faint);
|
|
325
|
+
text-transform: uppercase;
|
|
326
|
+
letter-spacing: 0.07em;
|
|
327
|
+
}
|
|
328
|
+
.ag-dlg select {
|
|
329
|
+
font: 13px var(--sans);
|
|
330
|
+
text-transform: none;
|
|
331
|
+
letter-spacing: 0;
|
|
332
|
+
border: 1px solid var(--border);
|
|
333
|
+
background: var(--bg);
|
|
334
|
+
color: var(--text);
|
|
335
|
+
border-radius: var(--radius-button);
|
|
336
|
+
padding: 6px 8px;
|
|
337
|
+
}
|
|
338
|
+
.ag-dlg .btns {
|
|
339
|
+
display: flex;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
justify-content: flex-end;
|
|
342
|
+
}
|
|
343
|
+
@media (max-width: 860px) {
|
|
344
|
+
.agx {
|
|
345
|
+
grid-template-columns: minmax(0, 1fr);
|
|
346
|
+
height: auto;
|
|
347
|
+
}
|
|
348
|
+
.ph .act {
|
|
349
|
+
flex-wrap: wrap;
|
|
350
|
+
}
|
|
351
|
+
.agp.n2,
|
|
352
|
+
.agp.n3,
|
|
353
|
+
.agp.n4 {
|
|
354
|
+
grid-template-columns: minmax(0, 1fr);
|
|
355
|
+
}
|
|
356
|
+
.pane {
|
|
357
|
+
min-height: 260px;
|
|
358
|
+
}
|
|
359
|
+
.agl {
|
|
360
|
+
max-height: 50vh;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
</style>
|
|
364
|
+
</head>
|
|
365
|
+
<body>
|
|
366
|
+
<!-- The shell: top bar and rail, the same on every page (shell.js). -->
|
|
367
|
+
<div id="den-shell" data-page="agents"></div>
|
|
368
|
+
<script src="/shell.js"></script>
|
|
369
|
+
|
|
370
|
+
<!-- The page's one main landmark: everything after the shell. -->
|
|
371
|
+
<main id="den-content">
|
|
372
|
+
<div class="ph">
|
|
373
|
+
<div>
|
|
374
|
+
<h1 id="ag-h">Agents</h1>
|
|
375
|
+
<div class="sub" id="ag-sub">
|
|
376
|
+
<span id="ag-count"></span> ·
|
|
377
|
+
<span class="mono" id="ag-url">/agents</span>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
<div class="act">
|
|
381
|
+
<button type="button" class="den-btn primary" id="ag-start">
|
|
382
|
+
+ Start agent
|
|
383
|
+
</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<div class="agx" id="agx">
|
|
387
|
+
<div
|
|
388
|
+
class="agl"
|
|
389
|
+
id="agl"
|
|
390
|
+
role="region"
|
|
391
|
+
aria-labelledby="ag-h"
|
|
392
|
+
aria-busy="true"
|
|
393
|
+
></div>
|
|
394
|
+
<div id="agr" class="agempty"></div>
|
|
395
|
+
</div>
|
|
396
|
+
<dialog class="ag-dlg" id="ag-dlg" aria-labelledby="ag-dlg-h">
|
|
397
|
+
<form method="dialog" id="ag-form">
|
|
398
|
+
<h2 id="ag-dlg-h">Start an agent</h2>
|
|
399
|
+
<label id="ag-ws-l"
|
|
400
|
+
><span id="ag-ws-t">Workspace</span>
|
|
401
|
+
<select name="ws" id="ag-ws"></select
|
|
402
|
+
></label>
|
|
403
|
+
<label
|
|
404
|
+
><span id="ag-p-t">Persona</span>
|
|
405
|
+
<select name="persona" id="ag-persona" required></select
|
|
406
|
+
></label>
|
|
407
|
+
<label
|
|
408
|
+
><span id="ag-r-t">Runtime</span>
|
|
409
|
+
<select name="runtime" id="ag-runtime"></select
|
|
410
|
+
></label>
|
|
411
|
+
<div class="btns">
|
|
412
|
+
<button type="button" class="den-btn" id="ag-cancel">Cancel</button>
|
|
413
|
+
<button type="submit" class="den-btn primary" id="ag-go">
|
|
414
|
+
Start
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
417
|
+
</form>
|
|
418
|
+
</dialog>
|
|
419
|
+
|
|
420
|
+
<script type="module">
|
|
421
|
+
import {
|
|
422
|
+
applyDenConfig,
|
|
423
|
+
initChangelog,
|
|
424
|
+
initHealth,
|
|
425
|
+
initLogBadge,
|
|
426
|
+
initWorkspacePill,
|
|
427
|
+
} from "/header.js";
|
|
428
|
+
import { mountAgentsPage } from "/agents-page.js";
|
|
429
|
+
void Promise.all([
|
|
430
|
+
initHealth(),
|
|
431
|
+
initWorkspacePill(),
|
|
432
|
+
applyDenConfig(),
|
|
433
|
+
initChangelog(),
|
|
434
|
+
]).then(initLogBadge);
|
|
435
|
+
void mountAgentsPage(document.getElementById("den-content"));
|
|
436
|
+
</script>
|
|
437
|
+
</main>
|
|
438
|
+
</body>
|
|
439
|
+
</html>
|
|
@@ -513,6 +513,9 @@ let inited = false;
|
|
|
513
513
|
export async function initAppearance() {
|
|
514
514
|
if (inited || typeof document === "undefined") return;
|
|
515
515
|
inited = true;
|
|
516
|
+
// A refusal page (SEC-1b): /api/themes answers the refusal; the page
|
|
517
|
+
// keeps its first paint.
|
|
518
|
+
if (window.denShell?.apiOff?.() === true) return;
|
|
516
519
|
const mq = window.matchMedia?.("(prefers-color-scheme: light)");
|
|
517
520
|
mq?.addEventListener?.("change", () => {
|
|
518
521
|
if (st.mode === "system") applyMode();
|
|
@@ -121,6 +121,77 @@ async function copy(text, btn, t) {
|
|
|
121
121
|
setTimeout(() => (btn.textContent = label), 1500);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/** `text` with `{cmd}` as inline code (the rest escaped). */
|
|
125
|
+
function withCmd(text, cmd) {
|
|
126
|
+
return esc(text).replace("\u0000", `<code class="mono">${esc(cmd)}</code>`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The signed-out page (SEC-1b, SEC-1 spec §2): "Sign in to use the
|
|
131
|
+
* terminal" (a shell-grade route on loopback) or "Sign in to use <name>"
|
|
132
|
+
* (beyond loopback), the command that prints a sign-in link with a copy
|
|
133
|
+
* button, and on 127.0.0.1 or [::1] a link to the same page on localhost,
|
|
134
|
+
* where the cookie is. It never asks for a secret or a token.
|
|
135
|
+
*
|
|
136
|
+
* A link from 127.0.0.1 lands here once without the cookie (a cross-site
|
|
137
|
+
* navigation doesn't carry a SameSite=Strict one); /api/session, asked
|
|
138
|
+
* same-origin, does carry it, so a signed-in browser reloads the page
|
|
139
|
+
* from here (same-origin) and is let through.
|
|
140
|
+
*/
|
|
141
|
+
function mountSignedOut(root, sh, d) {
|
|
142
|
+
const t = sh.t;
|
|
143
|
+
const s = d.signin;
|
|
144
|
+
const title = s.expired
|
|
145
|
+
? t("err.signin.expiredTitle")
|
|
146
|
+
: s.scope === "terminal"
|
|
147
|
+
? t("err.signin.terminal")
|
|
148
|
+
: t("err.signin.title", { name: s.name || "Den" });
|
|
149
|
+
document.title = document.title.replace(/Error$/, title);
|
|
150
|
+
const lead = s.expired
|
|
151
|
+
? withCmd(t("err.signin.expired", { cmd: "\u0000" }), s.cmd)
|
|
152
|
+
: withCmd(t("err.signin.lead", { cmd: "\u0000" }), s.cmd);
|
|
153
|
+
root.innerHTML =
|
|
154
|
+
`<div class="ph"><div><h1>${esc(title)}</h1>` +
|
|
155
|
+
`<div class="sub">${esc(t("err.signin.sub"))}</div></div></div>` +
|
|
156
|
+
`<div class="empty" id="den-signin"><p class="lead">${lead}</p>` +
|
|
157
|
+
`<pre class="code" id="den-signin-cmd">${esc(s.cmd)}</pre>` +
|
|
158
|
+
(s.localhost
|
|
159
|
+
? `<p>${esc(t("err.signin.local", { host: location.host }))}</p>`
|
|
160
|
+
: "") +
|
|
161
|
+
`<p class="den-signin-why">${esc(t("err.signin.why"))}</p>` +
|
|
162
|
+
`<dl class="facts">` +
|
|
163
|
+
`<dt>${esc(t("err.500.id"))}</dt><dd class="mono" id="den-err-id">${esc(d.requestId)}</dd>` +
|
|
164
|
+
`</dl><div class="acts">` +
|
|
165
|
+
(s.localhost
|
|
166
|
+
? `<a class="btn primary" id="den-signin-local" href="${esc(s.localhost)}">${esc(t("err.421.open"))}</a>`
|
|
167
|
+
: "") +
|
|
168
|
+
`<button type="button" class="btn${s.localhost ? "" : " primary"}" id="den-signin-copy">${esc(t("err.signin.copy"))}</button>` +
|
|
169
|
+
`</div></div>`;
|
|
170
|
+
const btn = root.querySelector("#den-signin-copy");
|
|
171
|
+
btn?.addEventListener("click", () => copy(s.cmd, btn, t));
|
|
172
|
+
if (s.expired || s.localhost) return;
|
|
173
|
+
let tried = false;
|
|
174
|
+
try {
|
|
175
|
+
tried = sessionStorage.getItem("den-signin-reload") === location.href;
|
|
176
|
+
sessionStorage.removeItem("den-signin-reload");
|
|
177
|
+
} catch {
|
|
178
|
+
/* no storage: try once */
|
|
179
|
+
}
|
|
180
|
+
if (tried) return;
|
|
181
|
+
fetch("/api/session", { cache: "no-store", credentials: "same-origin" })
|
|
182
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
183
|
+
.then((b) => {
|
|
184
|
+
if (!b || b.signedIn !== true) return;
|
|
185
|
+
try {
|
|
186
|
+
sessionStorage.setItem("den-signin-reload", location.href);
|
|
187
|
+
} catch {
|
|
188
|
+
/* none */
|
|
189
|
+
}
|
|
190
|
+
location.replace(location.href);
|
|
191
|
+
})
|
|
192
|
+
.catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
|
|
124
195
|
export function mountErrorPage(root) {
|
|
125
196
|
const sh = window.denShell;
|
|
126
197
|
const d = readData();
|
|
@@ -219,6 +290,38 @@ export function mountErrorPage(root) {
|
|
|
219
290
|
return;
|
|
220
291
|
}
|
|
221
292
|
|
|
293
|
+
if (d.status === 401 && d.code === "signin_required" && d.signin) {
|
|
294
|
+
mountSignedOut(root, sh, d);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (d.status === 421 || d.status === 403 || d.status === 401) {
|
|
299
|
+
// SEC-1a: the request checks refused it (a host that isn't this
|
|
300
|
+
// server's, a cross-site request). A 421 links to the same path on
|
|
301
|
+
// localhost, the canonical loopback name.
|
|
302
|
+
const title =
|
|
303
|
+
d.status === 421 ? t("err.421.title") : t("err.refused.title");
|
|
304
|
+
document.title = document.title.replace(/Error$/, title);
|
|
305
|
+
const here =
|
|
306
|
+
d.status === 421
|
|
307
|
+
? `http://localhost${location.port ? `:${location.port}` : ""}${d.path ?? "/"}`
|
|
308
|
+
: null;
|
|
309
|
+
root.innerHTML =
|
|
310
|
+
header(title, `<span class="mono">${esc(d.code ?? "")}</span>`) +
|
|
311
|
+
`<div class="empty"><p class="lead">${esc(d.status === 421 ? t("err.421.lead") : (d.message ?? ""))}</p>` +
|
|
312
|
+
`<dl class="facts">` +
|
|
313
|
+
`<dt>${esc(t("err.500.id"))}</dt><dd class="mono" id="den-err-id">${esc(d.requestId)}</dd>` +
|
|
314
|
+
`</dl><div class="acts">` +
|
|
315
|
+
(here
|
|
316
|
+
? `<a class="btn primary" id="den-err-local" href="${esc(here)}">${esc(t("err.421.open"))}</a>`
|
|
317
|
+
: `<button type="button" class="btn primary" id="den-err-retry">${esc(t("err.retry"))}</button>`) +
|
|
318
|
+
`</div></div>`;
|
|
319
|
+
root
|
|
320
|
+
.querySelector("#den-err-retry")
|
|
321
|
+
?.addEventListener("click", () => location.reload());
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
222
325
|
document.title = document.title.replace(/Error$/, t("err.500.title"));
|
|
223
326
|
const what = `${d.method ?? "GET"} ${d.route ?? ""}`;
|
|
224
327
|
const when = new Date(d.time);
|