html
Skill44 starsChanged 8 months agoMITView on GitHub
Generate an interactive local React app wired to the built-in cockpit bash SDK.
- Sends data out
18 other projects use a version of this.
Adopted by
Most recently taken up by
- y-a-v-a/skillstheir own version, first seen Oct 24, 2026
- NazmusSayad/skillstheir own version, first seen Oct 13, 2026
- shreyam1008/shre-skillstheir own version, first seen Oct 9, 2026
- Mindrally/skillstheir own version, first seen Oct 8, 2026
- Pawel-Kica/ai-coding-100their own version, first seen Oct 8, 2026
- Pawel-Kica/claude-code-100their own version, first seen Sep 12, 2026
Used with
Projects with it also use thermo-nuclear-code-quality-review, prototype and 4 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
jaydubya818Skillthermo-nuclear-code-quality-review
Use when the user requests an intentionally strict maintainability review.
Also in 3 projects that use this
GoogleSkillprototype
Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
Also in 4 projects that use this
GoogleSkillto-spec
Turn the current conversation into a spec and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed.
Also in 3 projects that use this
MicrosoftSkillhandoff
Compact the current conversation into a handoff document for another agent to pick up.
Also in 4 projects that use this
- +2 more from google
Other versions18 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 18
- plannotator/effective-html★ 3.8ksame name
- Mindrally/skills★ 275same name
- eliasstravik/skills★ 22same name
- julianoczkowski/html★ 9same name
- nsmith/html★ 9same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- NazmusSayad/skills★ 2same name
- Pawel-Kica/ai-coding-100★ 2same name
- Pawel-Kica/claude-code-100★ 2same name
- arkan/skills★ 1same name
8 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 0 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 17 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 29
Every file of the app lives in that one directory, with data split into cache/ state/ out/ per section 4.
- Very specific: line 73
All four meta lines matter: the HTML panel renders cards from them, and the console opens the app via cockpit-name (/name).
- Very specific: line 159
When one command is enough and the data isn't worth keeping (just viewing current state), skip api.mjs and call cockpit.bash("curl -s …") …
“Common” is measured: kept by at least 6 in 10 of the 17 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Build a Cockpit app (a local React page)
A local application, not a one-off page: the preview injects a global window.cockpit
SDK — essentially the Bash tool exposed to the page, so buttons can curl, read/write files
and run scripts; data lands on disk inside the app directory and is there on reopen.
Zero build: React / Babel load from the same-origin /html-lib — no bundler, no deps, works offline.
1. Flow: align first, write only after confirmation (important)
Don't write files up front. Three steps:
- Align on the requirement — restate your understanding of the app (what it does, where data comes from, key features/interactions). Ask back only when the requirement is ambiguous or has several reasonable readings; if it's already clear, just give a short summary — don't interrogate.
- Tell the storage directory — resolve
<dir>/<name>/and tell the user exactly where it goes and what it's called; they may reject or change it:- You always pick
<name>from the request (lowercase kebab-case, matchingcockpit-name) — the user only describes the app, they don't name it. - The user gave a directory →
<the given directory>/<name>/; none given →<name>/under the current chat working directory (this session's cwd). - Every file of the app lives in that one directory, with data split into
cache/state/out/per section 4. Don't scatter files elsewhere or invent a.cockpit-apps.
- You always pick
- Write only after confirmation —
Writethe files once the user clearly agrees (confirm / start / go / "write it" — any affirmative). Until then, only discuss.
Even if the user already spelled out requirement and directory in the /html call, still stop
once to present "understanding + directory + a 'shall I start writing?'" and wait — but keep that
round lightweight, don't re-interrogate.
2. Skeleton: three files, copy them
index.html — use this fixed shell, don't improvise:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Page title</title>
<meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ -->
<meta name="description" content="one line about the page">
<meta name="cockpit-icon" content="🔍"> <!-- emoji or icon url, optional -->
<meta name="cockpit-theme" content="auto"> <!-- light/dark toggle, see section 5 -->
<link rel="stylesheet" href="/html-lib/theme.css">
</head>
<body>
<div id="root"></div>
<script src="/html-lib/react.production.min.js"></script>
<script src="/html-lib/react-dom.production.min.js"></script>
<script src="/html-lib/babel.min.js"></script>
<script>
(async () => {
const src = await (await fetch('./app.jsx')).text();
// MUST use classic runtime: the default automatic runtime injects an import of
// react/jsx-runtime, which — appended as a plain script — throws
// "Cannot use import statement outside a module".
const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
})();
</script>
</body>
</html>All four meta lines matter: the HTML panel renders cards from them, and the console opens the
app via cockpit-name (/name).
app.jsx — global React, render at the end. Opening reads the cache; only Refresh goes out:
const { useState, useEffect, useCallback } = React;
function App() {
const [data, setData] = useState(null);
const [busy, setBusy] = useState(false);
const [err, setErr] = useState(null);
const load = useCallback(async (refresh) => {
setBusy(true); setErr(null);
try {
const arg = btoa(JSON.stringify({ refresh: !!refresh })); // structured args as base64
const { stdout, stderr, exitCode } = await cockpit.bash(`node ./api.mjs ${arg}`);
if (exitCode !== 0) throw new Error(stderr || 'script failed'); // ran, but failed
const d = JSON.parse(stdout);
if (d.error && !d.stale) throw new Error(d.error);
setData(d);
if (d.stale) setErr(d.error); // old data is still shown
} catch (e) {
setErr(e.message); // a throw = spawn/infra failure. Keep existing data on screen
} finally { setBusy(false); }
}, []);
useEffect(() => { load(false); }, [load]); // on open: cache only, zero requests
if (!data) return <div>{err ?? 'Loading…'}</div>;
return (
<div>
<span>{data.cached ? 'cached' : 'updated'} · {new Date(data.fetchedAt).toLocaleString()}</span>
<button disabled={busy} onClick={() => load(true)}>{busy ? 'Refreshing…' : 'Refresh'}</button>
{err && <em>Refresh failed, showing older data: {err}</em>}
{/* render data */}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);api.mjs — the backend handler: fetching, parsing and persistence live here; one JSON on stdout:
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const DIR = dirname(fileURLToPath(import.meta.url));
const CACHE = join(DIR, 'cache/data.json');
const VERSION = 1; // bump whenever the payload shape changes, or old cache feeds a new UI
const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
function readCache() {
if (!existsSync(CACHE)) return null;
const c = JSON.parse(readFileSync(CACHE, 'utf8'));
return c.v === VERSION ? c : null; // version mismatch = void it, act as if empty
}
function writeCache(payload) {
mkdirSync(dirname(CACHE), { recursive: true });
const tmp = `${CACHE}.${process.pid}.tmp`; // atomic: a kill mid-write leaves no half JSON
writeFileSync(tmp, JSON.stringify({ v: VERSION, fetchedAt: new Date().toISOString(), payload }));
renameSync(tmp, CACHE);
}
try {
const hit = args.refresh ? null : readCache();
if (hit) {
process.stdout.write(JSON.stringify({ ...hit.payload, cached: true, fetchedAt: hit.fetchedAt }));
} else {
const payload = await fetchFresh(args); // ← your fetching logic (curl / DB / commands)
writeCache(payload);
process.stdout.write(JSON.stringify({ ...payload, cached: false, fetchedAt: new Date().toISOString() }));
}
} catch (e) {
const hit = readCache(); // failed refresh: fall back to the old cache, labelled
process.stdout.write(JSON.stringify(hit
? { ...hit.payload, cached: true, stale: true, error: e.message, fetchedAt: hit.fetchedAt }
: { error: e.message }));
}- When one command is enough and the data isn't worth keeping (just viewing current state), skip
api.mjsand callcockpit.bash("curl -s …")directly. The moment data should survive a reopen, use this skeleton rather than inventing your own. - Same for other languages:
python3 ./api.py/bash ./api.sh(explicit interpreter avoidschmod +x); probenode/python3withcommand -vif unsure and fall back to shell. - The cache key must include every parameter that changes the result (time range, fetch depth
…): use
cache/<param>.jsonor bucket by key inside the file — otherwise a 7-day cache renders in the 30-day view.
3. Contract: window.cockpit
The SDK is ready on load — no library to import:
cockpit.cwd: string— directory of the current file; relative-path commands run here.cockpit.bash(command, opts?)— run one bash command (commandis a raw shell string):- Foreground (default, short/discrete) →
Promise<{ stdout, stderr, exitCode }>;awaitfor the full output at once. - Background (
opts.background: true, long/live) →{ kill() }, streaming via callbacks. Large/continuous output must use background — foreground buffers it all in memory.
- Foreground (default, short/discrete) →
const h = cockpit.bash("npm run build", {
background: true,
onOutput: c => append(c),
onStderr: c => append(c),
onExit: code => append('\n[exit ' + code + ']'), // command finished; non-zero = failed
onError: msg => append('\n[error ' + msg + ']'), // spawn / infra failure
});
// h.kill() on unmount or when the user abortsRules every page must follow:
- Data only through
cockpit.bash(curlto read,curl -X POSTto write, or read/write files) — neverfetch(externalURL): the same-origin sandbox blocks it via CORS. (fetch('./app.jsx')and other same-directory relative requests are fine.) - Handle both failure modes: a throw caught by
try/catch= spawn/infra failure (bad path, dropped connection); no throw butexitCode !== 0= the command ran and failed — showstderror the error body in stdout, don't just say "failed". Background:onError(infra) + a non-zerocodeinonExit(command). commandis a shell string: validate or escape any dynamic/user input before interpolating it (shell metacharacters inject); pass structured input as base64 (see the skeleton) instead of string-building; use parameterized statements for DB writes.- Resource refs: relative siblings (
./app.jsx,./style.css, images) and absolute CDN URLs load; root-relative (/assets/x.css) 404s — the server only hosts/html-lib/*and/apps/*.
4. Local data: three directories
Data must land on disk and be there on reopen — don't refetch everything on every open. All
persistence goes through api.mjs; inside the app directory, split it by "can this be rebuilt?"
into three directories instead of one flat pile:
cache/— rebuildable (API responses, raw dumps, derived indexes). Deleting it only costs time. Gitignore the whole directory.state/— NOT rebuildable (daily snapshots, hand-maintained lists). The source can't hand it to you twice; deleted means gone. Commit it.out/— artifacts for humans (reports, exports), date-named, kept.
The split only pays off through the git policy: directories separated but cache/ not ignored and
state/ not committed means nothing was separated.
What the skeleton already handles (versioning, atomic writes, the refresh flag, the fetch
timestamp, stale fallback) isn't repeated here. What's left is on you:
- Any growing directory needs a cap enforced in the script (keep N entries / prune past X MB). A cap that lives only in a comment is not a cap — unattended dump directories reach GB scale.
- Never write absolute paths into a file on disk (store paths relative to the app directory), or moving the directory voids the index; anything holding private data (emails …) is gitignored.
- Provide a clear/reset entry point (a
clear_cachemode inapi.mjs+ a button), or the user's only option is guessing which file is safe to delete in a file manager. - To use another app's credentials / helper scripts, reference them as
../<app>/and probe that they exist first; on failure give one actionable line ("log in again at xxx") rather than dumping the raw error.
5. Theme
Style with the Cockpit theme by default (index.html already links /html-lib/theme.css).
Fetch that file once before you pick any colour — it is the single source of truth for the
variables, so don't guess names from memory:
curl -fsS "{{BASE_URL}}/html-lib/theme.css":rootholds the light values,.darkthe dark ones. The raw scales (--slate-*/--teal-*/--red-*…) store HSL components, so translucency is justhsl(var(--green-9) / .12).- Take foregrounds in pairs (
--card/--card-foreground…), except--brand:--brand-foregroundis a tint of the same hue and hits only 1.5:1 on a--brandfill — usevar(--background)for text on a brand fill. - The
cockpit-thememeta gives the preview a floating light/dark toggle (top-right by default, draggable to any corner — so don't reserve top-right space for it); it follows the Cockpit host theme by default (outside Cockpit, the meta's auto/light/dark decides), and a manual toggle is remembered per app across reloads and overrides the host, or callcockpit.toggleTheme(). - Don't invent a palette or add Tailwind; add a small
<style>for app-specific bits.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

