daimon 0.4.3 → 0.6.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.
- package/CHANGELOG.md +231 -0
- package/dist/cli.js +71 -58
- package/dist/dashboard/3rdpartylicenses.txt +461 -0
- package/dist/dashboard/browser/chunk-4VNZXWQZ.js +3 -0
- package/dist/dashboard/browser/chunk-4ZR4NTJW.js +1 -0
- package/dist/dashboard/browser/chunk-7L4DWDGL.js +4 -0
- package/dist/dashboard/browser/chunk-BLNPJ4WD.js +4 -0
- package/dist/dashboard/browser/chunk-E3HUMJ6S.js +6 -0
- package/dist/dashboard/browser/chunk-EBOHC6KX.js +1 -0
- package/dist/dashboard/browser/chunk-ESDYJ3BP.js +1 -0
- package/dist/dashboard/browser/chunk-JUZHAE4L.js +2 -0
- package/dist/dashboard/browser/chunk-K43KW4K2.js +4 -0
- package/dist/dashboard/browser/chunk-K46KCL6L.js +1 -0
- package/dist/dashboard/browser/chunk-KDCKLTCZ.js +1 -0
- package/dist/dashboard/browser/chunk-L63FHXGH.js +4 -0
- package/dist/dashboard/browser/chunk-LBRCWYRN.js +3 -0
- package/dist/dashboard/browser/chunk-MHOSWLRR.js +3 -0
- package/dist/dashboard/browser/chunk-N5GRSTMJ.js +6 -0
- package/dist/dashboard/browser/chunk-NDSAQ2HK.js +1 -0
- package/dist/dashboard/browser/chunk-NN2YNLGP.js +3 -0
- package/dist/dashboard/browser/chunk-PJPGLT4T.js +1 -0
- package/dist/dashboard/browser/chunk-R6J2WYUD.js +1 -0
- package/dist/dashboard/browser/chunk-T2YKGOEM.js +3 -0
- package/dist/dashboard/browser/chunk-U6AY7XZK.js +5 -0
- package/dist/dashboard/browser/chunk-UC3XMN2Y.js +1 -0
- package/dist/dashboard/browser/chunk-UNT27XFJ.js +2 -0
- package/dist/dashboard/browser/chunk-V2CQL6W5.js +1 -0
- package/dist/dashboard/browser/chunk-VZ6E4VQY.js +2 -0
- package/dist/dashboard/browser/chunk-WAN7TQQW.js +1 -0
- package/dist/dashboard/browser/chunk-WJUGRIIZ.js +1 -0
- package/dist/dashboard/browser/chunk-WRBP4DQN.js +4 -0
- package/dist/dashboard/browser/chunk-XUU4AY4M.js +2 -0
- package/dist/dashboard/browser/chunk-XYVF5I5T.js +1 -0
- package/dist/dashboard/browser/chunk-Y6B6X4Y6.js +2 -0
- package/dist/dashboard/browser/chunk-YNQPX5G6.js +1 -0
- package/dist/dashboard/browser/chunk-YYAZGY5M.js +1 -0
- package/dist/dashboard/browser/index.html +15 -0
- package/dist/dashboard/browser/main-ZADV4SOC.js +1 -0
- package/dist/dashboard/browser/styles-SIPYJLMG.css +1 -0
- package/dist/dashboard/prerendered-routes.json +3 -0
- package/dist/main.js +52 -43
- package/dist/mcp.js +3 -2
- package/package.json +6 -5
- package/src/templates/claude/skill.md.tmpl +23 -31
- package/src/dashboard.html +0 -451
- package/src/templates/claude/commands/doctor.md.tmpl +0 -10
- package/src/templates/claude/commands/errors.md.tmpl +0 -10
- package/src/templates/claude/commands/logs.md.tmpl +0 -10
- package/src/templates/claude/commands/restart.md.tmpl +0 -10
- package/src/templates/claude/commands/start.md.tmpl +0 -12
- package/src/templates/claude/commands/status.md.tmpl +0 -10
- package/src/templates/claude/commands/stop.md.tmpl +0 -10
- package/src/templates/claude/commands/up.md.tmpl +0 -10
- package/src/templates/claude/commands/wait.md.tmpl +0 -10
- package/src/templates/claude/commands/why.md.tmpl +0 -10
package/src/dashboard.html
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>daimon</title>
|
|
6
|
-
<style>
|
|
7
|
-
body { font-family: -apple-system, Segoe UI, Roboto, sans-serif; background: #0f1115; color: #e6e6e6; margin: 0; padding: 20px; }
|
|
8
|
-
h1 { font-size: 18px; font-weight: 600; margin: 0 0 8px; letter-spacing: 0.5px; }
|
|
9
|
-
.tabs { display: flex; gap: 4px; margin-bottom: 12px; }
|
|
10
|
-
.tab { padding: 6px 12px; background: #1a1d24; color: #9aa3b2; border: 1px solid #232730; border-radius: 4px; cursor: pointer; font-size: 13px; }
|
|
11
|
-
.tab.active { background: #2c313b; color: #e6e6e6; border-color: #353a45; }
|
|
12
|
-
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
13
|
-
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #232730; vertical-align: middle; }
|
|
14
|
-
th { color: #9aa3b2; font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
15
|
-
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; }
|
|
16
|
-
.s-stopped { background: #2b2f38; color: #9aa3b2; }
|
|
17
|
-
.s-starting, .s-compiling { background: #423a17; color: #e0c060; }
|
|
18
|
-
.s-serving { background: #19402a; color: #62d28a; }
|
|
19
|
-
.s-error { background: #4a1f23; color: #ef6f74; }
|
|
20
|
-
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
|
|
21
|
-
.h-healthy { background: #62d28a; }
|
|
22
|
-
.h-unhealthy { background: #ef6f74; }
|
|
23
|
-
.h-unknown { background: #555a66; }
|
|
24
|
-
a { color: #8ab4ff; text-decoration: none; }
|
|
25
|
-
a:hover { text-decoration: underline; }
|
|
26
|
-
.err { color: #ef6f74; }
|
|
27
|
-
.meta { color: #8a93a2; font-size: 12px; margin-bottom: 12px; }
|
|
28
|
-
.num { text-align: right; font-variant-numeric: tabular-nums; color: #c8cdd6; }
|
|
29
|
-
button { background: #232730; color: #e6e6e6; border: 1px solid #353a45; padding: 3px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; margin-right: 4px; }
|
|
30
|
-
button:hover:not(:disabled) { background: #2c313b; }
|
|
31
|
-
button:disabled { opacity: 0.4; cursor: default; }
|
|
32
|
-
.chev { display: inline-block; width: 12px; cursor: pointer; user-select: none; color: #9aa3b2; }
|
|
33
|
-
.drawer { background: #14171d; padding: 10px 18px 12px 36px; border-bottom: 1px solid #232730; }
|
|
34
|
-
.errrow { display: grid; grid-template-columns: 12px 1fr 90px 90px auto; gap: 10px; padding: 4px 0; font-size: 13px; align-items: baseline; border-bottom: 1px solid #1a1d24; }
|
|
35
|
-
.errrow .file { color: #8ab4ff; font-family: ui-monospace, Consolas, monospace; }
|
|
36
|
-
.errrow .code { color: #d0a060; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
|
|
37
|
-
.errrow .msg { color: #d8d8d8; }
|
|
38
|
-
.errrow .count { color: #c8cdd6; font-variant-numeric: tabular-nums; text-align: right; }
|
|
39
|
-
.errrow .seen { color: #6c757d; font-variant-numeric: tabular-nums; text-align: right; font-size: 11px; }
|
|
40
|
-
.sev { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #ef6f74; }
|
|
41
|
-
.errrow button { font-size: 10px; padding: 2px 6px; }
|
|
42
|
-
.panel { background: #14171d; padding: 14px; border-radius: 6px; border: 1px solid #232730; margin-top: 12px; }
|
|
43
|
-
.toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; }
|
|
44
|
-
.toolbar input[type=text] { background: #0f1115; color: #e6e6e6; border: 1px solid #353a45; padding: 4px 8px; border-radius: 4px; font-size: 13px; min-width: 240px; }
|
|
45
|
-
.toolbar select { background: #0f1115; color: #e6e6e6; border: 1px solid #353a45; padding: 4px 8px; border-radius: 4px; font-size: 13px; }
|
|
46
|
-
.group-h { color: #9aa3b2; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; margin: 12px 0 4px; }
|
|
47
|
-
</style>
|
|
48
|
-
</head>
|
|
49
|
-
<body>
|
|
50
|
-
<h1 style="display:flex;align-items:center;justify-content:space-between">daimon <button id="gear" title="Configuration" style="font-size:14px;padding:4px 10px;margin:0">⚙ config</button></h1>
|
|
51
|
-
<div class="tabs">
|
|
52
|
-
<div class="tab active" data-tab="apps">Apps</div>
|
|
53
|
-
<div class="tab" data-tab="all-errors">All errors</div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<div id="config-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100;align-items:flex-start;justify-content:center;padding:40px 20px;overflow:auto">
|
|
57
|
-
<div style="background:#14171d;border:1px solid #353a45;border-radius:8px;padding:18px;max-width:900px;width:100%;color:#e6e6e6">
|
|
58
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
|
59
|
-
<div style="font-weight:600;font-size:15px">Configuration</div>
|
|
60
|
-
<div><button id="cfg-reload">soft reload</button><button id="cfg-save">save</button><button id="cfg-close">close</button></div>
|
|
61
|
-
</div>
|
|
62
|
-
<div id="cfg-toast" style="margin-bottom:8px;font-size:12px;color:#d0a060;min-height:14px"></div>
|
|
63
|
-
<div class="tabs" style="margin-bottom:8px">
|
|
64
|
-
<div class="tab active" data-cfgtab="perapp">Per-app</div>
|
|
65
|
-
<div class="tab" data-cfgtab="global">Global</div>
|
|
66
|
-
</div>
|
|
67
|
-
<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">
|
|
68
|
-
<label style="color:#9aa3b2;font-size:12px">apply preset:</label>
|
|
69
|
-
<select id="preset-select" style="background:#0f1115;color:#e6e6e6;border:1px solid #353a45;padding:3px 8px;border-radius:3px"><option value="">—</option></select>
|
|
70
|
-
<button id="preset-apply">load into patch</button>
|
|
71
|
-
</div>
|
|
72
|
-
<div id="cfg-perapp"></div>
|
|
73
|
-
<div id="cfg-global" style="display:none">
|
|
74
|
-
<div style="color:#9aa3b2;font-size:12px;margin-bottom:6px">JSON merge patch. Saving sends only this object to the server.</div>
|
|
75
|
-
<textarea id="cfg-textarea" style="width:100%;height:380px;background:#0f1115;color:#e6e6e6;border:1px solid #353a45;padding:8px;border-radius:4px;font-family:ui-monospace,Consolas,monospace;font-size:12px"></textarea>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
<div class="meta" id="meta">loading…</div>
|
|
80
|
-
|
|
81
|
-
<div id="view-apps">
|
|
82
|
-
<table>
|
|
83
|
-
<thead>
|
|
84
|
-
<tr>
|
|
85
|
-
<th style="width:18px"></th>
|
|
86
|
-
<th>name</th><th>status</th><th>health</th><th>port</th><th>url</th>
|
|
87
|
-
<th>errors</th><th>uptime</th><th class="num">cpu%</th><th class="num">mem MB</th>
|
|
88
|
-
<th>recent compile</th><th>bundle</th><th>actions</th>
|
|
89
|
-
</tr>
|
|
90
|
-
</thead>
|
|
91
|
-
<tbody id="rows"></tbody>
|
|
92
|
-
</table>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<div id="view-all-errors" style="display:none">
|
|
96
|
-
<div class="toolbar">
|
|
97
|
-
<input type="text" id="all-err-search" placeholder="search (substring)…" />
|
|
98
|
-
<label style="color:#9aa3b2;font-size:12px">group:</label>
|
|
99
|
-
<select id="all-err-group">
|
|
100
|
-
<option value="app">by app</option>
|
|
101
|
-
<option value="file">by file</option>
|
|
102
|
-
<option value="code">by code</option>
|
|
103
|
-
</select>
|
|
104
|
-
<span style="color:#6c757d;font-size:12px" id="all-err-count"></span>
|
|
105
|
-
</div>
|
|
106
|
-
<div id="all-err-body" class="panel"></div>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<script>
|
|
110
|
-
let editorScheme = 'vscode';
|
|
111
|
-
const expanded = new Set();
|
|
112
|
-
const errorCache = new Map();
|
|
113
|
-
let currentTab = 'apps';
|
|
114
|
-
let configCache = null;
|
|
115
|
-
let configEtag = '';
|
|
116
|
-
const tokenKey = 'daimon.token.' + location.port;
|
|
117
|
-
|
|
118
|
-
function loadToken() { try { return localStorage.getItem(tokenKey) || ''; } catch { return ''; } }
|
|
119
|
-
function saveToken(t) { try { if (t) localStorage.setItem(tokenKey, t); else localStorage.removeItem(tokenKey); } catch {} }
|
|
120
|
-
function authedHeaders(extra = {}) {
|
|
121
|
-
const tok = loadToken();
|
|
122
|
-
return tok ? { ...extra, 'Authorization': 'Bearer ' + tok } : extra;
|
|
123
|
-
}
|
|
124
|
-
async function authedFetch(url, init = {}) {
|
|
125
|
-
init.headers = authedHeaders(init.headers || {});
|
|
126
|
-
let res = await fetch(url, init);
|
|
127
|
-
if (res.status === 401) {
|
|
128
|
-
const t = prompt('daimon API token:');
|
|
129
|
-
if (t) {
|
|
130
|
-
saveToken(t);
|
|
131
|
-
init.headers = authedHeaders((init.headers && init.headers['Content-Type']) ? { 'Content-Type': init.headers['Content-Type'] } : {});
|
|
132
|
-
res = await fetch(url, init);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return res;
|
|
136
|
-
}
|
|
137
|
-
function setCfgToast(msg, color) { const t = document.getElementById('cfg-toast'); t.style.color = color || '#d0a060'; t.textContent = msg; }
|
|
138
|
-
|
|
139
|
-
document.querySelectorAll('.tab').forEach(t => {
|
|
140
|
-
t.addEventListener('click', () => {
|
|
141
|
-
currentTab = t.dataset.tab;
|
|
142
|
-
document.querySelectorAll('.tab').forEach(x => x.classList.toggle('active', x === t));
|
|
143
|
-
document.getElementById('view-apps').style.display = currentTab === 'apps' ? '' : 'none';
|
|
144
|
-
document.getElementById('view-all-errors').style.display = currentTab === 'all-errors' ? '' : 'none';
|
|
145
|
-
tick();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
function fmtUptime(ms) {
|
|
150
|
-
if (ms == null) return '';
|
|
151
|
-
const s = Math.floor(ms / 1000);
|
|
152
|
-
if (s < 60) return s + 's';
|
|
153
|
-
const m = Math.floor(s / 60);
|
|
154
|
-
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
|
155
|
-
const h = Math.floor(m / 60);
|
|
156
|
-
return h + 'h ' + (m % 60) + 'm';
|
|
157
|
-
}
|
|
158
|
-
function sparkline(values) {
|
|
159
|
-
if (!values || !values.length) return '';
|
|
160
|
-
const w = 60, h = 16, max = Math.max(...values, 1);
|
|
161
|
-
const step = values.length > 1 ? w / (values.length - 1) : 0;
|
|
162
|
-
const pts = values.map((v, i) => `${(i * step).toFixed(1)},${(h - (v / max) * (h - 2) - 1).toFixed(1)}`).join(' ');
|
|
163
|
-
return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><polyline fill="none" stroke="#8ab4ff" stroke-width="1.2" points="${pts}"/></svg>`;
|
|
164
|
-
}
|
|
165
|
-
function editorUrl(file, line, col) {
|
|
166
|
-
if (!file) return '#';
|
|
167
|
-
const safe = String(file).replace(/\\/g, '/');
|
|
168
|
-
const suffix = (line ? ':' + line : '') + (col ? ':' + col : '');
|
|
169
|
-
if (editorScheme.includes('://')) return editorScheme + safe + suffix;
|
|
170
|
-
return editorScheme + '://file/' + safe + suffix;
|
|
171
|
-
}
|
|
172
|
-
function escapeHtml(s) {
|
|
173
|
-
return String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
174
|
-
}
|
|
175
|
-
async function action(name, kind, btn) {
|
|
176
|
-
const buttons = btn.parentNode.querySelectorAll('button');
|
|
177
|
-
buttons.forEach(b => b.disabled = true);
|
|
178
|
-
try { await authedFetch('/api/apps/' + encodeURIComponent(name) + '/' + kind, { method: 'POST' }); } catch {}
|
|
179
|
-
tick();
|
|
180
|
-
}
|
|
181
|
-
window.action = action;
|
|
182
|
-
async function copyError(err) {
|
|
183
|
-
const payload = { file: err.parsed?.file ?? null, line: err.parsed?.line ?? null, col: err.parsed?.col ?? null, code: err.parsed?.code ?? null, message: err.parsed?.message ?? err.message };
|
|
184
|
-
try { await navigator.clipboard.writeText(JSON.stringify(payload)); } catch {}
|
|
185
|
-
}
|
|
186
|
-
window.copyError = copyError;
|
|
187
|
-
const logStreams = new Map();
|
|
188
|
-
const logBuf = new Map();
|
|
189
|
-
function startLogStream(name) {
|
|
190
|
-
if (logStreams.has(name)) return;
|
|
191
|
-
try {
|
|
192
|
-
const es = new EventSource('/api/apps/' + encodeURIComponent(name) + '/logs/stream');
|
|
193
|
-
logStreams.set(name, es);
|
|
194
|
-
if (!logBuf.has(name)) logBuf.set(name, []);
|
|
195
|
-
es.onmessage = ev => {
|
|
196
|
-
try {
|
|
197
|
-
const d = JSON.parse(ev.data);
|
|
198
|
-
const arr = logBuf.get(name);
|
|
199
|
-
arr.push(d.line);
|
|
200
|
-
if (arr.length > 200) arr.splice(0, arr.length - 200);
|
|
201
|
-
const slot = document.querySelector(`[data-livelog="${name}"]`);
|
|
202
|
-
if (slot) slot.textContent = arr.slice(-12).join('\n');
|
|
203
|
-
} catch {}
|
|
204
|
-
};
|
|
205
|
-
es.onerror = () => { es.close(); logStreams.delete(name); };
|
|
206
|
-
} catch {}
|
|
207
|
-
}
|
|
208
|
-
function stopLogStream(name) {
|
|
209
|
-
const es = logStreams.get(name);
|
|
210
|
-
if (es) { try { es.close(); } catch {}; logStreams.delete(name); }
|
|
211
|
-
}
|
|
212
|
-
async function toggleDrawer(name) {
|
|
213
|
-
if (expanded.has(name)) { expanded.delete(name); stopLogStream(name); }
|
|
214
|
-
else { expanded.add(name); await fetchErrors(name); startLogStream(name); }
|
|
215
|
-
tick();
|
|
216
|
-
}
|
|
217
|
-
window.toggleDrawer = toggleDrawer;
|
|
218
|
-
async function fetchErrors(name) {
|
|
219
|
-
try {
|
|
220
|
-
const r = await fetch('/api/apps/' + encodeURIComponent(name) + '/errors');
|
|
221
|
-
if (r.ok) errorCache.set(name, await r.json());
|
|
222
|
-
} catch {}
|
|
223
|
-
}
|
|
224
|
-
function renderDrawer(name) {
|
|
225
|
-
const errs = errorCache.get(name) || [];
|
|
226
|
-
const liveLogHtml = `<pre data-livelog="${name}" style="background:#0c0e13;color:#a7b1c2;padding:8px;border-radius:4px;font-size:11px;margin:0 0 8px;max-height:160px;overflow:auto"></pre>`;
|
|
227
|
-
if (errs.length === 0) return '<div class="drawer">' + liveLogHtml + '<div style="color:#6c757d">no errors recorded</div></div>';
|
|
228
|
-
const rows = errs.map((e, i) => {
|
|
229
|
-
const p = e.parsed || {};
|
|
230
|
-
const fileTxt = p.file ? `${p.file}${p.line ? ':' + p.line : ''}${p.col ? ':' + p.col : ''}` : '(unparsed)';
|
|
231
|
-
const link = p.file ? `<a href="${editorUrl(p.file, p.line, p.col)}">${escapeHtml(fileTxt)}</a>` : escapeHtml(fileTxt);
|
|
232
|
-
const code = escapeHtml(p.code || '');
|
|
233
|
-
const msg = escapeHtml((p.message || e.message || '').slice(0, 200));
|
|
234
|
-
const seen = new Date(e.lastSeen).toLocaleTimeString();
|
|
235
|
-
return `<div class="errrow"><span class="sev"></span><div><div class="file">${link}</div><div class="msg" title="${escapeHtml(e.message)}">${msg}</div></div><div class="code">${code}</div><div class="count">×${e.count}</div><div><button onclick='copyError(${JSON.stringify(JSON.stringify(e))})'>copy</button>${p.file ? `<a href="${editorUrl(p.file, p.line, p.col)}"><button>open</button></a>` : ''}<div class="seen">${seen}</div></div></div>`;
|
|
236
|
-
}).join('');
|
|
237
|
-
return `<div class="drawer">${liveLogHtml}${rows}</div>`;
|
|
238
|
-
}
|
|
239
|
-
async function loadEditorScheme() {
|
|
240
|
-
try {
|
|
241
|
-
const r = await fetch('/api/config');
|
|
242
|
-
if (r.ok) {
|
|
243
|
-
const cfg = await r.json();
|
|
244
|
-
configCache = cfg.config;
|
|
245
|
-
configEtag = cfg.etag;
|
|
246
|
-
editorScheme = cfg?.config?.editor?.scheme || 'vscode';
|
|
247
|
-
}
|
|
248
|
-
} catch {}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function openConfigPanel() {
|
|
252
|
-
const r = await fetch('/api/config');
|
|
253
|
-
if (!r.ok) { setCfgToast('failed to load config: ' + r.status, '#ef6f74'); return; }
|
|
254
|
-
const j = await r.json();
|
|
255
|
-
configCache = j.config;
|
|
256
|
-
configEtag = j.etag;
|
|
257
|
-
document.getElementById('cfg-textarea').value = JSON.stringify({}, null, 2);
|
|
258
|
-
renderPerAppEditor();
|
|
259
|
-
document.getElementById('config-modal').style.display = 'flex';
|
|
260
|
-
setCfgToast('');
|
|
261
|
-
}
|
|
262
|
-
function closeConfigPanel() { document.getElementById('config-modal').style.display = 'none'; }
|
|
263
|
-
function renderPerAppEditor() {
|
|
264
|
-
const wrap = document.getElementById('cfg-perapp');
|
|
265
|
-
if (!configCache) { wrap.innerHTML = ''; return; }
|
|
266
|
-
const overrides = configCache.overrides || {};
|
|
267
|
-
const apps = Array.from(new Set([...Object.keys(overrides), ...(window.__appNames || [])])).sort();
|
|
268
|
-
if (apps.length === 0) { wrap.innerHTML = '<div style="color:#6c757d;font-size:12px">no apps yet — open the global tab to set searchRoots</div>'; return; }
|
|
269
|
-
wrap.innerHTML = apps.map(name => {
|
|
270
|
-
const ov = overrides[name] || {};
|
|
271
|
-
return `<div style="border-bottom:1px solid #232730;padding:10px 0">
|
|
272
|
-
<div style="font-weight:600;margin-bottom:6px">${name}</div>
|
|
273
|
-
<div style="display:grid;grid-template-columns:90px 1fr;gap:6px 12px;font-size:12px">
|
|
274
|
-
<label>port</label><input data-app="${name}" data-key="port" type="number" value="${ov.port ?? ''}" style="background:#0f1115;color:#e6e6e6;border:1px solid #353a45;padding:3px 6px;border-radius:3px" />
|
|
275
|
-
<label>command</label><input data-app="${name}" data-key="command" type="text" value="${(ov.command ?? '').replace(/"/g, '"')}" style="background:#0f1115;color:#e6e6e6;border:1px solid #353a45;padding:3px 6px;border-radius:3px" />
|
|
276
|
-
<label>url</label><input data-app="${name}" data-key="url" type="text" value="${(ov.url ?? '').replace(/"/g, '"')}" style="background:#0f1115;color:#e6e6e6;border:1px solid #353a45;padding:3px 6px;border-radius:3px" />
|
|
277
|
-
<label>hidden</label><input data-app="${name}" data-key="hidden" type="checkbox" ${ov.hidden ? 'checked' : ''} />
|
|
278
|
-
</div>
|
|
279
|
-
</div>`;
|
|
280
|
-
}).join('');
|
|
281
|
-
}
|
|
282
|
-
function collectPatch() {
|
|
283
|
-
let textPatch = {};
|
|
284
|
-
try { textPatch = JSON.parse(document.getElementById('cfg-textarea').value || '{}'); }
|
|
285
|
-
catch (e) { throw new Error('invalid JSON in global tab: ' + e.message); }
|
|
286
|
-
const overridesPatch = {};
|
|
287
|
-
document.querySelectorAll('#cfg-perapp [data-app]').forEach(inp => {
|
|
288
|
-
const name = inp.dataset.app;
|
|
289
|
-
const key = inp.dataset.key;
|
|
290
|
-
let v;
|
|
291
|
-
if (inp.type === 'checkbox') v = inp.checked;
|
|
292
|
-
else if (inp.type === 'number') v = inp.value === '' ? null : Number(inp.value);
|
|
293
|
-
else v = inp.value === '' ? null : inp.value;
|
|
294
|
-
const current = (configCache.overrides || {})[name]?.[key] ?? null;
|
|
295
|
-
const norm = v === '' ? null : v;
|
|
296
|
-
if (JSON.stringify(norm) !== JSON.stringify(current)) {
|
|
297
|
-
overridesPatch[name] = overridesPatch[name] || {};
|
|
298
|
-
overridesPatch[name][key] = norm;
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
const out = { ...textPatch };
|
|
302
|
-
if (Object.keys(overridesPatch).length) {
|
|
303
|
-
out.overrides = { ...(textPatch.overrides || {}), ...overridesPatch };
|
|
304
|
-
}
|
|
305
|
-
return out;
|
|
306
|
-
}
|
|
307
|
-
async function saveConfig() {
|
|
308
|
-
let patch;
|
|
309
|
-
try { patch = collectPatch(); } catch (e) { setCfgToast(e.message, '#ef6f74'); return; }
|
|
310
|
-
if (!Object.keys(patch).length) { setCfgToast('nothing to save', '#9aa3b2'); return; }
|
|
311
|
-
const res = await authedFetch('/api/config', { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'If-Match': configEtag }, body: JSON.stringify(patch) });
|
|
312
|
-
if (res.status === 412) {
|
|
313
|
-
setCfgToast('config was changed elsewhere — click Reload to refresh', '#ef6f74');
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if (!res.ok) {
|
|
317
|
-
let msg = 'save failed: ' + res.status;
|
|
318
|
-
try { const e = await res.json(); if (e?.error) msg += ' — ' + e.error; } catch {}
|
|
319
|
-
setCfgToast(msg, '#ef6f74');
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
const j = await res.json();
|
|
323
|
-
configEtag = j.etag;
|
|
324
|
-
let msg = 'saved · applied: ' + (j.applied || []).join(', ');
|
|
325
|
-
if (j.restartRequired && j.restartRequired.length) msg += ' · applies on next restart of: ' + j.restartRequired.join(', ');
|
|
326
|
-
setCfgToast(msg, '#62d28a');
|
|
327
|
-
await loadEditorScheme();
|
|
328
|
-
await openConfigPanel();
|
|
329
|
-
}
|
|
330
|
-
async function reloadConfig() {
|
|
331
|
-
const res = await authedFetch('/api/config/reload', { method: 'POST' });
|
|
332
|
-
if (!res.ok) { setCfgToast('reload failed: ' + res.status, '#ef6f74'); return; }
|
|
333
|
-
const j = await res.json();
|
|
334
|
-
setCfgToast('reloaded · added: ' + (j.addedApps || []).join(',') + ' · removed: ' + (j.removedApps || []).join(','), '#62d28a');
|
|
335
|
-
await loadEditorScheme();
|
|
336
|
-
await openConfigPanel();
|
|
337
|
-
}
|
|
338
|
-
async function loadPresets() {
|
|
339
|
-
try {
|
|
340
|
-
const r = await fetch('/api/presets');
|
|
341
|
-
if (!r.ok) return;
|
|
342
|
-
const list = await r.json();
|
|
343
|
-
const sel = document.getElementById('preset-select');
|
|
344
|
-
sel.innerHTML = '<option value="">—</option>' + list.map((p, i) => `<option value="${i}">${escapeHtml(p.name)} — ${escapeHtml(p.description || '')}</option>`).join('');
|
|
345
|
-
sel._list = list;
|
|
346
|
-
} catch {}
|
|
347
|
-
}
|
|
348
|
-
document.getElementById('preset-apply').addEventListener('click', () => {
|
|
349
|
-
const sel = document.getElementById('preset-select');
|
|
350
|
-
const list = sel._list || [];
|
|
351
|
-
const idx = sel.value;
|
|
352
|
-
if (idx === '') return;
|
|
353
|
-
const preset = list[Number(idx)];
|
|
354
|
-
if (!preset) return;
|
|
355
|
-
document.getElementById('cfg-textarea').value = JSON.stringify(preset.patch, null, 2);
|
|
356
|
-
document.querySelectorAll('[data-cfgtab]').forEach(x => { x.classList.toggle('active', x.dataset.cfgtab === 'global'); });
|
|
357
|
-
document.getElementById('cfg-perapp').style.display = 'none';
|
|
358
|
-
document.getElementById('cfg-global').style.display = '';
|
|
359
|
-
setCfgToast('preset loaded into Global tab — click Save to apply', '#62d28a');
|
|
360
|
-
});
|
|
361
|
-
document.getElementById('gear').addEventListener('click', () => { loadPresets(); openConfigPanel(); });
|
|
362
|
-
document.getElementById('cfg-close').addEventListener('click', closeConfigPanel);
|
|
363
|
-
document.getElementById('cfg-save').addEventListener('click', saveConfig);
|
|
364
|
-
document.getElementById('cfg-reload').addEventListener('click', reloadConfig);
|
|
365
|
-
document.querySelectorAll('[data-cfgtab]').forEach(t => t.addEventListener('click', () => {
|
|
366
|
-
document.querySelectorAll('[data-cfgtab]').forEach(x => x.classList.toggle('active', x === t));
|
|
367
|
-
document.getElementById('cfg-perapp').style.display = t.dataset.cfgtab === 'perapp' ? '' : 'none';
|
|
368
|
-
document.getElementById('cfg-global').style.display = t.dataset.cfgtab === 'global' ? '' : 'none';
|
|
369
|
-
}));
|
|
370
|
-
async function renderAllErrors() {
|
|
371
|
-
const r = await fetch('/api/apps');
|
|
372
|
-
if (!r.ok) return;
|
|
373
|
-
const apps = await r.json();
|
|
374
|
-
await Promise.all(apps.map(a => fetchErrors(a.name)));
|
|
375
|
-
const allRows = [];
|
|
376
|
-
for (const a of apps) {
|
|
377
|
-
const errs = errorCache.get(a.name) || [];
|
|
378
|
-
for (const e of errs) allRows.push({ app: a.name, err: e });
|
|
379
|
-
}
|
|
380
|
-
const filter = (document.getElementById('all-err-search').value || '').toLowerCase();
|
|
381
|
-
const group = document.getElementById('all-err-group').value;
|
|
382
|
-
const filtered = filter ? allRows.filter(r => (r.err.parsed?.message || r.err.message || '').toLowerCase().includes(filter) || (r.err.parsed?.file || '').toLowerCase().includes(filter)) : allRows;
|
|
383
|
-
document.getElementById('all-err-count').textContent = filtered.length + ' entr' + (filtered.length === 1 ? 'y' : 'ies');
|
|
384
|
-
const buckets = new Map();
|
|
385
|
-
for (const row of filtered) {
|
|
386
|
-
const key = group === 'app' ? row.app : group === 'file' ? (row.err.parsed?.file || '(unparsed)') : (row.err.parsed?.code || '(no code)');
|
|
387
|
-
if (!buckets.has(key)) buckets.set(key, []);
|
|
388
|
-
buckets.get(key).push(row);
|
|
389
|
-
}
|
|
390
|
-
const html = [...buckets.entries()].map(([k, rows]) => {
|
|
391
|
-
const inner = rows.map(({ app, err }) => {
|
|
392
|
-
const p = err.parsed || {};
|
|
393
|
-
const fileTxt = p.file ? `${p.file}${p.line ? ':' + p.line : ''}${p.col ? ':' + p.col : ''}` : '(unparsed)';
|
|
394
|
-
const link = p.file ? `<a href="${editorUrl(p.file, p.line, p.col)}">${escapeHtml(fileTxt)}</a>` : escapeHtml(fileTxt);
|
|
395
|
-
const code = escapeHtml(p.code || '');
|
|
396
|
-
const msg = escapeHtml((p.message || err.message || '').slice(0, 200));
|
|
397
|
-
return `<div class="errrow"><span class="sev"></span><div><div><span style="color:#9aa3b2;font-size:11px">${escapeHtml(app)}</span> · <span class="file">${link}</span></div><div class="msg">${msg}</div></div><div class="code">${code}</div><div class="count">×${err.count}</div><div><button onclick='copyError(${JSON.stringify(JSON.stringify(err))})'>copy</button>${p.file ? `<a href="${editorUrl(p.file, p.line, p.col)}"><button>open</button></a>` : ''}</div></div>`;
|
|
398
|
-
}).join('');
|
|
399
|
-
return `<div class="group-h">${escapeHtml(k)} (${rows.length})</div>${inner}`;
|
|
400
|
-
}).join('');
|
|
401
|
-
document.getElementById('all-err-body').innerHTML = html || '<div style="color:#6c757d">no errors</div>';
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async function tick() {
|
|
405
|
-
if (currentTab === 'all-errors') { await renderAllErrors(); document.getElementById('meta').textContent = 'all errors — last updated ' + new Date().toLocaleTimeString(); return; }
|
|
406
|
-
try {
|
|
407
|
-
const res = await fetch('/api/apps');
|
|
408
|
-
const apps = await res.json();
|
|
409
|
-
window.__appNames = apps.map(a => a.name);
|
|
410
|
-
const tbody = document.getElementById('rows');
|
|
411
|
-
const refreshDrawers = [];
|
|
412
|
-
tbody.innerHTML = apps.map(a => {
|
|
413
|
-
const isOpen = expanded.has(a.name);
|
|
414
|
-
const chev = isOpen ? '▾' : '▸';
|
|
415
|
-
const rowHtml = `
|
|
416
|
-
<tr>
|
|
417
|
-
<td><span class="chev" onclick="toggleDrawer('${a.name}')">${chev}</span></td>
|
|
418
|
-
<td onclick="toggleDrawer('${a.name}')" style="cursor:pointer">${a.name}</td>
|
|
419
|
-
<td><span class="badge s-${a.status}">${a.status}</span></td>
|
|
420
|
-
<td><span class="dot h-${a.health || 'unknown'}"></span>${a.health || 'unknown'}</td>
|
|
421
|
-
<td>${a.port ?? ''}</td>
|
|
422
|
-
<td>${a.url ? `<a href="${a.url}" target="_blank">${a.url}</a>` : ''}</td>
|
|
423
|
-
<td class="${a.errorCount ? 'err' : ''}">${a.errorCount}</td>
|
|
424
|
-
<td>${fmtUptime(a.uptimeMs)}</td>
|
|
425
|
-
<td class="num">${a.cpu == null ? '' : (a.cpu.toFixed ? a.cpu.toFixed(1) : a.cpu)}</td>
|
|
426
|
-
<td class="num">${a.memMB == null ? '' : a.memMB}</td>
|
|
427
|
-
<td>${sparkline((a.compileHistoryMs || []).slice(-10))}</td>
|
|
428
|
-
<td>${a.bundle ? `${a.bundle.initialKB}KB initial · ${a.bundle.lazyKB}KB lazy${a.bundleRegressionPct != null && a.bundleRegressionPct > 10 ? ` <span class="err">(+${a.bundleRegressionPct}%)</span>` : ''}` : ''}</td>
|
|
429
|
-
<td>
|
|
430
|
-
<button onclick="action('${a.name}','start',this)">start</button>
|
|
431
|
-
<button onclick="action('${a.name}','stop',this)">stop</button>
|
|
432
|
-
<button onclick="action('${a.name}','restart',this)">restart</button>
|
|
433
|
-
</td>
|
|
434
|
-
</tr>`;
|
|
435
|
-
const drawer = isOpen ? `<tr><td colspan="13" style="padding:0">${renderDrawer(a.name)}</td></tr>` : '';
|
|
436
|
-
if (isOpen) refreshDrawers.push(a.name);
|
|
437
|
-
return rowHtml + drawer;
|
|
438
|
-
}).join('');
|
|
439
|
-
document.getElementById('meta').textContent = apps.length + ' app' + (apps.length === 1 ? '' : 's') + ' — last updated ' + new Date().toLocaleTimeString();
|
|
440
|
-
for (const n of refreshDrawers) void fetchErrors(n);
|
|
441
|
-
} catch (e) {
|
|
442
|
-
document.getElementById('meta').textContent = 'error: ' + e.message;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
document.getElementById('all-err-search').addEventListener('input', () => { if (currentTab === 'all-errors') renderAllErrors(); });
|
|
446
|
-
document.getElementById('all-err-group').addEventListener('change', () => { if (currentTab === 'all-errors') renderAllErrors(); });
|
|
447
|
-
loadEditorScheme().then(tick);
|
|
448
|
-
setInterval(tick, 2000);
|
|
449
|
-
</script>
|
|
450
|
-
</body>
|
|
451
|
-
</html>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-doctor
|
|
3
|
-
description: Sanity-check the daimon config and environment. Exits 1 if anything is wrong.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon doctor`
|
|
9
|
-
|
|
10
|
-
Does not require the daemon. Returns `{ checks: [{name, ok, detail?}], ok: bool }`. On `ok: false`, report each failed check.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-errors
|
|
3
|
-
description: Get deduplicated errors for an app. Prefer --structured for parsed TS errors (file:line:col, code).
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon errors $1 --structured`
|
|
9
|
-
|
|
10
|
-
Add `--since 5m` to scope to a recent window. For diff-mode (only errors newer than your last query), use `--since-last --client <your-id>`.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-logs
|
|
3
|
-
description: Tail recent log lines for an app. Default tail is 50.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon logs $1 --tail ${2:-50}`
|
|
9
|
-
|
|
10
|
-
Returns `{ lines: string[] }`. ANSI-stripped, in chronological order.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-restart
|
|
3
|
-
description: Restart an daimon-managed dev server. Then block until healthy.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon restart $1`
|
|
9
|
-
|
|
10
|
-
Then: `daimon wait $1 --until healthy --timeout 60s`. On timeout, call `daimon errors $1 --structured` and `daimon why $1`.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-start
|
|
3
|
-
description: Start an daimon-managed dev server. Prefer "daimon up <profile>" when starting multiple apps with dependencies.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon start $1`
|
|
9
|
-
|
|
10
|
-
Then block until healthy: `daimon wait $1 --until healthy --timeout 60s`.
|
|
11
|
-
|
|
12
|
-
If `wait` exits with code 2 (timeout), run `daimon errors $1 --structured` and `daimon why $1` and report both.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-status
|
|
3
|
-
description: Get the current status of an daimon-managed dev server (status, port, health, uptime, error count).
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon status $1`
|
|
9
|
-
|
|
10
|
-
Return the JSON object as-is. If exit code 1, the app is unknown or the daemon is unreachable.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-up
|
|
3
|
-
description: Bring up a profile or autoStart set. Cascades dependencies and blocks until each app is healthy (120s budget).
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon up $1` (or just `daimon up` to use autoStart).
|
|
9
|
-
|
|
10
|
-
Returns an array of `{ name, status, health }`. Any entry with `status != "serving"` or `health != "healthy"` indicates a failure — follow up with `daimon errors <name>` and `daimon why <name>`.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-wait
|
|
3
|
-
description: Block until an app reaches a target state. Prefer over polling. Default state is "serving"; pass --until healthy for full readiness.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon wait $1 --until ${2:-healthy} --timeout ${3:-60s}`
|
|
9
|
-
|
|
10
|
-
Exit 0 = condition met, exit 2 = timed out (stdout still has the JSON), exit 1 = unknown app or other error.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: daimon-why
|
|
3
|
-
description: Diagnose why an app is in error — returns the last status transition plus 5 preceding events.
|
|
4
|
-
daimon-version: {{daimon_version}}
|
|
5
|
-
generated-at: {{generated_at}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Run: `daimon why $1`
|
|
9
|
-
|
|
10
|
-
Returns one JSON object with the trigger event and surrounding context. Combine with `daimon errors $1 --structured` for a complete picture.
|