doesitfire 0.1.2__py3-none-any.whl

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.
@@ -0,0 +1,341 @@
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>doesitfire report</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect x='1' y='1' width='22' height='22' rx='6' fill='%23a1319b'/%3E%3Cpath d='M13.4 4.6 7.4 13.1h4.3l-1 6.3 6-8.6h-4.3z' fill='%23fff' stroke='%23fff' stroke-width='1.1' stroke-linejoin='round'/%3E%3C/svg%3E">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">
11
+ <style>
12
+ :root {
13
+ --bg: #ffffff; --surface: #ffffff; --sunken: #f6f6f9;
14
+ --fg: #111116; --muted: #5c5d66; --border: #e4e4e9;
15
+ --shadow: 0 1px 2px rgba(17,17,22,.05), 0 6px 20px rgba(17,17,22,.06);
16
+ --accent: #a1319b; --accent-soft: rgba(161,49,155,.10);
17
+ --ok: #11813c; --warn: #a45e00; --err: #c22826;
18
+ --track: #ececf1;
19
+ --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
20
+ --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
21
+ color-scheme: light;
22
+ }
23
+ @media (prefers-color-scheme: dark) {
24
+ :root:not([data-theme="light"]) {
25
+ --bg: #000000; --surface: #0d0d10; --sunken: #0d0d10;
26
+ --fg: #f4f5f8; --muted: #a3a4ae; --border: #28282e;
27
+ --shadow: 0 0 0 1px rgba(255,255,255,.02), 0 10px 30px rgba(0,0,0,.7);
28
+ --accent: #df8cd8; --accent-soft: rgba(223,140,216,.12);
29
+ --ok: #5fd37f; --warn: #f7b83d; --err: #f66d67; --track: #1d1d22;
30
+ color-scheme: dark;
31
+ }
32
+ }
33
+ :root[data-theme="dark"] {
34
+ --bg: #000000; --surface: #0d0d10; --sunken: #0d0d10;
35
+ --fg: #f4f5f8; --muted: #a3a4ae; --border: #28282e;
36
+ --shadow: 0 0 0 1px rgba(255,255,255,.02), 0 10px 30px rgba(0,0,0,.7);
37
+ --accent: #df8cd8; --accent-soft: rgba(223,140,216,.12);
38
+ --ok: #5fd37f; --warn: #f7b83d; --err: #f66d67; --track: #1d1d22;
39
+ color-scheme: dark;
40
+ }
41
+ * { box-sizing: border-box; }
42
+ html { -webkit-text-size-adjust: 100%; }
43
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.55 var(--font-sans); }
44
+ .wrap { max-width: 1080px; margin: 0 auto; padding: 28px 16px 64px; }
45
+ a { color: var(--accent); }
46
+ code, .mono { font-family: var(--font-mono); font-size: .9em; }
47
+ header.top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
48
+ .brand { display: flex; align-items: center; gap: 12px; margin-right: auto; min-width: 0; }
49
+ .brand svg { width: 40px; height: 40px; flex: none; }
50
+ .brand h1 { font-size: 1.4rem; margin: 0; letter-spacing: -.01em; }
51
+ .brand p { margin: 0; color: var(--muted); font-size: .875rem; }
52
+ .controls { display: flex; gap: 8px; }
53
+ .seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--surface); }
54
+ .seg button { border: 0; background: transparent; color: var(--muted); font: 500 .8125rem var(--font-sans); padding: 6px 10px; cursor: pointer; }
55
+ .seg button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
56
+ .seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
57
+ .banner { overflow-wrap: anywhere; margin: 20px 0 0; padding: 10px 14px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 8px; background: var(--surface); font-size: .875rem; color: var(--muted); }
58
+ .banner strong { color: var(--fg); }
59
+ .kpis { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin: 20px 0 28px; }
60
+ .kpi { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); padding: 14px 16px; min-width: 0; }
61
+ .kpi .label { color: var(--muted); font-size: .8125rem; }
62
+ .kpi .value { font: 600 1.6rem/1.2 var(--font-mono); margin-top: 4px; }
63
+ .kpi .value .sep { color: var(--muted); font-weight: 400; }
64
+ .kpi .hint { color: var(--muted); font-size: .75rem; margin-top: 2px; }
65
+ h2 { font-size: 1.1rem; margin: 32px 0 6px; }
66
+ .lead { color: var(--muted); margin: 0 0 14px; font-size: .9rem; }
67
+ .panel { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); }
68
+ .matrix-wrap { overflow-x: auto; }
69
+ table.matrix { width: 100%; border-collapse: collapse; min-width: 560px; }
70
+ .matrix th, .matrix td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
71
+ .matrix tr:last-child td { border-bottom: 0; }
72
+ .matrix thead th { font-size: .8125rem; color: var(--muted); font-weight: 500; }
73
+ .matrix thead th b { display: block; color: var(--fg); font-weight: 600; font-size: .9rem; }
74
+ .matrix tbody th { font-weight: 600; font-size: .9rem; white-space: nowrap; }
75
+ .tag { display: inline-block; font-size: .6875rem; font-weight: 600; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--warn); color: var(--warn); margin-left: 4px; vertical-align: 1px; }
76
+ .metric { display: grid; grid-template-columns: 78px 1fr auto; align-items: center; gap: 8px; font-size: .8125rem; }
77
+ .metric + .metric { margin-top: 6px; }
78
+ .metric .name { color: var(--muted); }
79
+ .bar { height: 6px; border-radius: 3px; background: var(--track); overflow: hidden; }
80
+ .bar i { display: block; height: 100%; border-radius: 3px; }
81
+ .metric .num { font-family: var(--font-mono); font-size: .75rem; white-space: nowrap; }
82
+ .c-ok { color: var(--ok); } .c-warn { color: var(--warn); } .c-err { color: var(--err); } .c-na { color: var(--muted); }
83
+ .b-ok { background: var(--ok); } .b-warn { background: var(--warn); } .b-err { background: var(--err); } .b-na { background: transparent; }
84
+ .errs { font-size: .75rem; color: var(--err); margin-top: 6px; }
85
+ details.skill { margin-top: 12px; }
86
+ details.skill > summary { cursor: pointer; padding: 14px 16px; font-weight: 600; list-style: none; display: flex; justify-content: space-between; gap: 8px; }
87
+ details.skill > summary::-webkit-details-marker { display: none; }
88
+ details.skill > summary::after { content: "+"; color: var(--muted); font-weight: 400; }
89
+ details.skill[open] > summary::after { content: "−"; }
90
+ .desc { color: var(--muted); font-size: .85rem; padding: 0 16px 10px; margin: 0; }
91
+ .prompts { width: 100%; border-collapse: collapse; }
92
+ .prompts th, .prompts td { padding: 10px 16px; border-top: 1px solid var(--border); text-align: left; vertical-align: top; font-size: .85rem; }
93
+ .prompts th { color: var(--muted); font-weight: 500; font-size: .75rem; white-space: nowrap; }
94
+ .prompts td.p { width: 100%; white-space: pre-wrap; word-break: break-word; }
95
+ .prompts td.r { white-space: nowrap; font-family: var(--font-mono); font-size: .8rem; }
96
+ .exp { display: inline-block; font-size: .6875rem; font-weight: 600; border-radius: 4px; padding: 1px 6px; margin-bottom: 4px; }
97
+ .exp.fire { color: var(--accent); background: var(--accent-soft); }
98
+ .exp.nofire { color: var(--muted); background: var(--sunken); border: 1px solid var(--border); }
99
+ .other { display: block; color: var(--muted); font-size: .72rem; margin-top: 2px; }
100
+ .notes { font-size: .85rem; color: var(--muted); padding: 14px 16px; }
101
+ .notes p { margin: 0 0 8px; }
102
+ footer { margin-top: 40px; color: var(--muted); font-size: .8rem; }
103
+ @media (max-width: 760px) {
104
+ .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
105
+ .kpi.wide { grid-column: span 2; }
106
+ .kpi .value { font-size: 1.25rem; }
107
+ table.matrix, table.matrix thead, table.matrix tbody, table.matrix tr, table.matrix th, table.matrix td { display: block; min-width: 0; width: 100%; }
108
+ table.matrix thead { display: none; }
109
+ .matrix tbody tr { border-bottom: 1px solid var(--border); padding: 6px 0; }
110
+ .matrix tbody tr:last-child { border-bottom: 0; }
111
+ .matrix td, .matrix tbody th { border: 0; padding: 6px 16px; }
112
+ .matrix td::before { content: attr(data-agent); display: block; font-weight: 600; font-size: .8rem; margin-bottom: 4px; }
113
+ .prompts, .prompts tbody { display: block; }
114
+ .prompts thead { display: none; }
115
+ .prompts tr { display: grid; grid-template-columns: repeat(3, auto); gap: 4px 12px; padding: 10px 16px; border-top: 1px solid var(--border); }
116
+ .prompts td { border: 0; padding: 0; }
117
+ .prompts td.p { grid-column: 1 / -1; width: auto; }
118
+ .prompts td.r { white-space: normal; }
119
+ .prompts td.r::before { content: attr(data-agent) " "; color: var(--muted); font-family: var(--font-sans); }
120
+ }
121
+ </style>
122
+ </head>
123
+ <body>
124
+ <div class="wrap">
125
+ <header class="top">
126
+ <div class="brand">
127
+ <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="#a1319b"/><path d="M13.4 4.6 7.4 13.1h4.3l-1 6.3 6-8.6h-4.3z" fill="#fff" stroke="#fff" stroke-width="1.1" stroke-linejoin="round"/></svg>
128
+ <div><h1>doesitfire</h1><p data-i18n="subtitle">Skill activation report</p></div>
129
+ </div>
130
+ <div class="controls">
131
+ <div class="seg" role="group" aria-label="Language">
132
+ <button type="button" data-lang="en">EN</button><button type="button" data-lang="es">ES</button>
133
+ </div>
134
+ <div class="seg" role="group" aria-label="Theme">
135
+ <button type="button" data-theme-set="light" data-i18n="light">Light</button><button type="button" data-theme-set="dark" data-i18n="dark">Dark</button>
136
+ </div>
137
+ </div>
138
+ </header>
139
+ <div class="banner" id="banner"></div>
140
+ <section class="kpis" id="kpis"></section>
141
+ <h2 data-i18n="matrixTitle">Activation matrix</h2>
142
+ <p class="lead" data-i18n="matrixLead">For each skill and agent: how often the skill loaded on prompts that should trigger it (recall), and how often it loaded on prompts that should not (false fires).</p>
143
+ <div class="panel matrix-wrap"><table class="matrix" id="matrix"></table></div>
144
+ <h2 data-i18n="promptsTitle">Prompt by prompt</h2>
145
+ <p class="lead" data-i18n="promptsLead">Fired runs out of valid runs for every prompt. Runs that errored are left out and counted apart.</p>
146
+ <div id="skills"></div>
147
+ <h2 data-i18n="howTitle">How to read this</h2>
148
+ <div class="panel notes" id="notes"></div>
149
+ <footer id="footer"></footer>
150
+ </div>
151
+ <script type="application/json" id="data">__DOESITFIRE_DATA__</script>
152
+ <script>
153
+ (function () {
154
+ "use strict";
155
+ var D = JSON.parse(document.getElementById("data").textContent);
156
+ var M = D.meta, S = D.summary;
157
+ var T = {
158
+ en: {
159
+ subtitle: "Skill activation report", light: "Light", dark: "Dark",
160
+ recorded: "Recorded result", live: "Run",
161
+ kRecall: "Recall", kRecallHint: "should-fire prompts that loaded the skill",
162
+ kFalse: "False fires", kFalseHint: "should-not prompts that loaded it",
163
+ kPrecision: "Precision", kPrecisionHint: "loads that were wanted",
164
+ kAgentHint: "recall / false-fire rate", kRuns: "Runs", kRunsHint: "sandboxed agent runs",
165
+ kCost: "Cost", kCostHint: "from provider usage",
166
+ matrixTitle: "Activation matrix",
167
+ matrixLead: "For each skill and agent: how often the skill loaded on prompts that should trigger it (recall), and how often it loaded on prompts that should not (false fires).",
168
+ skill: "Skill", recall: "recall", falsef: "false fires", precision: "precision", errors: "runs with errors",
169
+ total: "All skills",
170
+ promptsTitle: "Prompt by prompt",
171
+ promptsLead: "Fired runs out of valid runs for every prompt. Runs that errored are left out and counted apart.",
172
+ prompt: "Prompt", should: "should fire", shouldNot: "should not fire", alsoLoaded: "also loaded:",
173
+ experimental: "experimental",
174
+ howTitle: "How to read this",
175
+ notes: [
176
+ "<b>Recall</b>: share of runs on should-fire prompts where the agent loaded the skill. <b>False fires</b>: share of runs on should-not prompts where it loaded it anyway. <b>Precision</b>: of all loads, how many were wanted.",
177
+ "Each agent ran its real CLI headless in a Docker container with no network except the model API, side-effect tools denied and a cap of {turns} turns. Runs per prompt (activation is not deterministic): {repeats}.",
178
+ "Detection: Claude Code, a <code>Skill</code> tool call in its stream-json output. OpenCode, its native <code>skill</code> tool. Codex reports no activation event, so a run counts when Codex reads the skill's <code>SKILL.md</code>; that is why it is marked experimental.",
179
+ "A small local model does not behave like a frontier model. These numbers describe this run, with these models; run <code>doesitfire run</code> with the models you actually use."
180
+ ],
181
+ footer: "Generated by doesitfire {version}. Started {start}, {dur} s.",
182
+ blocked: "Egress attempts blocked by the sandbox proxy: {n}.",
183
+ price: "Cost for {agent} ({model}): {input} per million input tokens and {output} per million output tokens, checked on {checked}.",
184
+ source: "Source"
185
+ },
186
+ es: {
187
+ subtitle: "Informe de activación de skills", light: "Claro", dark: "Oscuro",
188
+ recorded: "Resultado grabado", live: "Ejecución",
189
+ kRecall: "Recall", kRecallHint: "prompts que debían activarla y la activaron",
190
+ kFalse: "Falsos disparos", kFalseHint: "prompts que no debían y la activaron",
191
+ kPrecision: "Precisión", kPrecisionHint: "activaciones que tocaban",
192
+ kAgentHint: "recall / tasa de falsos disparos", kRuns: "Ejecuciones", kRunsHint: "ejecuciones de agente aisladas",
193
+ kCost: "Coste", kCostHint: "según el uso del proveedor",
194
+ matrixTitle: "Matriz de activación",
195
+ matrixLead: "Para cada skill y agente: cuántas veces se cargó la skill con prompts que debían activarla (recall) y cuántas se cargó con prompts que no debían (falsos disparos).",
196
+ skill: "Skill", recall: "recall", falsef: "falsos", precision: "precisión", errors: "ejecuciones con error",
197
+ total: "Todas las skills",
198
+ promptsTitle: "Prompt a prompt",
199
+ promptsLead: "Ejecuciones con activación sobre ejecuciones válidas para cada prompt. Las que fallaron se excluyen y se cuentan aparte.",
200
+ prompt: "Prompt", should: "debe activar", shouldNot: "no debe activar", alsoLoaded: "también cargó:",
201
+ experimental: "experimental",
202
+ howTitle: "Cómo leerlo",
203
+ notes: [
204
+ "<b>Recall</b>: parte de las ejecuciones con prompts que debían activar la skill en las que el agente la cargó. <b>Falsos disparos</b>: parte de las ejecuciones con prompts que no debían en las que la cargó igualmente. <b>Precisión</b>: de todas las cargas, cuántas tocaban.",
205
+ "Cada agente ejecutó su CLI real sin interfaz dentro de un contenedor Docker, sin más red que la API del modelo, con las herramientas con efectos denegadas y un máximo de {turns} turnos. Ejecuciones por prompt (la activación no es determinista): {repeats}.",
206
+ "Detección: Claude Code, una llamada a la herramienta <code>Skill</code> en su salida stream-json. OpenCode, su herramienta nativa <code>skill</code>. Codex no emite un evento de activación, así que una ejecución cuenta cuando Codex lee el <code>SKILL.md</code> de la skill; por eso va marcado como experimental.",
207
+ "Un modelo local pequeño no se comporta como un modelo de frontera. Estas cifras describen esta ejecución con estos modelos; lanza <code>doesitfire run</code> con los modelos que uses de verdad."
208
+ ],
209
+ footer: "Generado por doesitfire {version}. Inicio {start}, {dur} s.",
210
+ blocked: "Intentos de salida bloqueados por el proxy del sandbox: {n}.",
211
+ price: "Coste de {agent} ({model}): {input} por millón de tokens de entrada y {output} por millón de salida, comprobado el {checked}.",
212
+ source: "Fuente"
213
+ }
214
+ };
215
+ var lang = "en";
216
+ try { lang = localStorage.getItem("doesitfire-lang") || ""; } catch (e) { lang = ""; }
217
+ if (lang !== "en" && lang !== "es") lang = (navigator.language || "en").toLowerCase().indexOf("es") === 0 ? "es" : "en";
218
+
219
+ function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c]; }); }
220
+ function loc() { return lang === "es" ? "es-ES" : "en-US"; }
221
+ function pct(v) { return v == null ? "n/a" : new Intl.NumberFormat(loc(), {style: "percent", maximumFractionDigits: 0}).format(v); }
222
+ function num(v) { return new Intl.NumberFormat(loc()).format(v); }
223
+ function usd(v) { return new Intl.NumberFormat(loc(), {style: "currency", currency: "USD", minimumFractionDigits: 2, maximumFractionDigits: v > 0 && v < 0.01 ? 4 : 2}).format(v); }
224
+ function fmt(s, o) { return s.replace(/\{(\w+)\}/g, function (_, k) { return o[k] == null ? "" : o[k]; }); }
225
+ function rcls(v) { return v == null ? "na" : v >= 0.8 ? "ok" : v >= 0.5 ? "warn" : "err"; }
226
+ function fcls(v) { return v == null ? "na" : v <= 0.1 ? "ok" : v <= 0.3 ? "warn" : "err"; }
227
+ function agentInfo(name) { for (var i = 0; i < M.agents.length; i++) if (M.agents[i].name === name) return M.agents[i]; return {name: name}; }
228
+ function metric(label, v, num, cls) {
229
+ var w = v == null || v === 0 ? 0 : Math.max(2, Math.round(v * 100));
230
+ return '<div class="metric"><span class="name">' + esc(label) + '</span><span class="bar"><i class="b-' + cls + '" style="width:' + w + '%"></i></span><span class="num c-' + cls + '">' + esc(num) + '</span></div>';
231
+ }
232
+
233
+ function render() {
234
+ var t = T[lang];
235
+ document.documentElement.lang = lang;
236
+ document.querySelectorAll("[data-i18n]").forEach(function (el) { var v = t[el.getAttribute("data-i18n")]; if (typeof v === "string") el.textContent = v; });
237
+ document.querySelectorAll("[data-lang]").forEach(function (b) { b.setAttribute("aria-pressed", String(b.getAttribute("data-lang") === lang)); });
238
+
239
+ var models = M.agents.map(function (a) { return a.name + ": " + a.provider + "/" + a.model + " (CLI " + a.cli_version + ")"; }).join(" · ");
240
+ var hw = M.host && M.host.hardware ? " · " + M.host.hardware : "";
241
+ document.getElementById("banner").innerHTML = "<strong>" + esc(M.recorded ? t.recorded : t.live) + "</strong> · " +
242
+ esc((M.started_at || "").slice(0, 10)) + " · doesitfire " + esc(M.version) + " · " + esc(models) + esc(hw);
243
+
244
+ // One card per agent: agents may run different models, so their numbers are never pooled.
245
+ var cards = S.agents.map(function (a) {
246
+ var c = S.totals[a], info = agentInfo(a);
247
+ return '<div class="kpi"><div class="label">' + esc(a) + " · " + esc(info.model || "") + '</div>' +
248
+ '<div class="value"><span class="c-' + rcls(c.recall) + '">' + esc(pct(c.recall)) + '</span>' +
249
+ ' <span class="sep">/</span> <span class="c-' + fcls(c.false_fire_rate) + '">' + esc(pct(c.false_fire_rate)) + '</span></div>' +
250
+ '<div class="hint">' + esc(t.kAgentHint) + '</div></div>';
251
+ });
252
+ cards.push('<div class="kpi"><div class="label">' + esc(t.kRuns) + '</div><div class="value">' + esc(num(D.runs)) + '</div><div class="hint">' + esc(t.kRunsHint) + '</div></div>');
253
+ cards.push('<div class="kpi"><div class="label">' + esc(t.kCost) + '</div><div class="value">' + esc(usd(M.cost_usd || 0)) + '</div><div class="hint">' + esc(t.kCostHint) + '</div></div>');
254
+ var kp = document.getElementById("kpis");
255
+ kp.style.gridTemplateColumns = "repeat(" + Math.min(cards.length, 5) + ", minmax(0, 1fr))";
256
+ kp.innerHTML = cards.join("");
257
+
258
+ var head = "<thead><tr><th>" + esc(t.skill) + "</th>" + S.agents.map(function (a) {
259
+ var info = agentInfo(a);
260
+ return "<th><b>" + esc(a) + (info.experimental ? ' <span class="tag">' + esc(t.experimental) + "</span>" : "") + "</b>" + esc(info.model || "") + "</th>";
261
+ }).join("") + "</tr></thead>";
262
+ function cellHtml(c, a) {
263
+ var info = agentInfo(a);
264
+ return '<td data-agent="' + esc(a + (info.experimental ? " (" + t.experimental + ")" : "")) + '">' +
265
+ metric(t.recall, c.recall, c.true_fires + "/" + c.fire_runs, rcls(c.recall)) +
266
+ metric(t.falsef, c.false_fire_rate, c.false_fires + "/" + c.nofire_runs, fcls(c.false_fire_rate)) +
267
+ metric(t.precision, c.precision, pct(c.precision), rcls(c.precision)) +
268
+ (c.errors ? '<div class="errs">' + esc(t.errors) + ": " + c.errors + "</div>" : "") + "</td>";
269
+ }
270
+ var rows = S.skills.map(function (s) {
271
+ return "<tr><th>" + esc(s) + "</th>" + S.agents.map(function (a) { return cellHtml(S.cells[a + "|" + s], a); }).join("") + "</tr>";
272
+ });
273
+ rows.push("<tr><th>" + esc(t.total) + "</th>" + S.agents.map(function (a) { return cellHtml(S.totals[a], a); }).join("") + "</tr>");
274
+ document.getElementById("matrix").innerHTML = head + "<tbody>" + rows.join("") + "</tbody>";
275
+
276
+ var descs = {};
277
+ (M.skills || []).forEach(function (s) { descs[s.name] = s.description; });
278
+ document.getElementById("skills").innerHTML = S.skills.map(function (s, idx) {
279
+ var byPrompt = {}, order = [];
280
+ S.agents.forEach(function (a) {
281
+ S.cells[a + "|" + s].prompts.forEach(function (p) {
282
+ var key = p.expect + "\u0000" + p.prompt;
283
+ if (!byPrompt[key]) { byPrompt[key] = {prompt: p.prompt, expect: p.expect, agents: {}}; order.push(key); }
284
+ byPrompt[key].agents[a] = p;
285
+ });
286
+ });
287
+ order.sort(function (x, y) { return byPrompt[x].expect === byPrompt[y].expect ? 0 : byPrompt[x].expect === "fire" ? -1 : 1; });
288
+ var body = order.map(function (key) {
289
+ var row = byPrompt[key];
290
+ var exp = row.expect === "fire" ? '<span class="exp fire">' + esc(t.should) + "</span>" : '<span class="exp nofire">' + esc(t.shouldNot) + "</span>";
291
+ return "<tr><td class=\"p\">" + exp + "<br>" + esc(row.prompt) + "</td>" + S.agents.map(function (a) {
292
+ var p = row.agents[a];
293
+ if (!p) return '<td class="r" data-agent="' + esc(a) + '">-</td>';
294
+ var rate = p.runs ? p.fired / p.runs : null;
295
+ var good = rate == null ? "na" : row.expect === "fire" ? rcls(rate) : fcls(rate);
296
+ var other = p.other && p.other.length ? '<span class="other">' + esc(t.alsoLoaded) + " " + esc(p.other.join(", ")) + "</span>" : "";
297
+ var errs = p.errors ? ' <span class="c-err">+' + p.errors + "err</span>" : "";
298
+ return '<td class="r" data-agent="' + esc(a) + '"><span class="c-' + good + '">' + p.fired + "/" + p.runs + "</span>" + errs + other + "</td>";
299
+ }).join("") + "</tr>";
300
+ }).join("");
301
+ return '<details class="panel skill"' + (idx === 0 ? " open" : "") + "><summary><span>" + esc(s) + "</span></summary>" +
302
+ '<p class="desc">' + esc(descs[s] || "") + "</p>" +
303
+ '<table class="prompts"><thead><tr><th>' + esc(t.prompt) + "</th>" + S.agents.map(function (a) { return "<th>" + esc(a) + "</th>"; }).join("") + "</tr></thead><tbody>" + body + "</tbody></table></details>";
304
+ }).join("");
305
+
306
+ var reps = M.agents.map(function (a) { return a.name + " " + (a.repeats || M.repeats); }).join(", ");
307
+ var notes = t.notes.map(function (n) { return "<p>" + fmt(n, {turns: M.max_turns, repeats: esc(reps)}) + "</p>"; });
308
+ M.agents.forEach(function (a) {
309
+ if (a.price) notes.push("<p>" + esc(fmt(t.price, {agent: a.name, model: a.model, input: usd(a.price.input), output: usd(a.price.output), checked: a.price.checked || ""})) +
310
+ (a.price.source ? ' <a href="' + esc(a.price.source) + '">' + esc(t.source) + "</a>" : "") + "</p>");
311
+ });
312
+ if (M.blocked_egress && M.blocked_egress.length) notes.push("<p>" + esc(fmt(t.blocked, {n: M.blocked_egress.length})) + "</p>");
313
+ document.getElementById("notes").innerHTML = notes.join("");
314
+ document.getElementById("footer").innerHTML = esc(fmt(t.footer, {version: M.version, start: (M.started_at || "").replace("T", " ").slice(0, 16), dur: num(Math.round(M.duration_s || 0))})) +
315
+ ' · <a href="https://github.com/diegoperezg7/doesitfire">github.com/diegoperezg7/doesitfire</a>';
316
+ }
317
+
318
+ function applyTheme(theme) {
319
+ if (theme === "light" || theme === "dark") document.documentElement.setAttribute("data-theme", theme);
320
+ else document.documentElement.removeAttribute("data-theme");
321
+ var effective = theme || (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
322
+ document.querySelectorAll("[data-theme-set]").forEach(function (b) { b.setAttribute("aria-pressed", String(b.getAttribute("data-theme-set") === effective)); });
323
+ }
324
+ var theme = null;
325
+ try { theme = localStorage.getItem("doesitfire-theme"); } catch (e) { theme = null; }
326
+ var q = new URLSearchParams(location.search);
327
+ if (q.get("theme")) theme = q.get("theme");
328
+ if (q.get("lang") === "es" || q.get("lang") === "en") lang = q.get("lang");
329
+ applyTheme(theme);
330
+ document.querySelectorAll("[data-theme-set]").forEach(function (b) {
331
+ b.addEventListener("click", function () { theme = b.getAttribute("data-theme-set"); try { localStorage.setItem("doesitfire-theme", theme); } catch (e) {} applyTheme(theme); });
332
+ });
333
+ document.querySelectorAll("[data-lang]").forEach(function (b) {
334
+ b.addEventListener("click", function () { lang = b.getAttribute("data-lang"); try { localStorage.setItem("doesitfire-lang", lang); } catch (e) {} render(); });
335
+ });
336
+ if (window.matchMedia) window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () { applyTheme(theme); });
337
+ render();
338
+ })();
339
+ </script>
340
+ </body>
341
+ </html>