Download app.js from smlflg/CodexMaxHarnessHowTo: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/smlflg/CodexMaxHarnessHowTo/resolve/main/app.js
- Command line
-
hf download hf://smlflg/CodexMaxHarnessHowTo/app.js
-
curl -L -o app.js https://huggingface.co/smlflg/CodexMaxHarnessHowTo/resolve/main/app.js
12.6 kB
| const rateCard = { | |
| "GPT-5.4": { input: 62.5, cached: 6.25, output: 375 }, | |
| "GPT-5.4-Mini": { input: 18.75, cached: 1.875, output: 113 }, | |
| "GPT-5.3-Codex": { input: 43.75, cached: 4.375, output: 350 }, | |
| "GPT-5.2-Codex": { input: 43.75, cached: 4.375, output: 350 }, | |
| }; | |
| const days = [ | |
| { | |
| title: "Inventar und Ziele", | |
| mode: "Scout", | |
| output: "Repo-Liste, Top-20 Task-Kandidaten, Risiko-Matrix", | |
| items: ["Codex Usage Panel pruefen", "beste Repos nach Impact sortieren", "keine Endlosjobs starten"], | |
| }, | |
| { | |
| title: "Harness-Benchmark", | |
| mode: "Scout", | |
| output: "Messbare Eval-Suite fuer Codex, Hermes, OpenCode", | |
| items: ["identische Tasksheets", "Zeit, Kosten, Fehlerklasse erfassen", "ein Scoreboard schreiben"], | |
| }, | |
| { | |
| title: "Testgenerator-Welle", | |
| mode: "Build", | |
| output: "Tests fuer riskante Module und fehlende Regressionen", | |
| items: ["erst failing tests", "dann Fixes", "Coverage-Luecken dokumentieren"], | |
| }, | |
| { | |
| title: "Refactor-Welle", | |
| mode: "Build", | |
| output: "kleinere APIs, weniger Duplication, bessere Grenzen", | |
| items: ["keine Big-Bang-Refactors", "jede Aenderung testbar", "Diffs nach Modul trennen"], | |
| }, | |
| { | |
| title: "Frontend-Finish", | |
| mode: "Ship", | |
| output: "visuell fertige lokale Tools mit Screenshot-Check", | |
| items: ["mobile und desktop pruefen", "Textueberlauf fixen", "leere States bauen"], | |
| }, | |
| { | |
| title: "Doku und Runbooks", | |
| mode: "Ship", | |
| output: "README, Setup, Troubleshooting, Agent-Prompts", | |
| items: ["nur operative Doku", "keine Markdown-Flut", "Prompts als wiederverwendbare Vorlagen"], | |
| }, | |
| { | |
| title: "Review-Tag", | |
| mode: "Verify", | |
| output: "Codex-Reviews, Security-Checks, CI-Fixes", | |
| items: ["codex review nutzen", "Findings zuerst", "nur echte Risiken fixen"], | |
| }, | |
| { | |
| title: "Final Sweep", | |
| mode: "Verify", | |
| output: "Archiv, offene Risiken, naechster Monatsplan", | |
| items: ["Zombie-Sessions schliessen", "Artefakte verlinken", "Lessons in Harness-Guide uebernehmen"], | |
| }, | |
| ]; | |
| const prompts = [ | |
| { | |
| title: "Deep Repo Audit", | |
| command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/DEIN_REPO \\ | |
| "Lies das Repo, finde die 10 hoechsten Hebel fuer Tests, Sicherheit und Wartbarkeit. Aendere noch nichts. Gib eine priorisierte, belegte Liste mit Dateien und konkreten naechsten Commands aus."`, | |
| }, | |
| { | |
| title: "Harness Eval Run", | |
| command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/HarnessEcosystem \\ | |
| "Baue eine kleine reproduzierbare Eval fuer Codex-Harness-Verhalten: Tasksheet, Messkriterien, Beispieltask, Ergebnis-Tabelle. Halte die Aenderung eng und teste lokal."`, | |
| }, | |
| { | |
| title: "Test-First Fix", | |
| command: `codex exec --full-auto -m gpt-5.4 -c model_reasoning_effort="high" -C /home/smlflg/Projekte/DEIN_REPO \\ | |
| "Finde eine reale, riskante Bugklasse. Schreibe zuerst einen failing Test, implementiere den kleinsten Fix, fuehre relevante Tests aus und erklaere den Diff."`, | |
| }, | |
| { | |
| title: "Codex Review", | |
| command: `codex review --base main "Priorisiere Bugs, Regressionen, Security-Risiken und fehlende Tests. Keine Stil-Nitpicks ohne Risiko."`, | |
| }, | |
| { | |
| title: "OpenAI Docs MCP Check", | |
| command: `codex exec --search -C /home/smlflg/Projekte/DEIN_REPO \\ | |
| "Nutze openaiDeveloperDocs, pruefe ob unsere OpenAI-API-Nutzung aktuell ist, und schlage gezielte Code-Aenderungen vor. Nur offizielle OpenAI-Doku verwenden."`, | |
| }, | |
| { | |
| title: "Finish Pass", | |
| command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/DEIN_REPO \\ | |
| "Mache aus diesem lokalen Tool ein fertiges Artefakt: README, Startanleitung, leere States, Fehlertexte, kleine UI-Politur und abschliessende Verifikation."`, | |
| }, | |
| ]; | |
| const lanes = ["Scout", "Build", "Verify", "Ship"]; | |
| const defaultTasks = [ | |
| { id: "seed-mcp-docs", title: "Codex MCP Docs in bestehende OpenAI-Projekte einbauen", lane: "Scout", model: "GPT-5.4" }, | |
| { id: "seed-promptgarage-eval", title: "PromptGarage: Eval-DB pruefen und Judge-Runs automatisieren", lane: "Build", model: "GPT-5.3-Codex" }, | |
| { id: "seed-token-diaet", title: "HarnessEcosystem: TOKEN_DIAET mit neuen Codex-Rate-Card-Daten aktualisieren", lane: "Ship", model: "GPT-5.4" }, | |
| { id: "seed-static-sites", title: "Alle privaten Static-Sites auf mobile Textueberlaeufe pruefen", lane: "Verify", model: "GPT-5.4-Mini" }, | |
| ]; | |
| const formatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 0 }); | |
| const preciseFormatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }); | |
| let taskState = loadTasks(); | |
| function escapeHtml(value) { | |
| return String(value) | |
| .replaceAll("&", "&") | |
| .replaceAll("<", "<") | |
| .replaceAll(">", ">") | |
| .replaceAll('"', """) | |
| .replaceAll("'", "'"); | |
| } | |
| function createId() { | |
| if (globalThis.crypto?.randomUUID) return crypto.randomUUID(); | |
| return `task-${Date.now()}-${Math.random().toString(16).slice(2)}`; | |
| } | |
| function normalizeTask(task) { | |
| const lane = lanes.includes(task?.lane) ? task.lane : "Scout"; | |
| const model = Object.hasOwn(rateCard, task?.model) ? task.model : "GPT-5.4"; | |
| return { | |
| id: String(task?.id || createId()), | |
| title: String(task?.title || "Unbenannter Task").slice(0, 240), | |
| lane, | |
| model, | |
| }; | |
| } | |
| function loadTasks() { | |
| try { | |
| const saved = localStorage.getItem("codex-max-harness-tasks"); | |
| if (!saved) return defaultTasks.map(normalizeTask); | |
| const parsed = JSON.parse(saved); | |
| return Array.isArray(parsed) ? parsed.map(normalizeTask) : defaultTasks.map(normalizeTask); | |
| } catch { | |
| return defaultTasks.map(normalizeTask); | |
| } | |
| } | |
| function numberFrom(id) { | |
| return Number(document.getElementById(id).value || 0); | |
| } | |
| function renderTimeline() { | |
| document.getElementById("timeline").innerHTML = days | |
| .map( | |
| (day, index) => ` | |
| <article class="day-card"> | |
| <div class="day-kicker"><span class="dot"></span>Tag ${index + 1} · ${escapeHtml(day.mode)}</div> | |
| <h3>${escapeHtml(day.title)}</h3> | |
| <p><strong>Output:</strong> ${escapeHtml(day.output)}</p> | |
| <ul>${day.items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul> | |
| </article> | |
| `, | |
| ) | |
| .join(""); | |
| } | |
| function renderModels() { | |
| const select = document.getElementById("modelSelect"); | |
| select.innerHTML = Object.keys(rateCard) | |
| .map((model) => `<option value="${model}">${model}</option>`) | |
| .join(""); | |
| } | |
| function updateBudget() { | |
| const remaining = numberFrom("remainingCredits"); | |
| const daysLeft = Math.max(1, numberFrom("remainingDays")); | |
| const buffer = Math.min(40, Math.max(0, numberFrom("bufferPercent"))) / 100; | |
| const blocks = Math.max(1, numberFrom("blocksPerDay")); | |
| const usable = remaining * (1 - buffer); | |
| const daily = usable / daysLeft; | |
| const block = daily / blocks; | |
| document.getElementById("usableCredits").textContent = formatter.format(usable); | |
| document.getElementById("dailyCredits").textContent = formatter.format(daily); | |
| document.getElementById("blockCredits").textContent = formatter.format(block); | |
| } | |
| function updateEstimate() { | |
| const selected = document.getElementById("modelSelect").value; | |
| const rate = rateCard[selected]; | |
| const input = numberFrom("inputTokens") * rate.input; | |
| const cached = numberFrom("cachedTokens") * rate.cached; | |
| const output = numberFrom("outputTokens") * rate.output; | |
| const multiplier = document.getElementById("fastMode").checked ? 2 : 1; | |
| const total = (input + cached + output) * multiplier; | |
| document.getElementById("estimatedCredits").textContent = preciseFormatter.format(total); | |
| document.getElementById("estimateDetail").textContent = | |
| `${selected}: Input ${rate.input}, Cached ${rate.cached}, Output ${rate.output} Credits pro 1M Tokens`; | |
| } | |
| function getTasks() { | |
| return taskState; | |
| } | |
| function saveTasks(tasks) { | |
| taskState = tasks.map(normalizeTask); | |
| try { | |
| localStorage.setItem("codex-max-harness-tasks", JSON.stringify(taskState)); | |
| } catch { | |
| // Keep the in-memory board working when file:// or private mode blocks storage. | |
| } | |
| } | |
| function moveTask(id, direction) { | |
| const tasks = getTasks(); | |
| const task = tasks.find((item) => item.id === id); | |
| if (!task) return; | |
| const index = lanes.indexOf(task.lane); | |
| const nextIndex = Math.min(lanes.length - 1, Math.max(0, index + direction)); | |
| task.lane = lanes[nextIndex]; | |
| saveTasks(tasks); | |
| renderKanban(); | |
| } | |
| function deleteTask(id) { | |
| saveTasks(getTasks().filter((task) => task.id !== id)); | |
| renderKanban(); | |
| } | |
| function renderKanban() { | |
| const tasks = getTasks(); | |
| document.getElementById("kanban").innerHTML = lanes | |
| .map((lane) => { | |
| const laneTasks = tasks.filter((task) => task.lane === lane); | |
| return ` | |
| <section class="lane" aria-label="${escapeHtml(lane)}"> | |
| <h3>${escapeHtml(lane)}<span class="lane-count">${laneTasks.length}</span></h3> | |
| ${laneTasks | |
| .map( | |
| (task) => ` | |
| <article class="task-card"> | |
| <strong>${escapeHtml(task.title)}</strong> | |
| <div class="task-meta"> | |
| <span class="tag">${escapeHtml(task.model)}</span> | |
| <span class="tag alt">${escapeHtml(task.lane)}</span> | |
| </div> | |
| <div class="task-actions"> | |
| <button type="button" data-task-move="${escapeHtml(task.id)}" data-direction="-1">Zurueck</button> | |
| <button type="button" data-task-move="${escapeHtml(task.id)}" data-direction="1">Weiter</button> | |
| <button type="button" data-task-delete="${escapeHtml(task.id)}">Loeschen</button> | |
| </div> | |
| </article> | |
| `, | |
| ) | |
| .join("")} | |
| </section> | |
| `; | |
| }) | |
| .join(""); | |
| } | |
| function renderPrompts() { | |
| document.getElementById("promptGrid").innerHTML = prompts | |
| .map( | |
| (prompt, index) => ` | |
| <article class="prompt-card"> | |
| <h3>${escapeHtml(prompt.title)}</h3> | |
| <pre><code>${escapeHtml(prompt.command)}</code></pre> | |
| <button class="button ghost small" type="button" data-copy="${index}">Kopieren</button> | |
| </article> | |
| `, | |
| ) | |
| .join(""); | |
| } | |
| function fallbackCopy(text) { | |
| const area = document.createElement("textarea"); | |
| area.value = text; | |
| area.setAttribute("readonly", ""); | |
| area.style.position = "fixed"; | |
| area.style.left = "-9999px"; | |
| document.body.append(area); | |
| area.select(); | |
| try { | |
| return document.execCommand("copy"); | |
| } finally { | |
| area.remove(); | |
| } | |
| } | |
| async function copyText(text) { | |
| try { | |
| if (navigator.clipboard?.writeText) { | |
| await navigator.clipboard.writeText(text); | |
| return true; | |
| } | |
| } catch { | |
| // Fall through to the textarea fallback for file:// contexts. | |
| } | |
| return fallbackCopy(text); | |
| } | |
| function attachEvents() { | |
| document.querySelectorAll("#budgetForm input").forEach((input) => { | |
| input.addEventListener("input", updateBudget); | |
| }); | |
| document.querySelectorAll("#costForm input, #costForm select").forEach((input) => { | |
| input.addEventListener("input", updateEstimate); | |
| input.addEventListener("change", updateEstimate); | |
| }); | |
| document.getElementById("taskForm").addEventListener("submit", (event) => { | |
| event.preventDefault(); | |
| const title = document.getElementById("taskTitle").value.trim(); | |
| if (!title) return; | |
| const tasks = getTasks(); | |
| tasks.push({ | |
| id: createId(), | |
| title, | |
| lane: document.getElementById("taskLane").value, | |
| model: document.getElementById("taskModel").value, | |
| }); | |
| saveTasks(tasks); | |
| event.currentTarget.reset(); | |
| renderKanban(); | |
| }); | |
| document.getElementById("kanban").addEventListener("click", (event) => { | |
| const moveButton = event.target.closest("[data-task-move]"); | |
| if (moveButton) { | |
| moveTask(moveButton.dataset.taskMove, Number(moveButton.dataset.direction)); | |
| return; | |
| } | |
| const deleteButton = event.target.closest("[data-task-delete]"); | |
| if (deleteButton) { | |
| deleteTask(deleteButton.dataset.taskDelete); | |
| } | |
| }); | |
| document.getElementById("promptGrid").addEventListener("click", async (event) => { | |
| const button = event.target.closest("[data-copy]"); | |
| if (!button) return; | |
| const prompt = prompts[Number(button.dataset.copy)]; | |
| const copied = await copyText(prompt.command); | |
| button.textContent = copied ? "Kopiert" : "Nicht kopiert"; | |
| window.setTimeout(() => { | |
| button.textContent = "Kopieren"; | |
| }, 1200); | |
| }); | |
| } | |
| renderTimeline(); | |
| renderModels(); | |
| renderKanban(); | |
| renderPrompts(); | |
| attachEvents(); | |
| updateBudget(); | |
| updateEstimate(); | |