Safis

Usage

Install it once, then ask.

Safis follows the Agent Skills format, so any agent that reads skills can use it. You ask how something works; it plans the page, builds it, checks it in a browser, and tells you what it made.

Needs Node 22+ and Chrome, Chromium, or Edge for the checksPages need nothing: they carry their fonts and runtime3D needs WebGL 2
1Install

One command, any agent.

Works in Claude Code, Codex, OpenCode, Cursor, and any agent that reads Agent Skills.

Any agent

The skills CLI asks which of your agents to set up. Leave off -g to install for one project only.

terminal
npx skills add broisnees/safis -g

Claude Code, by hand

Clone the repo and link the skill folder into your skills directory.

terminal
git clone https://github.com/broisnees/safis
ln -s "$PWD/safis/skills/safis" ~/.claude/skills/safis

Anything else

Copy the skill folder into the folder your agent reads skills from. Its paths are relative to its own folder.

terminal
cp -r safis/skills/safis <skills-folder>/
2Ask

Name the skill, or just ask.

In Claude Code, type /safis and what you want to understand. In any agent, asking for an interactive page, a visual essay, or an explainer with figures is enough; the skill picks itself up.

The agent shares a short plan first: the question, the model every figure draws from, and one line per figure. Then it builds the page in passes, checks it, and reports one line per figure, what the checks found, and what it simplified.

The page lands where you say, or at ./<slug>/index.html. Open it in a browser; no server needed.

› /safis how a rocket engine makes thrust, in 3D, it should look real

Question: how does an open chamber of burning gas push a rocket?
Shared model: E3.parts rocketEngine, 110 mm throat, 16× bell
1. Firing at a pad. Figure (3D): throttle / the plume shortens
2. Pressure is molecules. Figure: push the piston / pressure doubles
…

✓ verify  desktop and phone, 8 figures, no errors
✓ explore 8 contact sheets read, 2 fixes
site/rocket-engine/index.html

An illustrative session, shortened.

3Prompts

How to word a prompt.

Any plain request works. These six habits get you the page you had in mind.

  1. Ask a question, not a topic. “What happens in the jaw joint when it locks?” gets a sharper page than “the TMJ”.
  2. Say what you want to see or move. “I want to turn the gears in 3D” or “let me drag the light” shapes the figures.
  3. Name the reader. “For a nursing student”, “for a curious 12-year-old”, “for an engineer” sets the pace and the terms.
  4. Say how long. In depth is the default (about 25 figures). Ask for “a short page” for 8 to 12.
  5. Ask for realism when it matters. “It should look real” means real dimensions, real materials, and the parts library.
  6. Point at sources. Link a paper or a spec sheet and the page follows it, with citations to real sources.

Example prompts, and what they get.

Pages that already exist are linked; the rest describe what the skill aims for with that request.

/safis how DNS finds a website

You get: An in-depth Computing page: the trip from your laptop to the root servers and back, referrals you step through, a map of the hops, and the caches that make it fast.

in depthmapsdiagrams
See the page →
explain how a rocket engine makes thrust, and show me the actual engine in 3D, it should look real

You get: A Machines page built on the parts library: the engine at real proportions, firing at a pad, its pumps cut open, and the thrust worked out from the physics.

3Dpartsplume
See the page →
explain how a car's differential lets the outside wheel spin faster, I want to see the gears in 3D

You get: Gears you turn and cut open in 3D, with the two wheel speeds read off as you steer through a corner.

3Dmechanism
a short interactive page on why the Pythagorean theorem is true

You get: A short piece: 8 to 12 figures and a proof you rearrange by dragging, instead of the default in-depth page.

shortmath
how does a heart valve work? go in depth, for a nursing student

You get: A Body page pitched at the reader you named, with every term defined while it's on screen.

in depthanatomyaudience
why do rainbows form, and why is the sky inside one brighter?

You get: Rays you send through a raindrop, the dispersion that splits white light, and the angle that puts the bow where it is.

opticsphysics
how do seismologists find where an earthquake started?

You get: Stations on a map, the circles their arrival times draw, and the point where the circles meet.

mapsEarth & Space
a visual essay on Diffie-Hellman key exchange

You get: The shared-secret trick as figures you run with your own numbers, from mixing colors to modular arithmetic.

computingmath
walk me through this paper's method with figures I can rerun: <link to the paper>

You get: The research arc: the question, what was known, the method as figures you run, the results reproduced live, the limits, and references with DOIs.

research
why are soap bubbles round? keep it light

You get: The same care in a lighter voice: surface tension you can stretch, and why a sphere wins.

casualphysics
make a small site of explainers on how a car works: engine, gearbox, differential, brakes

You get: Several pages in one folder, a home page grouped by category, and a shared sidebar index across them.

sitemachines
add an interactive figure of a spring and mass to my React blog post

You get: A figure spec and the component to mount it with, using the npm packages.

packagesReact
4Options

Length, reader, and realism.

The defaults, and the words that change them.

You wantSayYou get
In depth (default)nothing extraAbout 25 figures in 7 to 12 sections, from the everyday phenomenon to the full mechanism and its complications
A short piece“a short page”, “keep it short”8 to 12 figures, 1,000 to 2,000 words
A research walkthrougha link to the paperQuestion, prior work, method, results reproduced live, limits, references with DOIs
A particular reader“for a nursing student”Definitions and pace for that reader
3D that looks real“show it in 3D”, “make it look real”Real proportions, real materials, cutaways, exploded views
A place to put ita path, or an existing site folder<root>/<slug>/index.html, joined to that site
5Sites

Several pages become a site.

Put each page at <root>/<slug>/index.html and run the site script. It writes a home page grouped by category and puts the same index in every page's sidebar, which readers hide with the corner button or Ctrl B (⌘ B on a Mac).

--thumbs takes a card image of each page's first figure, in light and dark. --shared moves the runtime into one cached file for a hosted site; the pages still open from disk.

terminal
node <skill>/scripts/site.mjs site --thumbs --shared
# a home page of your own:
node <skill>/scripts/site.mjs site --shared --home home.html
6Check

Check a page yourself.

The agent runs these while it builds, and you can run them on any page. verify.mjs runs the page's self-check at desktop and phone widths and saves screenshots; explore.mjs plays with every figure into contact sheets. Without Node or Chrome, add ?check to the page's address.

What the checks catch →

terminal
node <skill>/scripts/render.mjs page.html     # diagrams and code, safe to repeat
node <skill>/scripts/verify.mjs page.html
node <skill>/scripts/explore.mjs page.html --figure plume
7In your app

The figures in React, Vue, Svelte, and Astro.

The runtime ships on npm as @safis/core, with a thin wrapper for each framework. Figure specs are identical everywhere; only the mounting differs.

terminal
npm install @safis/core @safis/react   # or @safis/vue, @safis/svelte, @safis/astro
figures/spring.js
// figures/spring.js: the same spec in every framework
export default () => ({
  controls: [{ key: "k", label: "Stiffness", min: 1, max: 10, value: 4, unit: "N/m" }],
  state: () => ({ x: 1, v: 0 }),
  step(dt, s, v) { s.v -= v.k * s.x * dt; s.x += s.v * dt; },
  draw(g, s) { g.circle([g.w / 2 + 120 * s.x, g.h / 2], 16, { fill: "blue" }); },
});
Post.jsx
import "@safis/core/scoped.css";
import { Explainer, Figure, Scrub, Tex } from "@safis/react";
import spring from "./figures/spring.js";

export default function Post() {
  return (
    <Explainer>
      <p>Give it a stiffness of <Scrub figure="spring" k="k" />:</p>
      <Figure id="spring" spec={spring} label="A block on a spring." />
      <p>One swing takes <Tex src="T = 2\pi\sqrt{m/k}" />.</p>
    </Explainer>
  );
}