smlflg's picture
Initial public upload from Projekte/HarnessEcosystem
5134cea verified
Raw History Blame Contribute Delete
30.4 kB
const state = {
snapshot: null,
view: "overview",
query: "",
type: "all",
harness: "all",
selectedItem: null,
lastFocused: null,
};
const ui = {};
document.addEventListener("DOMContentLoaded", init);
function init() {
cacheUi();
bindControls();
loadSnapshot();
}
function cacheUi() {
ui.subtitle = document.getElementById("snapshot-subtitle");
ui.meta = document.getElementById("topbar-meta");
ui.content = document.getElementById("content");
ui.viewTitle = document.getElementById("view-title");
ui.viewNav = document.getElementById("view-nav");
ui.search = document.getElementById("search-input");
ui.type = document.getElementById("type-filter");
ui.harness = document.getElementById("harness-filter");
ui.reset = document.getElementById("reset-filters");
ui.dialog = document.getElementById("detail-dialog");
ui.detailKind = document.getElementById("detail-kind");
ui.detailTitle = document.getElementById("detail-title");
ui.detailSummary = document.getElementById("detail-summary");
ui.detailMeta = document.getElementById("detail-meta");
ui.detailRaw = document.getElementById("detail-raw");
ui.closeDialog = document.getElementById("close-dialog");
}
function bindControls() {
ui.viewNav.addEventListener("click", (event) => {
const button = event.target.closest("[data-view]");
if (!button) return;
state.view = button.dataset.view;
render();
});
ui.search.addEventListener("input", (event) => {
state.query = event.target.value.trim().toLowerCase();
render();
});
ui.type.addEventListener("change", (event) => {
state.type = event.target.value;
render();
});
ui.harness.addEventListener("change", (event) => {
state.harness = event.target.value;
render();
});
ui.reset.addEventListener("click", () => {
state.query = "";
state.type = "all";
state.harness = "all";
ui.search.value = "";
ui.type.value = "all";
ui.harness.value = "all";
render();
});
ui.closeDialog.addEventListener("click", closeDialog);
ui.dialog.addEventListener("click", (event) => {
if (event.target === ui.dialog) closeDialog();
});
ui.dialog.addEventListener("cancel", closeDialog);
}
async function loadSnapshot() {
ui.content.replaceChildren(emptyState("Daten werden geladen."));
try {
const response = await fetch("data/harness-snapshot.json", { cache: "no-store" });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
state.snapshot = normalizeSnapshot(await response.json());
populateHarnessFilter();
render();
} catch (error) {
renderMissingSnapshot(error);
}
}
function renderMissingSnapshot(error) {
ui.subtitle.textContent = "Kein lesbarer Snapshot.";
ui.meta.replaceChildren();
ui.content.replaceChildren(
emptyState(
"Snapshot fehlt oder ist nicht lesbar.",
"Führe im Ordner `sidebyside` den Befehl `python3 scripts/build_snapshot.py` aus und lade die Seite über einen lokalen HTTP-Server neu.",
error ? `Technischer Hinweis: ${error.message}` : "",
),
);
}
function render() {
if (!state.snapshot) return;
const snapshot = state.snapshot;
const harnesses = filterHarnesses(snapshot.harnesses);
const items = filterItems(snapshot.items);
const discoveries = filterDiscoveries(snapshot.discoveries);
ui.subtitle.textContent = snapshot.meta.generated_at
? `Stand ${formatDate(snapshot.meta.generated_at)}`
: "Stand unbekannt";
ui.meta.replaceChildren(
statPill("Harnesses", snapshot.harnesses.length),
statPill("Inventar", snapshot.items.length),
statPill("Funde", snapshot.discoveries.length),
statPill("Privat", snapshot.meta.privacy_mode || "lokal"),
);
updateNav();
const titleMap = {
overview: "Vergleich",
usage: "Meine Nutzung",
inventory: "Skills & Commands",
discoveries: "Unentdeckt",
sources: "Quellen",
};
ui.viewTitle.textContent = titleMap[state.view] || "Vergleich";
const node = {
overview: () => renderOverview(snapshot, harnesses, discoveries),
usage: () => renderUsage(harnesses),
inventory: () => renderInventory(items, snapshot.items.length),
discoveries: () => renderDiscoveries(discoveries),
sources: () => renderSources(harnesses, snapshot),
}[state.view]();
ui.content.replaceChildren(node);
}
function updateNav() {
ui.viewNav.querySelectorAll("[data-view]").forEach((button) => {
button.classList.toggle("active", button.dataset.view === state.view);
});
}
function populateHarnessFilter() {
ui.harness.replaceChildren(option("all", "Alle Harnesses"));
for (const harness of state.snapshot.harnesses) {
ui.harness.append(option(harness.id, harness.name));
}
}
function normalizeSnapshot(raw) {
const meta = asObject(raw.meta);
const harnesses = asArray(raw.harnesses).map(normalizeHarness);
const comparisonRows = asArray(raw.comparison).map(normalizeComparisonRow);
const discoveries = dedupeDiscoveries([
...asArray(raw.discoveries).map((entry) => normalizeDiscovery(entry, null)),
...harnesses.flatMap((harness) => harness.discoveries.map((entry) => normalizeDiscovery(entry, harness))),
]);
const items = harnesses.flatMap(collectDetailItems);
return { meta, harnesses, comparisonRows, discoveries, items };
}
function normalizeHarness(raw) {
const base = asObject(raw);
const extensionObject = asObject(base.extensions);
const skills = bucketEntries(extensionObject, "skills");
const commands = bucketEntries(extensionObject, "commands");
const agents = bucketEntries(extensionObject, "agents");
const extensions = flattenExtensionEntries(extensionObject).filter((item) => !["skills", "commands", "agents"].includes(item.bucket));
const harness = {
id: textValue(base.id),
name: textValue(base.name || base.id || "Unbenannt"),
status: statusText(base.status),
rawStatus: asObject(base.status),
config: asObject(base.config),
rawUsage: asObject(base.usage),
instructionLayers: collectArrayLike(base.instruction_layers),
discoveries: collectArrayLike(base.discoveries),
sources: collectArrayLike(base.sources),
warnings: collectArrayLike(base.warnings),
skills,
commands,
agents,
extensions,
};
harness.metrics = metricsForHarness(harness);
return harness;
}
function metricsForHarness(harness) {
const status = harness.rawStatus;
const usage = harness.rawUsage;
const counts = asObject(status.counts);
return {
model: modelLabel(harness.config),
sessions: firstNumber(status.thread_count, status.session_count, counts.project_transcripts, counts.sessions, usage.sessions?.db_count),
tools: firstNumber(status.log_rows, usage.tools?.observed_count, counts.tools, usage.tools?.db_count),
extensions: harness.skills.length + harness.commands.length + harness.agents.length + harness.extensions.length,
layers: harness.instructionLayers.length,
discoveries: harness.discoveries.length,
sources: harness.sources.length,
};
}
function normalizeComparisonRow(raw) {
const base = asObject(raw);
return {
label: textValue(base.label || base.metric || "Vergleich"),
note: textValue(base.note || ""),
values: asObject(base.values),
};
}
function normalizeDiscovery(raw, harness) {
const base = asObject(raw);
const ownerId = textValue(base.harness_id || base.harnessId || harness?.id || "");
const ownerName = textValue(base.harness || harness?.name || "Global");
const title = discoveryTitle(base);
return {
kind: "discovery",
id: `${ownerId}:${title}:${textValue(base.path || base.table || "")}`,
name: title,
category: textValue(base.kind || base.category || "discovery"),
path: textValue(base.path || base.table || ""),
summary: textValue(base.summary || base.reason || base.candidate || base.message || ""),
confidence: confidenceValue(base.confidence),
score: numberValue(base.score),
harnessId: ownerId,
harnessName: ownerName,
raw: base,
};
}
function discoveryTitle(base) {
const kind = textValue(base.kind);
const name = textValue(base.name);
const table = textValue(base.table);
if (kind === "memory-store") return "Codex Memories sind leer";
if (kind === "hooks") return "Codex Hooks sind leer";
if (kind === "sessions") return "Session Store ist leer";
if (kind === "plugin" && name === "github") return "Nur GitHub ist als Codex-Plugin aktiv";
if (kind === "plugin") return "Plugin-Marktplatz ist kaum genutzt";
if (kind === "database" && table) return `${table} ist leer`;
if (kind === "dormant_surface") return "Plugin-System wirkt ungenutzt";
if (kind === "feature_gap") return "Changelog-Features ohne Nutzungsspur";
if (kind === "lightly_used_surface") return "Große Oberfläche, wenig belegte Nutzung";
if (kind === "sparse_history") return "Session-History ist dünn";
if (kind === "empty_channels") return "Hermes Channels sind leer";
if (kind === "gateway_startup_failed") return "Hermes Gateway startet nicht sauber";
if (kind.endsWith("_disabled")) return `${kind.replaceAll("_", " ")} ist deaktiviert`;
if (kind === "many_skills_low_usage") return "Viele Hermes-Skills kaum genutzt";
return textValue(base.name || base.title || base.message || base.candidate || base.kind || "Fund");
}
function collectDetailItems(harness) {
return [
...harness.skills.map((item) => detailItem(item, harness, "skill")),
...harness.commands.map((item) => detailItem(item, harness, "command")),
...harness.agents.map((item) => detailItem(item, harness, "agent")),
...harness.instructionLayers.map((item) => detailItem(item, harness, "layer")),
...harness.extensions.map((item) => detailItem(item, harness, "extension")),
...usageEntries(harness.rawUsage).map((item) => detailItem(item, harness, "usage")),
];
}
function detailItem(raw, harness, kind) {
const base = asObject(raw);
return {
kind,
harnessId: harness.id,
harnessName: harness.name,
id: `${harness.id}:${kind}:${textValue(base.path || base.name || base.label || Math.random())}`,
name: textValue(base.name || base.label || base.id || base.layer || base.kind || kind),
category: textValue(base.category || base.group || base.bucket || base.kind || kind),
path: textValue(base.path || base.file || base.location || ""),
summary: textValue(base.summary || base.description || base.preview || base.detail || summarizeValue(base.value)),
usage: base.usage || base.count || base.value || "",
confidence: confidenceValue(base.confidence),
raw: base,
};
}
function renderOverview(snapshot, harnesses, discoveries) {
const wrapper = viewStack();
wrapper.append(
sectionHeader("Direkter Vergleich", "Die drei Harnesses als Arbeitswerkzeuge, nicht als Rohdaten-Dump."),
harnessOverviewGrid(harnesses),
focusPanel(discoveries),
comparisonTable(snapshot.comparisonRows, harnesses),
);
return wrapper;
}
function harnessOverviewGrid(harnesses) {
const grid = el("div", "harness-overview");
for (const harness of harnesses) {
const card = el("article", `harness-tile ${harness.id}`);
card.append(
row(
stack(title(harness.name, "h3"), muted(harness.metrics.model || "Modell unbekannt")),
statusBadge(harness.status),
),
metricStrip([
["Sessions", harness.metrics.sessions],
["Tools", harness.metrics.tools],
["Inventar", harness.metrics.extensions],
["Funde", harness.metrics.discoveries],
]),
conciseInsight(harness),
);
grid.append(card);
}
return grid;
}
function conciseInsight(harness) {
const firstDiscovery = normalizeDiscovery(harness.discoveries[0] || {}, harness);
const box = el("div", "insight-line");
box.append(
tag(harness.id),
textSpan(firstDiscovery.name || "Keine priorisierte Lücke im Snapshot."),
);
return box;
}
function focusPanel(discoveries) {
const panel = el("section", "focus-panel");
panel.append(sectionHeader("Fokus zuerst", "Die wichtigsten unentdeckten oder untergenutzten Features."));
const list = el("div", "focus-list");
balancedFocus(discoveries).forEach((item, index) => {
const button = el("button", "focus-item");
button.type = "button";
button.addEventListener("click", () => openDetail(item));
button.append(
textSpan(String(index + 1), "rank"),
stack(strong(item.name), muted(`${item.harnessName} · Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)),
);
list.append(button);
});
panel.append(list);
return panel;
}
function balancedFocus(discoveries) {
const byHarness = discoveries.reduce((acc, item) => {
const key = item.harnessId || item.harnessName || "global";
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {});
const firstPass = Object.values(byHarness).map((items) => items[0]).filter(Boolean);
const rest = discoveries.filter((item) => !firstPass.includes(item));
return [...firstPass, ...rest].slice(0, 5);
}
function comparisonTable(rows, harnesses) {
const section = el("section", "panel-section");
section.append(sectionHeader("Matrix", "Normierte Kennzahlen aus dem lokalen Snapshot."));
const wrap = el("div", "table-wrap");
const table = el("table");
const head = el("tr");
head.append(th("Metrik"), ...harnesses.map((harness) => th(harness.name)));
table.append(el("thead", "", head));
const body = el("tbody");
for (const rowData of rows) {
const tr = el("tr");
const label = el("td");
label.append(strong(rowData.label), rowData.note ? muted(rowData.note) : "");
tr.append(label);
harnesses.forEach((harness) => tr.append(td(formatComparisonValue(rowData.values[harness.id] ?? rowData.values[harness.name]))));
body.append(tr);
}
table.append(body);
wrap.append(table);
section.append(wrap);
return section;
}
function renderUsage(harnesses) {
const wrapper = viewStack();
wrapper.append(sectionHeader("Meine Nutzung", "Was deine lokalen Spuren über deinen tatsächlichen Einsatz zeigen."));
const grid = el("div", "usage-grid");
harnesses.forEach((harness) => grid.append(usagePanel(harness)));
wrapper.append(grid);
return wrapper;
}
function usagePanel(harness) {
const panel = el("article", `usage-panel ${harness.id}`);
panel.append(row(stack(title(harness.name, "h3"), muted(harness.metrics.model)), statusBadge(harness.status)));
panel.append(metricStrip([
["Sessions", harness.metrics.sessions],
["Toolcalls", harness.metrics.tools],
["Layer", harness.metrics.layers],
["Quellen", harness.metrics.sources],
]));
panel.append(toolBlock(harness), compactConfig(harness));
return panel;
}
function toolBlock(harness) {
const tools = topTools(harness);
const block = el("div", "data-block");
block.append(strong("Top-Tools"));
if (!tools.length) {
block.append(muted("Keine Tool-Nutzung im Snapshot gefunden."));
return block;
}
const max = Math.max(...tools.map((item) => numberValue(item.count)), 1);
tools.slice(0, 8).forEach((item) => {
const bar = el("div", "bar-row");
bar.append(textSpan(item.name), textSpan(formatNumber(item.count), "bar-value"));
const meter = el("div", "bar-meter");
const fill = el("span");
fill.style.width = `${Math.max(6, (numberValue(item.count) / max) * 100)}%`;
meter.append(fill);
block.append(bar, meter);
});
return block;
}
function compactConfig(harness) {
const rows = [
["Modell", harness.metrics.model],
["Status", harness.status],
["Skills", harness.skills.length],
["Commands", harness.commands.length],
["Agents", harness.agents.length],
];
const block = el("div", "data-block");
block.append(strong("Profil"));
rows.forEach(([label, value]) => block.append(keyValue(label, formatAny(value))));
return block;
}
function renderInventory(items, total) {
const wrapper = viewStack();
wrapper.append(sectionHeader("Skills & Commands", `${items.length} von ${total} Einträgen nach aktuellen Filtern.`));
const typeCounts = countBy(items, "kind");
wrapper.append(metricStrip([
["Skills", typeCounts.skill || 0],
["Commands", typeCounts.command || 0],
["Agents", typeCounts.agent || 0],
["Layer", typeCounts.layer || 0],
["Erweiterungen", typeCounts.extension || 0],
]));
const list = el("div", "inventory-list");
items.slice(0, 160).forEach((item) => list.append(inventoryRow(item)));
if (items.length > 160) list.append(emptyState(`${items.length - 160} weitere Treffer ausgeblendet. Nutze Suche oder Filter.`));
wrapper.append(list);
return wrapper;
}
function inventoryRow(item) {
const button = el("button", "inventory-row");
button.type = "button";
button.addEventListener("click", () => openDetail(item));
button.append(
tag(kindLabel(item.kind)),
stack(strong(item.name), muted([item.harnessName, item.category, item.path].filter(Boolean).join(" · "))),
textSpan(item.summary || "Keine Kurzbeschreibung.", "row-summary"),
);
return button;
}
function renderDiscoveries(discoveries) {
const wrapper = viewStack();
wrapper.append(sectionHeader("Unentdeckt", `${discoveries.length} priorisierte Hinweise aus deinen lokalen Harness-Spuren.`));
const list = el("div", "discovery-list");
discoveries.forEach((item, index) => {
const card = el("button", "discovery-row");
card.type = "button";
card.addEventListener("click", () => openDetail(item));
card.append(
textSpan(String(index + 1), "rank"),
stack(strong(item.name), muted(item.summary || "Keine Beschreibung.")),
stack(tag(item.harnessName), muted(`Score ${formatNumber(item.score)} · ${confidenceLabel(item.confidence)}`)),
);
list.append(card);
});
wrapper.append(list);
return wrapper;
}
function renderSources(harnesses, snapshot) {
const wrapper = viewStack();
wrapper.append(
sectionHeader("Quellen & Datenschutz", "Gruppierte Quellen statt endloser Dateiliste."),
privacyPanel(snapshot),
);
harnesses.forEach((harness) => wrapper.append(sourceGroup(harness)));
return wrapper;
}
function privacyPanel(snapshot) {
const panel = el("section", "privacy-panel");
panel.append(
keyValue("Privacy Mode", snapshot.meta.privacy_mode || "summaries-only"),
keyValue("Ausgeschlossen", asArray(snapshot.meta.excluded_patterns).join(", ")),
keyValue("Warnungen", asArray(snapshot.meta.warnings).length || "keine"),
);
return panel;
}
function sourceGroup(harness) {
const section = el("section", "source-group");
section.append(row(stack(title(harness.name, "h3"), muted(`${harness.sources.length} Quellen`)), statusBadge(harness.status)));
const kinds = countBy(harness.sources.map(normalizeSource), "kind");
section.append(metricStrip(Object.entries(kinds).slice(0, 5).map(([kind, count]) => [kind, count])));
const list = el("div", "source-list");
harness.sources.slice(0, 22).forEach((source) => {
const item = normalizeSource(source);
list.append(keyValue(item.kind || "Quelle", item.path || item.name || ""));
});
if (harness.sources.length > 22) list.append(muted(`${harness.sources.length - 22} weitere Quellen im Snapshot.`));
section.append(list);
return section;
}
function filterHarnesses(harnesses) {
return harnesses.filter((harness) => state.harness === "all" || harness.id === state.harness);
}
function filterItems(items) {
return items.filter((item) => {
if (state.harness !== "all" && item.harnessId !== state.harness) return false;
if (state.type !== "all" && item.kind !== state.type) return false;
return matchesQuery(item);
}).sort((a, b) => `${a.harnessName}:${a.kind}:${a.name}`.localeCompare(`${b.harnessName}:${b.kind}:${b.name}`, "de"));
}
function filterDiscoveries(discoveries) {
return discoveries
.filter((item) => {
if (state.harness !== "all" && item.harnessId !== state.harness) return false;
if (state.type !== "all" && state.type !== "discovery") return false;
return matchesQuery(item);
})
.sort((a, b) => numberValue(b.score) - numberValue(a.score) || confidenceValue(b.confidence) - confidenceValue(a.confidence));
}
function matchesQuery(item) {
if (!state.query) return true;
return [item.name, item.kind, item.category, item.path, item.summary, item.harnessName]
.map((value) => textValue(value).toLowerCase())
.join(" ")
.includes(state.query);
}
function openDetail(item) {
state.selectedItem = item;
state.lastFocused = document.activeElement;
ui.detailKind.textContent = `${item.harnessName || "Global"} · ${kindLabel(item.kind)}`;
ui.detailTitle.textContent = item.name;
ui.detailSummary.textContent = item.summary || "Keine Kurzbeschreibung im Snapshot.";
ui.detailMeta.replaceChildren(
...metaPair("Kategorie", item.category || "unbekannt"),
...metaPair("Pfad", item.path || "kein Pfad"),
...metaPair("Nutzung", formatAny(item.usage)),
...metaPair("Confidence", confidenceLabel(item.confidence)),
);
ui.detailRaw.textContent = clippedJson(item.raw || item);
ui.dialog.showModal ? ui.dialog.showModal() : ui.dialog.setAttribute("open", "");
}
function closeDialog() {
state.selectedItem = null;
if (ui.dialog.open) ui.dialog.close();
if (state.lastFocused && state.lastFocused.focus) state.lastFocused.focus();
}
function viewStack() {
return el("div", "view-stack");
}
function sectionHeader(titleText, copy) {
const header = el("header", "section-header");
header.append(stack(title(titleText, "h2"), muted(copy)));
return header;
}
function metricStrip(entries) {
const strip = el("div", "metric-strip");
entries.forEach(([label, value]) => {
const metric = el("div", "metric");
metric.append(textSpan(label, "metric-label"), textSpan(formatAny(value), "metric-value"));
strip.append(metric);
});
return strip;
}
function keyValue(key, value) {
const rowNode = el("div", "kv-row");
rowNode.append(textSpan(key, "kv-key"), textSpan(formatAny(value), "kv-value"));
return rowNode;
}
function statusBadge(status) {
const badge = el("span", `status-badge ${statusState(status)}`);
badge.textContent = status;
return badge;
}
function statPill(label, value) {
const pill = el("span", "stat-pill");
pill.append(textSpan(label), strong(formatAny(value)));
return pill;
}
function tag(text) {
const node = el("span", "tag");
node.textContent = text || "n/a";
return node;
}
function option(value, label) {
const node = document.createElement("option");
node.value = value;
node.textContent = label;
return node;
}
function th(text) {
const node = document.createElement("th");
node.textContent = text;
return node;
}
function td(text) {
const node = document.createElement("td");
node.textContent = text;
return node;
}
function title(text, level = "h3") {
const node = document.createElement(level);
node.textContent = text;
return node;
}
function strong(text) {
const node = document.createElement("strong");
node.textContent = text;
return node;
}
function muted(text) {
const node = el("p", "muted");
node.textContent = text;
return node;
}
function textSpan(text, className = "") {
const node = el("span", className);
node.textContent = text;
return node;
}
function stack(...children) {
return el("div", "stack", ...children.filter(Boolean));
}
function row(...children) {
return el("div", "row", ...children.filter(Boolean));
}
function el(tagName, className = "", ...children) {
const node = document.createElement(tagName);
if (className) node.className = className;
children.flat().forEach((child) => {
if (child === null || child === undefined || child === "") return;
node.append(child);
});
return node;
}
function emptyState(titleText, copy = "", detail = "") {
const node = el("div", "empty-state");
node.append(strong(titleText));
if (copy) node.append(muted(copy));
if (detail) node.append(muted(detail));
return node;
}
function bucketEntries(extensionObject, bucket) {
return collectArrayLike(extensionObject[bucket]).map((entry) => {
const base = asObject(entry);
return { ...base, bucket, category: base.category || base.group || base.kind || bucket };
});
}
function flattenExtensionEntries(extensionObject) {
return Object.entries(extensionObject).flatMap(([bucket, value]) => (
collectArrayLike(value).map((entry) => ({ ...asObject(entry), bucket, category: asObject(entry).category || bucket }))
));
}
function usageEntries(usage) {
return Object.entries(asObject(usage)).map(([key, value]) => ({
name: key,
category: "usage",
value,
summary: summarizeValue(value),
}));
}
function topTools(harness) {
const usage = harness.rawUsage;
if (Array.isArray(usage.tools?.top)) return usage.tools.top.map((item) => ({ name: item.name || item.target || "tool", count: item.count || item.n || 0 }));
if (Array.isArray(usage.log_target_counts)) return usage.log_target_counts.map((item) => ({ name: item.target || item.name || "tool", count: item.count || item.n || 0 }));
return [];
}
function modelLabel(config) {
const model = config.model;
if (model && typeof model === "object") {
return [model.default || model.model, model.provider].filter(Boolean).join(" · ");
}
return textValue(model || config.default_model || config.provider || "unbekannt");
}
function firstNumber(...values) {
for (const value of values) {
const number = numberValue(value);
if (number > 0) return number;
}
return 0;
}
function countBy(items, key) {
return items.reduce((acc, item) => {
const value = textValue(item[key] || "unbekannt");
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {});
}
function dedupeDiscoveries(discoveries) {
const seen = new Set();
return discoveries.filter((item) => {
const key = `${item.harnessId}:${item.name}:${item.summary}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
function normalizeSource(source) {
return typeof source === "string" ? { kind: "Quelle", path: source } : asObject(source);
}
function statusText(value) {
if (typeof value === "string") return value || "unknown";
const base = asObject(value);
if (base.gateway?.startup_failed || base.flags?.gateway_startup_failed) return "partial";
if (base.exists && Object.values(base.exists).some((entry) => entry === false)) return "partial";
if (Object.keys(base).length) return "active";
return "unknown";
}
function statusState(status) {
const text = textValue(status).toLowerCase();
if (text.includes("partial") || text.includes("warn")) return "warn";
if (text.includes("fail") || text.includes("error") || text.includes("missing")) return "bad";
return "ok";
}
function kindLabel(kind) {
return {
skill: "Skill",
command: "Befehl",
agent: "Agent",
layer: "Instruktion",
extension: "Erweiterung",
usage: "Nutzung",
discovery: "Fund",
}[kind] || kind;
}
function formatComparisonValue(value) {
if (value === undefined || value === null || value === "") return "n/a";
if (typeof value === "number") return formatNumber(value);
if (typeof value === "string" && /^\d+$/.test(value)) return formatNumber(value);
return String(value);
}
function formatDate(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return textValue(value);
return new Intl.DateTimeFormat("de-DE", { dateStyle: "medium", timeStyle: "short" }).format(date);
}
function formatNumber(value) {
const number = Number(value);
if (!Number.isFinite(number)) return "0";
return new Intl.NumberFormat("de-DE").format(number);
}
function formatAny(value) {
if (value === null || value === undefined || value === "") return "n/a";
if (typeof value === "number") return formatNumber(value);
if (typeof value === "boolean") return value ? "Ja" : "Nein";
if (typeof value === "string") return value;
if (Array.isArray(value)) return `${value.length} Einträge`;
if (typeof value === "object") return summarizeValue(value);
return String(value);
}
function summarizeValue(value) {
if (value === null || value === undefined) return "";
if (Array.isArray(value)) return `${value.length} Einträge`;
if (typeof value === "object") {
const keys = Object.keys(value).slice(0, 6);
return keys.length ? `Felder: ${keys.join(", ")}` : "Leeres Objekt";
}
return textValue(value);
}
function confidenceLabel(value) {
const confidence = confidenceValue(value);
return confidence ? `${Math.round(confidence * 100)}%` : "n/a";
}
function confidenceValue(value) {
if (typeof value === "number") return value > 1 ? value / 100 : value;
const text = textValue(value).toLowerCase();
if (text.startsWith("high")) return 1;
if (text.startsWith("medium")) return 0.72;
if (text.startsWith("low")) return 0.45;
return numberValue(value);
}
function numberValue(value) {
const number = Number(value);
return Number.isFinite(number) ? number : 0;
}
function textValue(value) {
if (value === null || value === undefined) return "";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (Array.isArray(value)) return value.map(textValue).filter(Boolean).join(", ");
if (typeof value === "object") return textValue(value.name || value.label || value.id || value.path || value.summary || value.description || "");
return String(value);
}
function clippedJson(value) {
let text = "";
try {
text = JSON.stringify(value, null, 2);
} catch {
text = String(value);
}
return text.length > 5000 ? `${text.slice(0, 5000)}\n... gekürzt ...` : text;
}
function collectArrayLike(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
if (typeof value === "object") return Object.values(value);
return [value];
}
function asArray(value) {
return Array.isArray(value) ? value : [];
}
function asObject(value) {
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}
function metaPair(label, value) {
const dt = document.createElement("dt");
dt.textContent = label;
const dd = document.createElement("dd");
dd.textContent = formatAny(value);
return [dt, dd];
}