- Home
- Plugin
- Strumenti e capacità
- dsh-card-export
dsh-card-export
7starsseeker/dsh-card-export
Render any Markdown report or DSH session excerpt into shareable PNG images with no npm dependencies, driving the Chromium-family browser already on the machine and falling back to PDF, then HTML. Reports come out as a summary card plus a full verbatim long image; a report written for dsh-fact-check also gets the tier badge, the two rails and the per-claim tiering, all taken from the report itself, and a report following one of the other report skeletons it defines gets the section that skeleton designates as its conclusion lifted onto the summary card. Conversations come out as a message flow, exportable by turn range, event range or the most recent N turns.
Installazione
dsh plugin --profile web add github:7starsseeker/dsh-card-exportREADME
dsh-card-export
中文版:README.zh-CN.md
Turn Markdown reports and DSH conversations into shareable images. A DeepSeek Harness plugin with two skills.
Zero npm dependencies. No bundled browser, no Chromium download: it drives the Chromium-family browser already on the machine (Edge → Chrome → Brave/Chromium/Vivaldi), degrades to PDF, then delivers HTML.
Contents
- What it is
- Two skills
- Requirements
- Install
- Usage
- Markdown support
- HTML and math
- Long images and cutting
- Configuration
- How it works
- Verify it yourself
- Limits
- Project layout
- Contributing
- License
What it is
Two things on one rendering base:
- Report → two images. Any Markdown report rendered through the
reportpreset (the default) produces a summary card plus a full verbatim long image. - Conversation → one image. A selected span of a DSH session becomes a message-flow image, with user turns and assistant replies; thinking blocks and tool calls are optional.
A single image only happens when you ask for it: --preset generic, meant for non-report documents (notes, explanations, checklists).
Rendering only, never rewriting: what is not in the report does not appear in the image, and nothing in the conversation is invented.
Two skills
| Skill | What it does |
|---|---|
report-card (RPC) | Any Markdown report → summary image + full verbatim long image |
transcript-card (TSC) | DSH session excerpt → message-flow long image |
report-card: why two images
The preset decides the output, not the content: report (the default) gives two images, generic (explicit) gives one.
- Summary image — as much information as possible in a small space. The conclusion is written by the model after reading the report (
--brief); the renderer only lays it out. Without a brief it degrades to an explicitly labelled "extracted from source" card. - Full image — the report's own structure, order and wording, section by section. No reordering, no omission, no rewriting, no summarising on the author's behalf.
Reports from dsh-fact-check get a skin within the report preset: the summary card also carries the tier breakdown, the two rails (甲轨/乙轨), and the one-sentence takeaway — but tiers and rails always come from the report itself, never from a brief. The skin is detected from that report's own headings (two of ## 一句话结论 / ## 哪些是真的 / ## 来源), so the coupling is owned by the fact-check template, not by this plugin. English reports get the same treatment: the equivalent anchors are One-line verdict / What is true, what is false / Sources, the same skin and the same verdict semantics apply, and only the labels switch language (Confirmed / Likely true / Unconfirmed / False / Unverifiable, plus footers, section titles and the "must not be cited" warning). The language is detected from the report's own headings and can be forced with --lang zh|en; Chinese output is byte-identical to before.
Since fact-check 2.0.0 a report can take one of six skeletons — and only the verdict one has export anchors. Definition / causal / comparison / map / metric reports have no anchors and fall back to a plain document (two images, nothing dropped). That is what fact-check explicitly asks for ("do not invent anchors for the other skeletons"), so do not add them for looks: a definition report would come out as a verdict with a tier badge. Two shapes that only exist in 2.0.0 are handled: the report's own preamble (a 【定型】 framing card, a 定型:A → B re-typing note, or a verification-basis statement) is drawn verbatim at the top of the long image, and sections belonging to another skeleton (Step 4c multi-product reports) are appended verbatim in source order instead of being silently dropped — the summary card says how many there are.
The five non-verdict skeletons get a summary card too, by a different route: the renderer does not parse their structure and does not invent tier badges — it lifts the section the skeleton itself designates as its conclusion onto the summary card (definition → ## 一句话定义, map → ## 核心发现, metric → ## 数值, causal → ## 主因 with ## 落地概率 / ## 因果链 as fallbacks, comparison → ## 分场景结论), labelled <skeleton> · <section>. Detection requires two of that skeleton's own headings, so shared headings (## 定界, ## 来源, ## 缺口, ## 可比性检验) never trigger it, and anything it cannot identify falls back to exactly the old plain document. In the long image, sections with a fixed semantic slot become enhanced blocks: metric's ## 数值 → number cards, map's ## 确定性分层 → tier rows, causal's ruled-out causes / comparison's ## 不可比 / definition's ## 常见误解 → grey blocks. Only the layout changes — every word still comes verbatim from the report, in order. Both tables live in lib/modes/skeletons.mjs as one declarative table, not five parsers. The table comes from upstream: dsh-fact-check declares it in reference/skeleton-anchors.json, and its own lint-skill keeps it in step with the headings in SKILL.md (rename a heading and its assertions go red). This plugin reads that file first; if it cannot (older version, package without reference/) it uses a built-in copy of the same content; if even that fails to identify the report it falls back to the plain document — three levels of degradation, with FACT_CHECK_ANCHORS=<path> as an explicit override. Matching follows upstream's stripTrailingParenThenExact: strip the trailing parenthetical, then compare for equality — no prefix, no substring (otherwise 边界 would swallow 边界与反例). 来源 is reserved for the verdict skeleton; 定界 / 缺口 / 可比性检验 may not be used by any skeleton.
transcript-card: faithful to what you saw
With GenUI installed, ```dsh-ui blocks render as cards in the conversation. Fidelity therefore means what you saw, so cards are rendered by default:
| Level | Trigger | Result |
|---|---|---|
| ① Render as card | Parses, component supported | Card (callout / list / keyvalue / table / stat / badge / steps / …) |
| ② Per-item fallback | One component unrecognised | "Unrecognised component, kept verbatim" + its JSON; the rest still renders |
| ③ Whole-block fallback | JSON invalid, or no items | Falls back to a code block, character-for-character |
Pass --no-render-ui to always see raw JSON.
Requirements
- Node.js ≥ 22.15 (session logs are zstd-compressed;
node:zlibgained zstd in 22.15) - One Chromium-family browser for rasterisation: Edge / Chrome / Brave / Chromium / Vivaldi
- DSH (as a plugin), or call the CLIs under
tools/directly
Without a browser it still works, but only PDF or HTML is produced.
Install
As a DSH plugin:
dsh plugin --profile desktop add dsh-card-export
Local development (edits take effect on restart; the marketplace marks it "local dev"):
dsh plugin --profile desktop add T:\dsh-card-export
Restart DSH; report-card and transcript-card appear in the skill catalog.
Without the plugin:
git clone https://github.com/7starsseeker/dsh-card-export.git
cd dsh-card-export
node tools/doctor.mjs
Usage
Reports
# inspect the parse first (no image)
node tools/render-report.mjs report.md --out ./out --dump
# summary + full image
node tools/render-report.mjs report.md --out ./out --png --width 960
# with a model-written summary (recommended)
node tools/render-report.mjs report.md --out ./out --brief brief.json --png
# HTML only (no browser needed)
node tools/render-report.mjs report.md --out ./out
brief.json:
{
"conclusion": "One sentence: what this report actually says",
"points": ["thing 1 worth knowing", "thing 2"],
"numbers": [{ "label": "metric", "value": "61% → 84%" }],
"caveat": "what this report does not cover"
}
Conversations
# list turns and event counts first
node tools/render-transcript.mjs session.v4.jsonl.zstd --out ./out --dump
# last 3 turns
node tools/render-transcript.mjs <session> --out ./out --last 3 --png
# specific turns / event range
node tools/render-transcript.mjs <session> --out ./out --turns 2-5 --png
node tools/render-transcript.mjs <session> --out ./out --seq 120-260 --png
# quote-based anchors: from "this line" to "that line"
node tools/render-transcript.mjs <session> --out ./out --from "first line" --to "last line" --png
Session files live at <DSH_HOME>/sessions/<workspace>/<session-id>/session.v4.jsonl.zstd.
Display levels mirror the client's "work steps" setting (settings.transcript.*):
--level | Exported |
|---|---|
compact | last reply per turn + user instructions |
standard (default) | all assistant replies + user instructions |
detailed | + tool calls (name and argument summary) |
verbose | + full thinking + tool results |
Markdown support
Supported: headings #–##### and Setext ===; paragraphs; ordered/unordered lists (continuation lines, nesting, and tables inside a list item after a blank line + indent — the GFM rule); tables (\| escapes, optional leading/trailing pipes, alignment); blockquotes; fenced code; thematic breaks; footnotes ([^1] with a notes section at the end); inline bold / italic / strikethrough / code / links / autolinks / image references / [n] citation superscripts.
Code blocks are always literal — LaTeX, HTML and Markdown inside a fence are shown as written, never rendered.
Not supported: footnotes inside tables, math via $…$ (see below), raw-HTML-only constructs (forms, iframes).
HTML and math
HTML: asked before rendering, never assumed
Raw HTML in a document can really execute in the headless browser that takes the screenshot. So the tool does not decide for you:
- Run the export. If the document contains raw HTML, the command exits with code 4 and writes no files, returning a question:
"htmlAsk": {
"detected": 304,
"breakdown": ["MathML 304 tags", "style tags 3 (<br> <mark> <span>)"],
"hasActive": false,
"question": "This document contains raw HTML (…). Should it take effect? …",
"options": [
{ "flag": "--html on", "meaning": "take effect (colours, <br>, <mark>, MathML)" },
{ "flag": "--html off", "meaning": "show everything literally (untrusted sources)" }
]
}
- The skill relays that question to the user.
- Re-run with the answer:
node tools/render-report.mjs report.md --out ./out --png --html on
node tools/render-report.mjs report.md --out ./out --png --html off
Detection is reported by category: math (MathML), style (span/br/mark/div/table/…), active (script/iframe/img/link/form/… — these additionally raise a "prefer literal" warning), other.
--preflight detects without rendering. Plain-text documents never trigger the question. Transcript dsh-ui cards are always escaped, regardless of --html.
Math: MathML, zero dependency
Chromium ships a MathML layout engine, so formulas written as MathML are typeset with no KaTeX, no network, no extra dependency.
| Want | Write |
|---|---|
| sum/product limits above and below | <math displaystyle="true"> (otherwise limits sit to the right) |
| brackets that grow with content | <mo stretchy="true">(</mo> … <mo stretchy="true">)</mo> |
Measured on 8 common formula shapes plus a long nested one (weighted cross-entropy): semantics preserved in every case; displaystyle and stretchy fix the two layout gaps versus LaTeX.
$…$ / $$…$$ are not interpreted — a model that wants typeset math should emit MathML. With --html off, formulas show as literal source.
Long images and cutting
The output is always one image. Rendering never splits.
When the image is long (> 12000 px) the result carries a tooLong block (height + every divider position + a question). The skill asks whether to cut; only after a yes may crop-image run:
node tools/crop-image.mjs image.png --lines # list divider positions
node tools/crop-image.mjs image.png --at 7,11 --confirmed # cut at lines 7 and 11
node tools/crop-image.mjs image.png --max 5000 --confirmed # keep each part ≤ 5000 px
--confirmed is a hard gate: without it the tool exits 3 and writes nothing. Cuts land on the drawn dividers — a full-bleed hairline between ## sections in reports and between turns in conversations — and the line itself is dropped from the seam, so no grey edge and no half sentence survives.
Configuration
Optional, outside the package (survives upgrades):
Windows %LOCALAPPDATA%\dsh-card-export\config.json
Other $XDG_CONFIG_HOME/dsh-card-export/config.json
{ "skills": { "report-card": true, "transcript-card": true } }
Set a skill to false and it stops being registered (saves context). node tools/config.mjs --list | --disable <id> | --init.
The first-run probe result is written to readiness.json in the same directory.
How it works
Markdown report ──┐
├─► parse ─► HTML (skin) ─► headless browser ─► PNG
DSH session ──────┘ │
└─► fallback ─► PDF ─► HTML
Container note: Linux paths are probed too; if the first attempt fails with sandbox symptoms (No usable sandbox, /dev/shm), --no-sandbox --disable-dev-shm-usage is appended for a single retry. Those flags weaken isolation, so they are never defaults.
Verify it yourself
npm run smoke # twelve suites, 484 assertions
npm run doctor # render-capability diagnosis (renders once for real)
The twelve suites cover: browser probe and container retry rules; first-run probe and advice matrix; report presets and brief sources; transcript parsing and four levels; dsh-ui cards and three-level fallback; fact-check detection and the no-promotion rule; the fact-check 2.0.0 contract (verbatim anchors, missing-section accounting, preamble and appendix sections reaching the image); plugin contract and per-skill toggles; divider detection and cropping; content coverage (every source line must appear in the output); Markdown conformance (30 constructs + explicit known limits + HTML detector); math (MathML structure, displaystyle/stretchy, long nesting, fenced LaTeX stays literal).
Limits
- Render only. Typos and wrong figures in the source appear unchanged in the image.
- Summary quality equals brief quality. The renderer never summarises.
- Missing sections are not filled in. They are listed at the end of the full image.
- No bundling. No fonts, no browser, no dependencies.
- Never split. One image; cut along the dividers if you need segments.
- DSH-specific. Conversation export depends on
session.v4.jsonl.zstdfields; field changes fail loudly. - Markdown subset. Footnotes inside tables,
$…$math, and raw-HTML-only constructs are out of scope. - HTML passthrough executes. Only render documents you wrote or the model produced; use
--html offotherwise.
Operational hints (what was excluded, which flag includes it) are printed to stderr, never baked into the image.
Project layout
lib/
index.js plugin: mount + skill registration + background first-run probe
first-run.mjs idempotent first-run probe (readiness.json)
user-config.mjs per-skill toggles outside the package
render/
rasterize.mjs browser probe → PNG / PDF / HTML
check.mjs real capability probe + advice matrix
png.mjs zero-dependency PNG read/write + row cropping
blank-height.mjs read-only blank measurement, used to correct screenshot height
modes/
markdown.mjs block/inline Markdown subset, HTML passthrough switch, footnotes
skin-report.mjs shared report skin
report.mjs report entry: preset detection, brief, dividers
verdict.mjs fact-check report: cover / detail
transcript.mjs conversation: session parsing, range selection, flow rendering
dsh-ui.mjs mini card renderer (three-level fallback)
skills/
report-card/SKILL.md
transcript-card/SKILL.md
tools/ doctor / config / render-* / crop-image / smoke-*
cases/ samples: report, generic, conversation, conformance, math
Contributing
See CONTRIBUTING.md. Security notes in SECURITY.md, release flow in RELEASING.md.
License
Apache License 2.0. Third-party notice in NOTICE.
Templates and CSS are original to this repository.
Plugin correlati
archify (deepseek-harness)
tt-a1i/archify
WeKnora (dsh-weknora)
tencent/weknora
next-ai-draw-io (dsh-plugin)
dayuanjiang/next-ai-draw-io
weknora
tencent/weknora