@oimdb/mcp 1.0.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/README.md +131 -0
- package/bin/cdp.js +96 -0
- package/bin/devtools.js +11 -0
- package/bin/server.js +9 -0
- package/dist/index.cjs +310 -0
- package/dist/index.d.cts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +280 -0
- package/package.json +61 -0
- package/ui/app.css +238 -0
- package/ui/app.js +273 -0
- package/ui/index.html +57 -0
package/ui/app.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/* OIMDB DevTools standalone UI — polls /api/inspect */
|
|
2
|
+
|
|
3
|
+
// ── State ──────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
let autoTimer = null;
|
|
6
|
+
let expanded = new Set();
|
|
7
|
+
|
|
8
|
+
// ── Elements ───────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const screenWaiting = document.getElementById('screen-waiting');
|
|
11
|
+
const screenMain = document.getElementById('screen-main');
|
|
12
|
+
const connDot = document.getElementById('conn-dot');
|
|
13
|
+
const connLabel = document.getElementById('conn-label');
|
|
14
|
+
const lastUpdated = document.getElementById('last-updated');
|
|
15
|
+
const badgeCollections = document.getElementById('badge-collections');
|
|
16
|
+
const badgeComputeds = document.getElementById('badge-computeds');
|
|
17
|
+
const listCollections = document.getElementById('list-collections');
|
|
18
|
+
const listComputeds = document.getElementById('list-computeds');
|
|
19
|
+
const btnRefresh = document.getElementById('btn-refresh');
|
|
20
|
+
const chkAuto = document.getElementById('chk-auto');
|
|
21
|
+
|
|
22
|
+
// ── Fetch ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
async function poll() {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch('/api/inspect');
|
|
27
|
+
const json = await res.json();
|
|
28
|
+
|
|
29
|
+
if (!json.connected || !json.data) {
|
|
30
|
+
showWaiting();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
showMain();
|
|
35
|
+
setConnected(json.tab);
|
|
36
|
+
render(json.data);
|
|
37
|
+
lastUpdated.textContent = new Date().toLocaleTimeString();
|
|
38
|
+
} catch {
|
|
39
|
+
showWaiting();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Layout ──────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function showWaiting() {
|
|
46
|
+
connDot.className = 'conn-dot disconnected';
|
|
47
|
+
connLabel.textContent = 'Waiting for browser…';
|
|
48
|
+
screenWaiting.classList.remove('hidden');
|
|
49
|
+
screenMain.classList.add('hidden');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function showMain() {
|
|
53
|
+
screenWaiting.classList.add('hidden');
|
|
54
|
+
screenMain.classList.remove('hidden');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setConnected(tab) {
|
|
58
|
+
connDot.className = 'conn-dot connected';
|
|
59
|
+
connLabel.textContent = tab ? `${tab.title}` : 'Connected';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Render ──────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function render(state) {
|
|
65
|
+
const collections = state.collections || {};
|
|
66
|
+
const computeds = state.computeds || {};
|
|
67
|
+
|
|
68
|
+
badgeCollections.textContent = Object.keys(collections).length;
|
|
69
|
+
badgeComputeds.textContent = Object.keys(computeds).length;
|
|
70
|
+
|
|
71
|
+
renderCollections(collections);
|
|
72
|
+
renderComputeds(computeds);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Collections ─────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function renderCollections(cols) {
|
|
78
|
+
const entries = Object.entries(cols);
|
|
79
|
+
if (!entries.length) {
|
|
80
|
+
listCollections.innerHTML = '<div class="empty">No collections registered.</div>';
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
listCollections.innerHTML = '';
|
|
84
|
+
entries.forEach(([name, info]) => listCollections.appendChild(buildCollRow(name, info)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildCollRow(name, info) {
|
|
88
|
+
const row = document.createElement('div');
|
|
89
|
+
row.className = 'c-row';
|
|
90
|
+
|
|
91
|
+
const isOpen = expanded.has(name);
|
|
92
|
+
|
|
93
|
+
const summary = document.createElement('div');
|
|
94
|
+
summary.className = 'c-summary';
|
|
95
|
+
|
|
96
|
+
const arrow = document.createElement('span');
|
|
97
|
+
arrow.className = 'c-arrow' + (isOpen ? ' open' : '');
|
|
98
|
+
arrow.textContent = '▶';
|
|
99
|
+
|
|
100
|
+
const nameEl = document.createElement('span');
|
|
101
|
+
nameEl.className = 'c-name';
|
|
102
|
+
nameEl.textContent = name;
|
|
103
|
+
|
|
104
|
+
const count = document.createElement('span');
|
|
105
|
+
count.className = 'c-count';
|
|
106
|
+
count.textContent = `${info.count} entities`;
|
|
107
|
+
|
|
108
|
+
summary.append(arrow, nameEl, count);
|
|
109
|
+
|
|
110
|
+
const detail = document.createElement('div');
|
|
111
|
+
detail.className = 'c-detail' + (isOpen ? ' open' : '');
|
|
112
|
+
detail.innerHTML = buildCollDetail(info);
|
|
113
|
+
|
|
114
|
+
summary.addEventListener('click', () => {
|
|
115
|
+
const opening = detail.classList.toggle('open');
|
|
116
|
+
arrow.classList.toggle('open', opening);
|
|
117
|
+
if (opening) expanded.add(name);
|
|
118
|
+
else expanded.delete(name);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
row.append(summary, detail);
|
|
122
|
+
return row;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildCollDetail(info) {
|
|
126
|
+
const parts = [];
|
|
127
|
+
|
|
128
|
+
if (info.sampleEntity && typeof info.sampleEntity === 'object') {
|
|
129
|
+
const tags = Object.entries(info.sampleEntity)
|
|
130
|
+
.map(([k, v]) => `<span class="tag">${esc(k)}: <span style="opacity:.6">${esc(typeof v)}</span></span>`)
|
|
131
|
+
.join('');
|
|
132
|
+
parts.push(drow('fields', tags));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const idxEntries = Object.entries(info.indexes || {});
|
|
136
|
+
if (idxEntries.length) {
|
|
137
|
+
const tags = idxEntries.map(([n, idx]) =>
|
|
138
|
+
`<span class="tag">${esc(n)} <span style="opacity:.6">(${idx.keyCount})</span></span>`
|
|
139
|
+
).join('');
|
|
140
|
+
parts.push(drow('indexes', tags));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const relEntries = Object.entries(info.relations || {});
|
|
144
|
+
if (relEntries.length) {
|
|
145
|
+
const tags = relEntries.map(([f, t]) =>
|
|
146
|
+
`<span class="tag">${esc(f)}<span class="tag-rel-arrow">→</span>${esc(t)}</span>`
|
|
147
|
+
).join('');
|
|
148
|
+
parts.push(drow('relations', tags));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (info.description) {
|
|
152
|
+
parts.push(drow('desc', `<span style="color:var(--text-dim)">${esc(info.description)}</span>`));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return parts.join('') || '<div class="d-row"><span style="color:var(--text-dim);font-size:11px">No details</span></div>';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function drow(label, content) {
|
|
159
|
+
return `<div class="d-row"><span class="d-label">${esc(label)}</span>${content}</div>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Computeds ───────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
function renderComputeds(comps) {
|
|
165
|
+
const entries = Object.entries(comps);
|
|
166
|
+
if (!entries.length) {
|
|
167
|
+
listComputeds.innerHTML = '<div class="empty">No computeds registered.</div>';
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
listComputeds.innerHTML = '';
|
|
171
|
+
entries.forEach(([name, info]) => listComputeds.appendChild(buildCompRow(name, info)));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function buildCompRow(name, info) {
|
|
175
|
+
const row = document.createElement('div');
|
|
176
|
+
row.className = 'comp-row';
|
|
177
|
+
|
|
178
|
+
let cls, label;
|
|
179
|
+
if (!info.isReady) {
|
|
180
|
+
cls = 'pending'; label = 'PENDING';
|
|
181
|
+
} else if (info.needsRecompute) {
|
|
182
|
+
cls = 'stale'; label = 'STALE';
|
|
183
|
+
} else {
|
|
184
|
+
cls = 'fresh'; label = 'FRESH';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const top = document.createElement('div');
|
|
188
|
+
top.className = 'comp-top';
|
|
189
|
+
|
|
190
|
+
const dot = document.createElement('span');
|
|
191
|
+
dot.className = `status-dot ${cls}`;
|
|
192
|
+
|
|
193
|
+
const nameEl = document.createElement('span');
|
|
194
|
+
nameEl.className = 'comp-name';
|
|
195
|
+
nameEl.textContent = name;
|
|
196
|
+
|
|
197
|
+
const pill = document.createElement('span');
|
|
198
|
+
pill.className = `status-pill ${cls}`;
|
|
199
|
+
pill.textContent = label;
|
|
200
|
+
|
|
201
|
+
top.append(dot, nameEl, pill);
|
|
202
|
+
|
|
203
|
+
if (info.isReady && info.currentValue !== undefined) {
|
|
204
|
+
const val = document.createElement('span');
|
|
205
|
+
val.className = 'comp-value';
|
|
206
|
+
const prefix = info.needsRecompute ? 'last: ' : '';
|
|
207
|
+
val.textContent = prefix + fmt(info.currentValue);
|
|
208
|
+
val.title = JSON.stringify(info.currentValue, null, 2);
|
|
209
|
+
top.appendChild(val);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
row.appendChild(top);
|
|
213
|
+
|
|
214
|
+
const deps = info.deps || [];
|
|
215
|
+
if (deps.length) {
|
|
216
|
+
const depsEl = document.createElement('div');
|
|
217
|
+
depsEl.className = 'comp-deps';
|
|
218
|
+
|
|
219
|
+
const lbl = document.createElement('span');
|
|
220
|
+
lbl.className = 'comp-deps-label';
|
|
221
|
+
lbl.textContent = 'deps:';
|
|
222
|
+
depsEl.appendChild(lbl);
|
|
223
|
+
|
|
224
|
+
deps.forEach(dep => {
|
|
225
|
+
const tag = document.createElement('span');
|
|
226
|
+
if (dep.name) {
|
|
227
|
+
tag.className = 'tag';
|
|
228
|
+
tag.textContent = dep.name;
|
|
229
|
+
} else {
|
|
230
|
+
tag.className = 'tag tag-warn';
|
|
231
|
+
tag.textContent = '⚠ unregistered';
|
|
232
|
+
tag.title = 'Register this source in your debug file';
|
|
233
|
+
}
|
|
234
|
+
depsEl.appendChild(tag);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
row.appendChild(depsEl);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return row;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
function esc(s) {
|
|
246
|
+
return String(s)
|
|
247
|
+
.replace(/&/g, '&')
|
|
248
|
+
.replace(/</g, '<')
|
|
249
|
+
.replace(/>/g, '>');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function fmt(v) {
|
|
253
|
+
if (v === null) return 'null';
|
|
254
|
+
if (typeof v === 'string') return `"${v}"`;
|
|
255
|
+
if (typeof v === 'object') {
|
|
256
|
+
const s = JSON.stringify(v);
|
|
257
|
+
return s.length > 48 ? s.slice(0, 48) + '…' : s;
|
|
258
|
+
}
|
|
259
|
+
return String(v);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Auto-refresh ─────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
function startAuto() { stopAuto(); autoTimer = setInterval(poll, 1000); }
|
|
265
|
+
function stopAuto() { if (autoTimer !== null) { clearInterval(autoTimer); autoTimer = null; } }
|
|
266
|
+
|
|
267
|
+
// ── Boot ────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
btnRefresh.addEventListener('click', poll);
|
|
270
|
+
chkAuto.addEventListener('change', () => chkAuto.checked ? startAuto() : stopAuto());
|
|
271
|
+
|
|
272
|
+
poll();
|
|
273
|
+
startAuto();
|
package/ui/index.html
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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">
|
|
6
|
+
<title>OIMDB DevTools</title>
|
|
7
|
+
<link rel="stylesheet" href="app.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header id="toolbar">
|
|
11
|
+
<div class="toolbar-brand">
|
|
12
|
+
<span class="brand-name">OIMDB</span>
|
|
13
|
+
<span class="brand-sub">DevTools</span>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="toolbar-status">
|
|
16
|
+
<span id="conn-dot" class="conn-dot"></span>
|
|
17
|
+
<span id="conn-label">Waiting for browser…</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="toolbar-actions">
|
|
20
|
+
<span id="last-updated"></span>
|
|
21
|
+
<button id="btn-refresh" title="Refresh now">⟳</button>
|
|
22
|
+
<label class="auto-label">
|
|
23
|
+
<input type="checkbox" id="chk-auto" checked>
|
|
24
|
+
Auto
|
|
25
|
+
</label>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<div id="screen-waiting">
|
|
30
|
+
<div class="waiting-card">
|
|
31
|
+
<div class="waiting-title">Waiting for browser</div>
|
|
32
|
+
<p>Open your app and make sure <code>debug.ts</code> calls:</p>
|
|
33
|
+
<pre>registry.connect(); // ws://localhost:7432</pre>
|
|
34
|
+
<p class="waiting-hint">Then reload the page.</p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div id="screen-main" class="hidden">
|
|
39
|
+
<div id="col-left" class="col">
|
|
40
|
+
<div class="col-header">
|
|
41
|
+
Collections
|
|
42
|
+
<span class="badge" id="badge-collections">0</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div id="list-collections" class="col-body"></div>
|
|
45
|
+
</div>
|
|
46
|
+
<div id="col-right" class="col">
|
|
47
|
+
<div class="col-header">
|
|
48
|
+
Computeds
|
|
49
|
+
<span class="badge" id="badge-computeds">0</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div id="list-computeds" class="col-body"></div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<script src="app.js"></script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|