lmhtml script authoring skill
Purpose
Write, compile and verify lmscript programs that inspect or transform HTML documents through the lmhtml host API.
When to use it
Use lmhtml for checking internal links, auditing CSS classes and IDs, comparing page structure, finding shared markup, or applying consistent edits across pages.
Install
Download the latest lmhtml binary from Releases. For DSL scripts, install Node.js 24.15 or newer, then use the published compiler package with npx --yes --package=@lmctl-ai/lmctl -- lmscript. Source builds need C++17, CMake, Ninja and the project setup described in the README.
Commands
lmhtml --dsl-schema
lmhtml --dsl-schema --json
lmhtml --dsl-signatures
npx --yes --package=@lmctl-ai/lmctl -- lmscript check.lms build/check.dslb
lmhtml --dsl build/check.dslb
Markdown notes use the same document tree. Native commands need only the host:
lmhtml md2html notes/chapter.md build/chapter.html lmhtml index notes lmhtml index notes --check lmhtml mdcheck notes lmhtml site notes build/notes --template shell.html
Raw HTML is disabled by default. Front matter may override title and summary. Use html.from_markdown or html.render_markdown for DSL post-processing, then html.save. Index drift and broken links fail with non-zero status. Web output must stay below the working directory and must not overlap its input.
Inputs and outputs
- Compile `_dsl_` blocks against the schema printed by lmhtml --dsl-schema --json.
- Use html.load(path) for one document and html.load_dir(path) for a host collection of documents.
- Methods return host objects such as Doc, Node, DocList and StrList; collections support len(), at(i) and foreach.
- The host writes reports, transformed HTML, or JSON as directed by script calls. A successful DSL run prints {"ok": true, "result": null}. Native index/check/site commands are quiet on success; md2html prints HTML when no output file is supplied.
Safety rules
- Only process synthetic or authorized site content. Use a scratch copy when demonstrating edits.
- External links are classified but not fetched. Decide explicitly how to handle external, special and outside-root link kinds.
- Use --dry-run for scripts that save HTML; it reports intended writes without changing files.
- Saving is constrained to the working directory and performed atomically. Keep input and output directories separate for reviewable changes.
- The bytecode VM has no execution budget or allocation cap. Run only trusted bytecode.
- There are no DSL list values; use schema collection objects and for (item in collection).
Worked examples
- Link audit: loop over html.load_dir("site"), then each document's links(); skip policy-approved kinds with html.link_kind(doc, url), and report missing internal destinations with html.link_exists(doc, url).
- Class audit: compare pages.classes_used() with pages.css_classes() using str_difference; call suggest(name, candidates) for near matches.
- Structural comparison: html.diff(before, after) ignores normalized whitespace, comments, attribute order and data-* noise.
- Template extraction: html.write_template(pages, minimum_count, output_path) emits shared subtrees and slot markers.
- Shared edit: select a node, call replace_with or set_attr, and save a copy with html.save(doc, output_path).
Errors and exit codes
Exit 0 means success. Exit 2 reports usage or bytecode compilation problems; exit 3 reports bytecode load or host runtime errors; exit 4 reports an application failure such as a report explicitly marked as failed. A missing file, invalid selector, or out-of-range collection access is a runtime failure. Compile errors point to a source location.
How to verify
- Compile using the embedded schema bytes from lmhtml --dsl-schema --json.
- Compare lmscript --dsl-signatures schema.json with lmhtml --dsl-signatures when validating a host adapter.
- Run the bytecode against a synthetic site first and inspect both the JSON result line and report file.
- For edits, use --dry-run, review the destination files, then run normally and re-diff.
- Repeat the run and compare outputs when deterministic reports matter.
Plain content for agents
Use lint to measure unnecessary decoration and entities, normalize to remove them without changing the text, and clean to extract main content for agents. Plain prose has no inline code or emphasis. Copyable commands belong in blocks. Text needs only ampersand and less-than escapes; attributes need only ampersand and quote escapes.
lmhtml lint --stats --json skills tools durable-memory
lmhtml lint --stats --compare BEFORE_DIRECTORY AFTER_DIRECTORY
lmhtml normalize skills tools
lmhtml lint durable-memory
lmhtml clean page.html --base https://example.org/docs/page.html
lmhtml clean page.html --check
lmhtml clean https://example.org/docs/page.html --markdown
clean is deterministic and offline for files. URL input uses curl and requires network access; it runs no JavaScript. Extraction removes navigation, forms, scripts, styles and common advertising/cookie containers, resolves links and prints byte counts and a bytes/4 token estimate on stderr. Heuristics can miss unusual site layouts: inspect extracted content before relying on it. clean --check compares extraction with the page's main content and fails with a diff if content would be dropped.
normalize preserves code-block alignment, removes inline typography, repairs entities and unfolds prose. lint counts violations per file and in total, with densities per 100 words; --config FILE accepts DIRECTORY METRIC MAX_COUNT rows. Defaults are zero, with h2/h3 anchors retained. The DSL equivalents are Doc.normalize() and Doc.clean(base_url).