Tool apps deploy as one HTML file with embedded CSS and JS, plus an AI-agent skill beside it.
dev/tools/deploy.ts publishes both as Secutils webhook responders on {{TOOLS_HOST}} (tools.secutils.dev).
MARS is the one bundled app, on its own host: read guides/mars.md before changing it.
The main CI workflow ignores dev/tools/**.
The MARS workflow covers js/mars/ and the shared foundation with build, typecheck, unit, and search-quality checks.
Run the tool verification and browser gates below locally.
- Files:
<name>.htmland<name>.skill.md. The filename need not match the URL path. - Metadata: the
su-tool-path,su-tool-name,su-tool-description, andsu-tool-promote<meta>tags are the source of truth fordeploy.ts,scripts/tools-check.ts, and the marketing site.e2e/tools/registry.tsrepeats those four fields beside its OG and E2E data, so update its row in the same change. - Hosts: write
{{TOOLS_HOST}}(and{{MARS_HOST}}in MARS) wherever a page or skill names its host;deploy.tssubstitutes them. The Plausible script URL is the one literal host (guides/chrome.md). - Dependencies: CDN-hosted libraries, or an npm package bundled through
data-su-bundle(guides/platform.md). Deployment inlines shared styles and glyphs for migrated tools. - State: reload-safe and shareable state lives in the URL fragment, in the shared encoding (guides/url-state.md).
- Browsers: current Chrome, Firefox, Safari, and Edge. Use a Baseline Newly Available feature when it replaces custom code.
For an engine that still lacks it, add a fallback only when it is at most 20 lines, needs no polyfill or extra script,
and the tool keeps working without it; otherwise stay on the existing pattern.
Limited Availability features are out of scope, except the File System Access API as a feature-detected enhancement in
md.htmland MARS. - Page:
<title>Tool Name | Secutils.dev</title>,data-theme="dark"on<html>with a theme toggle, and the shared chrome. MARS, ECHO, and Webhook Inspector use the shared design system.md.htmlremains the reference for unmigrated tools. Known deviations:mock-saml-idp.html(pre-banner chrome), andindex.htmlandcertificate-decoder.html(ES5 syntax).
Read every guide whose surface the change touches.
| Surface | Guide |
|---|---|
su-tool-* metadata, promotion, hosts, @su:responder-script, dev/tools/js/ bundles, responder setup |
guides/platform.md |
| URL-fragment state, share links, short links | guides/url-state.md |
*.skill.md, llms.txt, robots.txt, sitemap.xml, agent-skills index, Accept: text/markdown, header Skill button |
guides/agent-surface.md |
<head> SEO tags, <noscript>, bottom CTA, OG images |
guides/seo-og.md |
Colors, typography, header, buttons, panes, toasts, footer, analytics, responsive rules, fetchpriority, exports and PDF |
guides/chrome.md |
| Shared CSS or glyphs, MARS chrome, migration of tool styling | guides/design-system.md |
| Privacy and Credits dialogs, popovers, form validation | guides/components.md |
| Tool JavaScript: style, long tasks, busy state, polling | guides/javascript.md |
| MARS | guides/mars.md |
- Edit the HTML. Update the skill in the same change when the edit alters what an agent can do or how it builds a URL.
- Run
make tools-verify ARGS="dev/tools/<name>.html": inline scripts and the responder script parse, and the minifier accepts the page. - For URL-state changes, round-trip representative payloads (small, large, Unicode, the default state):
decodeState(await encodeState(x)) === x. - For responder-script changes, run
make e2e-standalone-testwhen a smoke spec covers the tool (e2e/standalone/md_responder_script_smoke.spec.tscoversmd.htmlandmars.html). Otherwise run the script undernode:vmwith a stubsecutils/Deno.corefor a valid input, a malformed input (a clean error response), and a missing input (null, so the static body is served). - Run
make tools-check. - Deploy when the user asks:
make deploy-tools ARGS="<name>". Every run also refreshes the tools-host aggregates. - After a deploy, run the tool's spec against the live host:
make e2e-tools-test ARGS="<slug>.spec.ts".
Completion: steps 2-5 pass, step 7 passes after a deploy, and the skill, registry row, and README describe the changed behavior. Report any step you could not run as unverified.
- Create two responders, one for the page and one for the skill, and record their IDs in
.envasSECUTILS_HTML_APP_RESPONDER_ID_<NAME>andSECUTILS_HTML_APP_RESPONDER_ID_<NAME>_MD(<NAME>is the filename stem in upper snake case). Responder location setup is in guides/platform.md. - Write
<name>.html: metadata (guides/platform.md), the SEO head (guides/seo-og.md), the shared chrome (guides/chrome.md), the dialogs (guides/components.md), and the Skill button (guides/agent-surface.md). - Write
<name>.skill.md(guides/agent-surface.md). - Add the
e2e/tools/registry.tsrow ande2e/tools/<slug>.spec.ts, starting fromjwt.spec.ts. - For a promoted tool, add an
index.htmlcard with its JSON-LDItemListentry, a README "Free single-page tools" row, and a card in the marketing site's#free-toolssection (path inSECUTILS_TOOLS_PROMO_HOME_INDEX). The aggregates regenerate on deploy. - Generate the OG pair:
make tools-og ARGS='-g "OG image for <slug>"'. - Run steps 2-7 of "Change a tool".
Completion: make tools-check passes, the new spec passes against the deployed tool,
and og-<slug>.png and og-<slug>-light.png exist in components/secutils-docs/static/img/og/.