
Kami
- 10.3k installs
- 10.3k repo stars
- Updated August 1, 2026
- tw93/kami
kami is an agent skill that Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, in.
About
Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN uses TsangerJinKai02, EN uses Charter, JA uses YuMincho (best-effort). Triggers on "做 PDF / 排版 / 一页纸 / 白皮书 / 作品集 / 简历 / PPT / slides / Marp / markdown slides / マークダウンのスライド / 落地页 / 官网 / landing page / p --- name: kami description: 'Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN uses TsangerJinKai02, EN uses Charter, JA uses YuMincho (best-effort). Triggers on "做 PDF / 排版 / 一页纸 / 白皮书 / 作品集 / 简历 / PPT / slides / Marp / markdown slides / マークダウンのスライド / 落地页 / 官网 / landing page / product page", or "build me a resume / make a one-pager / design a slide deck / turn this into a PDF / make this presentable / create a landing page".' --- # kami · 紙 **紙 · かみ** - the paper your deliverables land on.
- Locate the referenced project's style files:
- Extract: dominant color values (hex / hsl), font stack, spacing scale, border-radius scale. Prefer values declared in CS
- Report back in one line before continuing: "scanned <project>, extracted N colors / M fonts; using as visual reference."
- If the conversation already answered a dimension, skip it silently.
- If a dimension can be inferred from the document type (e.g. resume purpose is always "get an interview"), skip it.
Kami by the numbers
- 10,329 all-time installs (skills.sh)
- +299 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #63 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
kami capabilities & compatibility
- Capabilities
- locate the referenced project's style files: · extract: dominant color values (hex / hsl), font · report back in one line before continuing: "scan · if the conversation already answered a dimension · if a dimension can be inferred from the document
- Use cases
- documentation
What kami says it does
--- name: kami description: 'Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages.
Warm parchment, ink-blue accent, serif-led hierarchy.
CN uses TsangerJinKai02, EN uses Charter, JA uses YuMincho (best-effort).
One design language across eight document types: warm parchment canvas, ink-blue accent, serif-led hierarchy, tight editorial rhythm.
npx skills add https://github.com/tw93/kami --skill kamiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10.3k |
|---|---|
| repo stars | ★ 10.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 1, 2026 |
| Repository | tw93/kami ↗ |
What problem does kami solve for developers using this skill?
Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN u
Who is it for?
Developers who need kami patterns described in the cached skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill's documented scope.
When should I use this skill?
Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN u
What you get
Actionable workflows and conventions from SKILL.md for kami.
- Typeset resume
- White paper
- Slide deck
By the numbers
- Supports 6 document types: resumes, one-pagers, white papers, letters, portfolios, slide decks
Files
kami · 紙
紙 · かみ - the paper your deliverables land on.
Good content deserves good paper. One design language across eight document types: warm parchment canvas, ink-blue accent, serif-led hierarchy, tight editorial rhythm.
Part of Kaku · Waza · Kami - Kaku writes code, Waza drills habits, Kami delivers documents.
Update check (non-blocking). At the start of a task, run bash scripts/check-update.sh. It does a read-only version check at most once per day and prints one line when a newer kami is available; relay that line to the user, then continue. It sends no data, and fails silently when offline, sandboxed, or without curl. Never let it block the work.
Step 0 · Load brand profile (if exists)
Check ~/.config/kami/brand.md (preferred) or ~/.kami/brand.md (legacy fallback). If found, read references/brand-profile.md for the full four-layer application spec (placeholder substitution, session defaults, visual customization, habit notes) and its six guardrails. If no profile exists, continue without interruption.
Key rule: explicit prompt > editorial judgment > habit notes > frontmatter defaults > built-in defaults. Profile fills gaps silently; it never overrides the current conversation.
Step 0.5 · User project style scan (opt-in)
Run this only when the user explicitly references a sibling project as a visual reference: "like my <project> site", "match the style of <repo>", "use the look from <directory>". Skip silently when no such reference exists.
When triggered, before generating:
1. Locate the referenced project's style files:
find <referenced-path> -maxdepth 4 \( -name "*.css" -o -name "tailwind.config.*" -o -name "theme.*" -o -name "tokens.*" \) | head -202. Extract: dominant color values (hex / hsl), font stack, spacing scale, border-radius scale. Prefer values declared in CSS variables or design tokens over inline literals. 3. Merge into the in-session brand profile as Layer C (visual customization), not Layer B (session defaults). Do not override an explicit --brand flag or values that the user typed in this turn. 4. Report back in one line before continuing: "scanned <project>, extracted N colors / M fonts; using as visual reference."
Skip and fall back to the brand profile defaults if the referenced path does not exist, no CSS-like files are found, or the extraction would conflict with the user's explicit values in the current message.
---
Step 1 · Decide the language
Match the user's language. Chinese -> *.html / slides-weasy.html. English -> *-en.html / slides-weasy-en.html. Japanese -> CJK path (.html / slides-weasy.html) as best-effort, JP Mincho first, visual QA before shipping. Korean -> dedicated *-ko.html / slides-weasy-ko.html family as best-effort, visual QA before shipping. Reference docs are shared English specs.
When ambiguous (e.g. a one-word command like "resume"), ask a one-liner rather than guess.
| User language | HTML templates | Slides (PDF default) | Slides (PPTX fallback) |
|---|---|---|---|
| Chinese (primary) | *.html | slides-weasy.html | slides.py |
| English | *-en.html | slides-weasy-en.html | slides-en.py |
| Japanese (best-effort) | *.html | slides-weasy.html | slides.py |
| Korean (best-effort) | *-ko.html | slides-weasy-ko.html | n/a (use slides-en.py only if PPTX is required) |
| Other languages (best-effort) | choose CJK or EN path by script coverage, then verify manually | choose slides-weasy.html or slides-weasy-en.html, then verify manually | use slides.py / slides-en.py only if PPTX is required |
Default to the WeasyPrint HTML path; fall back to PPTX (slides*.py) only when the user explicitly needs an editable deck.Always use CHEATSHEET.md and references/*.md for design, writing, production, and diagram guidance.
Code blocks with class="language-*" are highlighted only when optional Pygments is installed in the build environment. Without it, PDFs still render and code blocks stay monochrome.
Step 1.5 · Intent extraction (silent checklist)
Before choosing a template, verify these four dimensions are clear. Do not ask unless 2+ are missing and cannot be inferred from context.
| Dimension | What to extract | Example |
|---|---|---|
| Purpose | Why this document exists | Persuade investor vs. align internal team vs. close a candidate |
| Audience | Who reads it, what they already know | Technical CTO (skip basics) vs. non-technical board (explain terms) |
| Constraint | Hard limits on length, format, tone, or delivery | "One page max", "formal English", "print-ready A4" |
| Success | What outcome counts as success | They schedule a meeting / they approve the budget / they understand the architecture |
Rules:
- If the conversation already answered a dimension, skip it silently.
- If a dimension can be inferred from the document type (e.g. resume purpose is always "get an interview"), skip it.
- If 2+ dimensions are genuinely unclear, ask in a single compact question (max 2 sub-questions).
- Never ask all four as a checklist. This is a background verification, not a form.
Execution contract
Before creating or modifying an output, lock the contract: language, template, output format, page or length target, visual acceptance check, and verification command. Infer from the user's request when clear; ask only when missing fields materially change the deliverable.
Use the nearest existing template and verification path. Do not add a new template, shared CSS layer, dependency, script flag, or optional mode unless the current request cannot be satisfied without it.
If a change touches SKILL.md, templates, scripts, references, or package inputs, decide whether dist/kami.zip must be refreshed before handoff. Shipped behavior is not ready until the package contains the changed files.
---
Step 2 · Pick the document type
| User says | Document | CN template | EN template | KO template |
|---|---|---|---|---|
| "one-pager / 方案 / 执行摘要 / exec summary" | One-Pager | one-pager.html | one-pager-en.html | one-pager-ko.html |
| "white paper / 白皮书 / 长文 / 年度总结 / technical report" | Long Doc | long-doc.html | long-doc-en.html | long-doc-ko.html |
| "formal letter / 信件 / 辞职信 / 推荐信 / memo" | Letter | letter.html | letter-en.html | letter-ko.html |
| "portfolio / 作品集 / case studies" | Portfolio | portfolio.html | portfolio-en.html | portfolio-ko.html |
| "resume / CV / 简历 / 履歴書" | Resume | resume.html | resume-en.html | resume-ko.html |
| "slides / PPT / deck / 演示" | Slides | slides-weasy.html | slides-weasy-en.html | slides-weasy-ko.html |
| "个股研报 / equity report / 估值分析 / investment memo / 股票分析" | Equity Report | equity-report.html | equity-report-en.html | equity-report-ko.html |
| "更新日志 / changelog / release notes / 版本记录" | Changelog | changelog.html | changelog-en.html | changelog-ko.html |
| "landing page / 落地页 / 官网 / product page / 产品页" | Landing Page | landing-page.html | landing-page-en.html | landing-page-ko.html |
Changelog vs. release notes: The changelog template above is for styled document output. GitHub release notes are a separate deliverable; use /write with Release Note Template Mode.Landing Page: Screen-first interactive template. No PDF output. Includes gallery carousel with auto-rotate, hero entrance animation, responsive breakpoints (880px / 480px), and prefers-reduced-motion support. Deploy as static HTML to Vercel / Netlify / any host. The agent fills {{PLACEHOLDER}} values and HTML comment blocks, then saves as a ready-to-serve .html file.Landing Page companion files: For a production multilingual deploy, copy the fivelanding-page-*.examplefiles alongside the main HTML, remove the.examplesuffix, and fill the placeholders. They cover Vercel rewrites and headers, sitemap hreflang, robots AI allowlist, and llms.txt + llms-full.txt for AI assistants. The main HTML already ships matching hreflang and og:locale in<head>; an Accept-Language redirect at the end oflanding-page-en.htmlis commented out for opt-in.{{SITE_ORIGIN}}is the scheme + host of your{{CANONICAL_URL}}(e.g.https://example.com). Seereferences/design.mdSection 11 «Companion assets».
Production product site mode: If the user needs docs, help, releases, changelog, roadmap, legal pages, or more than two locales, treat it as a site system. Lock product category, real screenshot slots, locale list, companion files, long-content pages, and generator/check needs before filling templates. Keep project-specific release artifacts, payment providers, appcast rules, and private local paths out of Kami. See references/design.md Section 11 «Product site system».Documentation pages: When a landing page grows into a docs or help site, use the doc shell in references/design.md Section 11 «Documentation site»: a sticky sidebar nav with a 2px brand rail (not a dark underline), an on-this-page TOC hidden below the tablet breakpoint, a constrained prose measure, and a quiet borderless prev/next pager (text links, not bordered cards). Highlight code at build time with zero runtime JS on a dark code surface; plain code stays the source of truth.Slides: default toslides-weasy.html/slides-weasy-en.html/slides-weasy-ko.html(WeasyPrint HTML → PDF). Useslides.py/slides-en.pyonly when the user explicitly requires an editable PPTX file. Useassets/templates/marp/slides-marp(.md|.css)only when the user explicitly asks for Marp / markdown slides / a deck that lives in a.mdfile.
Deck recipe: read design.md Section 8 before drafting slides. Sketch title sequence, evidence shape, and image slot before generating or cropping visuals. Keep audience copy separate from visual briefs. Marp-specific constraints live in design.md §8 «Marp variant».
Decision tree (use before asking)
Walk this tree before reaching for a one-liner question. Ask only when two cells genuinely both fit.
| Signal | Document |
|---|---|
| Length target unknown | Ask "how many pages" before classifying |
| ≤ 1 page + investor / recruiter / exec summary audience | one-pager |
| ≤ 1 page + formal correspondence (sales, hiring, resignation, memo) | letter |
| 1.5-2 pages + career narrative + project bullets | resume |
| 3-6 pages + project showcase + visual heavy | portfolio |
| 6-15 pages + sustained argument + low visual density | long-doc |
| Presentation flow + speaker support + per-slide assertion | slides |
| Financial / metrics dashboard + thesis + price or risk view | equity-report |
| Version-by-version log + release facts | changelog |
| Product showcase + pricing + screenshots + FAQ for browser | landing-page |
Ambiguity examples that justify a one-liner:
- "1.5 page career story with heavy visuals" -> ask "resume or portfolio?"
- "2 page exec summary with metric tiles" -> ask "one-pager or equity-report?"
- "5 page argument with several charts" -> ask "long-doc or portfolio?"
Pick from the tree first. Ask only when the tree is genuinely silent.
Diagrams (primitives, not a separate template type)
When the user asks for a diagram inside a long-doc / portfolio / slide (not a standalone document), route to assets/diagrams/ rather than a template:
| User says | Diagram | Template |
|---|---|---|
| "架构图 / architecture / 系统图 / components diagram" | Architecture | assets/diagrams/architecture.html |
| "流程图 / flowchart / 决策流 / branching logic" | Flowchart | assets/diagrams/flowchart.html |
| "象限图 / quadrant / 优先级矩阵 / 2×2 matrix" | Quadrant | assets/diagrams/quadrant.html |
| "柱状图 / bar chart / 分类对比 / grouped bars" | Bar Chart | assets/diagrams/bar-chart.html |
| "折线图 / line chart / 趋势 / 股价 / time series" | Line Chart | assets/diagrams/line-chart.html |
| "环形图 / donut / pie / 占比 / 分布结构" | Donut Chart | assets/diagrams/donut-chart.html |
| "状态机 / state machine / 状态图 / lifecycle" | State Machine | assets/diagrams/state-machine.html |
| "时间线 / timeline / 里程碑 / milestones / roadmap" | Timeline | assets/diagrams/timeline.html |
| "泳道图 / swimlane / 跨角色流程 / cross-team flow" | Swimlane | assets/diagrams/swimlane.html |
| "树状图 / tree / hierarchy / 层级 / 组织架构" | Tree | assets/diagrams/tree.html |
| "分层图 / layer stack / 分层架构 / OSI / stack" | Layer Stack | assets/diagrams/layer-stack.html |
| "维恩图 / venn / 交集 / overlap / 集合关系" | Venn | assets/diagrams/venn.html |
| "K 线 / candlestick / OHLC / 股价走势 / price history" | Candlestick | assets/diagrams/candlestick.html |
| "瀑布图 / waterfall / 收入桥 / revenue bridge / decomposition" | Waterfall | assets/diagrams/waterfall.html |
Read references/diagrams.md before drawing - it has the selection guide, kami token map, and the AI-slop anti-pattern table. Extract the <svg> block from the template and drop it into a <figure> inside long-doc / portfolio.
Before drawing, always ask: would a well-written paragraph teach the reader less than this diagram? If no, don't draw.
Auto-select charts from data. When content contains numerical data, choose the chart type and embed it without waiting for the user to specify. Decision tree (first match wins):
| Data shape | Chart |
|---|---|
| Has open/high/low/close fields, or per-day price | Candlestick |
| Has + and - contributions that sum to a total (bridge, waterfall, P&L) | Waterfall |
| One series, values sum to ~100%, items ≤ 6 | Donut |
| One series, values sum to ~100%, items ≥ 7 | Horizontal bar |
| Two or more series across time (months, quarters, years) | Line |
| One series across time, large count changes dominate (not rate) | Bar |
| Multiple categories, same time snapshot, 2+ series | Grouped bar |
| 2×2 strategic or priority positioning | Quadrant |
| Hierarchical data with depth ≥ 2 | Tree |
| Process with decision branches | Flowchart |
| Cross-team or cross-role process with ≥ 3 actors | Swimlane |
| Set overlaps or shared attributes between 2-3 groups | Venn |
| Category comparison, single series, no time axis | Bar |
When data fits multiple types, prefer the one that shows variance most clearly. Always embed inside a <figure> with a caption that states the insight, not just the data range.
Step 2.1 · Source and material pass
Run this before distilling or filling content when the document depends on facts or materials outside the user's draft. Skip it only for personal drafts where the user already supplied everything needed.
Source check
Trigger when the document mentions a specific company, product, person, release date, version, funding round, metric, market fact, technical spec, or any current fact likely to change.
- Use primary sources before writing: user-provided material, official site, docs, filings, press release, app store page, or repo release
- Keep a short note of source names and dates for facts that drive the document
- If sources conflict or a fact cannot be checked quickly, ask the user instead of choosing silently
- Avoid current-sounding claims such as "latest", "recent", "new", version numbers, launch dates, or financial figures unless they are checked
Material check
Trigger when the document is about a company, product, project, venue, or personal brand.
Confirm the materials that make the subject recognizable before layout:
| Need | Required when | Accept |
|---|---|---|
| Logo | Any branded document | User file or official SVG/PNG |
| Product image | Physical product / venue / object | Official image, user image, or marked gap |
| UI screenshot | App / SaaS / website / tool | Current screenshot, official product image, or user capture |
| Brand colors | Branded one-pager / portfolio / deck | Official value, extracted asset value, or keep kami ink-blue |
| Fonts | Only if brand typography matters | Official font, close system fallback, or kami default |
If a required item is missing, use a compact gap table and ask once. Do not replace missing material with generic imagery, approximate logo drawings, or invented values.
Logo fallback: when the request names no logo but the brand profile has a logo path, fill the commented .brand-logo slot in one-pager / portfolio / slides-weasy per references/brand-profile.md Layer C. Expand ~ to an absolute path, and if the file is missing or the template has no slot, leave it commented and render without a logo (never insert a broken image). An explicit logo in the current request always wins.
Materials status block
After the material check, output a structured status block before continuing. This is a one-shot transparency display, not a question:
Materials status:
- Logo: OK assets/client-logo.svg
- Brand colors: OK #1B365D mapped to --brand
- Product screenshot: MISSING (proceeding with kami default placeholder)
- UI screenshot: not required for this doc typeUse OK, MISSING, or not required. If a required item is missing and no user input arrived, ask once with the gap table; otherwise continue silently.
Step 2.5 · Distill raw content (if applicable)
Auto-detect whether to distill. Do not ask the user; judge from the input:
| Skip distill (fill directly) | Run distill |
|---|---|
| Content has explicit section labels matching template structure | Raw prose without section structure |
| Metrics already quantified with units in place | Numbers scattered or implied, not extracted |
| User wrote "use this as-is" / "直接用这个" / "原封不动" | User pasted multi-source dump (chat / email thread / multiple docs) |
| Content count matches template (e.g. 4 metrics for 4 metric cards) | Content count mismatches template (too many or too few items) |
| One coherent voice with consistent claims | Conflicting claims or duplicate facts across sources |
When in doubt, run distill. Distill is cheap; rebuilding a misaligned doc is not.
When the user hands over raw material (meeting notes, brain dump, existing doc in different format, chat transcript, scattered points):
1. Extract: pull out every factual claim, number, date, name, source, material reference, and action item 2. Classify: map each extract to the target template's sections (see references/writing.md for section structure per doc type) 3. Gap-check: list what the template needs but the raw content doesn't have - include missing facts, missing proof, and missing materials 4. Ask once: share the gap table with the user. Do not guess to fill gaps.
Example gap-check:
| Template needs | Found | Missing |
|---|---|---|
| 4 metric cards | "8 years", "50-person team" | 2 more quantifiable results |
| 3-5 core projects | 2 mentioned | at least 1 more with outcome |
| Materials | logo file provided | product screenshot source |
Then proceed to Step 2.6 (slides) or the layout note (all other doc types) with structured, distilled content.
Step 2.6 · Deck pre-flight (slides only)
Skip this step for every doc type except slides.
Path selection
Default to the WeasyPrint HTML path. Switch to pptx only if the user explicitly requires an editable PPTX file. Switch to Marp only when the user explicitly asks for Marp / markdown slides.
| Path | Template | When |
|---|---|---|
| WeasyPrint HTML → PDF (default) | slides-weasy.html / slides-weasy-en.html / slides-weasy-ko.html | All cases unless PPTX or Marp is required |
| python-pptx → PPTX (fallback) | slides.py / slides-en.py | User explicitly requires editable PPTX |
| Marp Markdown (variant) | assets/templates/marp/slides-marp.md (+ slides-marp.css) / slides-marp-en.md (+ slides-marp-en.css) | User explicitly asks for Marp, "markdown slides", or a .md deck. Shipped .md is a working demo of Kami Marp itself; copy it, swap content, keep the structure. Renders via local marp CLI; not bundled. |
Page size
Default is 280mm 158mm. Ask only if the user has mentioned length or density constraints.
| Size | When |
|---|---|
280mm 158mm | Default; fits most decks |
297mm 167mm | User wants a bit more room |
338mm 190mm | Heavy content slide or many data points per page |
Content pre-flight
Before drafting any slide, confirm these points with the user. Ask all at once, skip any already answered:
| # | Question |
|---|---|
| 1 | Audience + venue - who is in the room, and is it live keynote, investor 1:1, or async share link? |
| 2 | Length target - presentation time or slide count? (15 min: ~10 slides / 30 min: ~20 slides / 45 min: ~25-30 slides) |
| 3 | Source material - what content is already ready: outline, doc, notes, data? |
| 4 | Images - are screenshots, charts, logos, or product images available; which slides need real evidence slots; and is a separate visual brief needed? |
| 5 | Hard constraints - brand colors, required logo, PPTX required, any slides that must exist? |
| 6 | Format confirmation - slides deck, or a one-pager that looks like a deck? |
Before drafting any landing page or product site, lock these points from the source material. Ask once only when a missing item would change the deliverable:
| # | Lock |
|---|---|
| 1 | Product category - first-viewport category: app, CLI, terminal, utility, skill, template system, or another user-provided label. |
| 2 | Real assets - available product screenshots, logo, icon, or UI captures, mapped to hero/gallery/feature/social slots. Missing assets must stay marked, not replaced with stock imagery. |
| 3 | Site shape - single page, or home plus docs/help/releases/changelog/roadmap/legal pages? |
| 4 | Locales - exact locale list, canonical paths, and whether a generator/check mode is needed. |
| 5 | Truth surfaces - install path, price, version, support route, FAQ, llms.txt, and llms-full.txt that must stay synchronized. |
Content rules for slides
- Ghost deck test: read only the slide titles in order. They must tell the argument; if not, fix titles or structure before styling
- One evidence shape per slide: chart, table, screenshot, code, quote, or conclusion. Split mixed evidence instead of crowding one slide
- Audience copy stays clean: titles, body, and captions never contain image prompts, crop instructions, or generation notes
- No section divider slides: use
.eyebrowfor section numbering, not a dedicated blue-background page - No CJK parentheses: replace
(...)with·or, - Each bullet fits one line: trim until it does
- 2×2 layouts: use
table.t2x2, not CSS Grid - Pinned conclusions: use
.coatposition: absolute; bottom: 12mm
These rules apply identically to Marp decks. Marp-specific syntax: see references/design.md §8 «Marp variant».
Step 2.7 · Layout note (transparent, non-blocking)
Before loading specs and filling the template, write a short editor-style note stating the layout intent: template choice, length target, narrative arc, embedded diagrams, material status, and output formats. Match the document's language. Keep it under 80 words, written as prose, not a status panel. Continue immediately after; do not wait.
Example (CN):
排版意图:Equity Report 中文版,2 页 A4。先立论与目标价,进入估值 (DCF 与可比公司),落于催化剂与风险。中段嵌一张营收趋势折线和 FY26 收入桥瀑布。Logo 已就位,产品图暂缺,header 改走纯文字。输出 HTML 与 PDF。
Example (EN):
Layout intent: Equity Report (EN), two pages A4. Open with thesis and price target, run through valuation (DCF and comparables), close on catalysts and risks. A revenue line chart and an FY26 waterfall sit mid-doc. Logo is in hand; product image is absent, so the header stays text-only. Output: HTML and PDF.
The note is for transparency, not approval. If the user pushes back, adjust; otherwise proceed to Step 3.
---
Step 3 · Load the right amount of spec
Pick the tier that matches the task. Default to the lowest tier that covers the work.
| Tier | When | Read |
|---|---|---|
| Content-only | Updating text, swapping bullets, translating an existing doc. CSS stays untouched. | CHEATSHEET.md only |
| Layout tweak | Adjusting spacing, moving sections, changing font size within spec. CSS touched. | CHEATSHEET.md + template (tokens already inline) |
| New document | Building from scratch or from raw content. | Full design spec + writing spec + template |
| Resume content | Resume-specific bullet structure, project framing, scope-result-outcome rules. | resume-writing.md + template |
| Sources / materials | Company, product, market, launch, funding, specs, or branded subject. | writing.md source rules + user/source material |
| Deck (>20 slides) | Long presentation needing Part Divider, Code Cards, section headers. | Full design spec + Deck Recipe (design.md section 8) |
| Troubleshoot | Rendering bug, font issue, page overflow. | production.md (+ design spec if CSS is the cause) |
| Anti-patterns | Reviewing AI-generated drafts before shipping. | anti-patterns.md (six-category checklist) |
| Diagram | Embedding SVG in a doc. | diagrams.md only (has its own token map) |
You can always escalate mid-task if the work turns out to need more than the initial tier.
The full spec files for reference:
- Design:
references/design.md - Writing (general):
references/writing.md - Writing (resume-specific):
references/resume-writing.md - Production:
references/production.md - Diagrams:
references/diagrams.md - Anti-patterns:
references/anti-patterns.md
Step 4 · Fill content into the template
- Copy the template into your working directory; don't write HTML from scratch
- CSS stays untouched, only edit the body
- Content follows
writing.md: data over adjectives, distinctive phrasing over industry clichés - Avoid patterns listed in
references/anti-patterns.md: emptiness, fabrication, mimicry, excess, source gaps, tone contamination - Before filling, read the quality bar for your document type in
writing.mdsection "Quality bars by document type". Structure is necessary but not sufficient: a resume bullet needs Action + Scope + Result + Business Outcome; an equity report needs variant perception + quantified catalysts; slides need assertion-evidence titles. Meeting the quality bar is as important as filling every placeholder.
Do not generate
These are the most common AI document failures. Cross-reference references/anti-patterns.md for the full list.
- Do not leave placeholder text in the final document ("Lorem ipsum", "[Insert here]", "TBD")
- Do not invent metrics, financial data, or statistics; mark gaps with
[DATA NEEDED: description] - Do not use stock-image descriptions as image placeholders ("A diverse team collaborating in a modern office")
- Do not pad content to fill template slots (a resume with 3 real projects does not need 5 fabricated ones)
- Do not write a paragraph that merely restates its own heading in sentence form
Fill PDF metadata (WeasyPrint reads these into the PDF)
Every template has meta placeholders in <head>. Fill all four before building:
| Placeholder (CN) | Placeholder (EN) | Rule |
|---|---|---|
{{作者}} | {{AUTHOR}} | Resume/letter/portfolio: use the person's name from the doc. All others: leave as-is (build script infers from git config or env) |
{{摘要}} | {{DESCRIPTION}} | Extract one sentence (≤150 chars) from the first 2 paragraphs |
{{关键词}} | {{KEYWORDS}} | 3-5 keywords from the title + section headings, comma-separated |
{{文档标题}} / {{信件主题}} etc. | {{DOC_TITLE}} / {{LETTER_SUBJECT}} etc. | Infer from the H1 or .header .title text |
<meta name="generator" content="Kami"> is already fixed in the template; do not change it.
Author inference: build.py automatically sets PDF /Author metadata from: 1. git config user.name (primary) 2. KAMI_AUTHOR environment variable (fallback) 3. "Kami" (final fallback)
For personal documents (resume/letter/portfolio), the HTML <meta name="author"> should match the person's name in the content. For non-personal documents (one-pager/long-doc), leave the placeholder as-is and let the build script infer it.
Step 4.1 · Per-page density target (multi-page templates only)
适用:slides-weasy / long-doc / portfolio / equity-report / changelog。不适用 resume / one-pager / letter(这些有独立的长度合约)。
正文页填充率目标 60-80%。封面 / 目录 / 末尾署名页豁免。这条规则解决的是 AI 生成多页文档时最常见的 draft 缺陷:把内容拆得太散,结果几页都填不满。
Items-per-page contract
| Template | Typical body page | Hard floor (merge if below) |
|---|---|---|
| slides-weasy | 1 assertion title + 3-5 supporting items, or 1 chart + 2-3 callouts | <3 items and no chart → merge into adjacent slide |
| long-doc | 1 chapter heading + 2-4 paragraphs + at most 1 figure | Chapter renders to <40% page → merge into neighbor chapter |
| portfolio | 1 project header + 1 hero image + 3-5 outcome bullets | No image and <3 outcomes → merge with adjacent project |
| equity-report | 1 section + 1 table/chart + supporting prose | Only a 2-row table on the page → combine sections |
| changelog | 1 version block + 4-8 entries | Version has <4 entries → place on the same page as the prior version |
Sparse-page merge rule
Before finalizing, scan the draft. Any body page that would render under 50% full → apply one of, in order:
1. Merge upward into the previous section. 2. Merge downward into the next section. 3. Promote a list to a small diagram or table that earns the space. 4. Pin a .co callout to bottom (slides-weasy only). Whitespace above a pinned callout is intentional, not sparse.
Forbidden ways to "fill" a sparse page: padding with filler prose, repeating the heading as a sentence, inventing statistics, restating the prior page in different words. If the merge options don't apply, the page itself shouldn't exist.
Last-page exemption
The last body page is allowed to run 40-60% fill. Forcing balance on the last page usually means padding. The colophon / closing slide may have any fill level.
Verify after build
python3 scripts/build.py --check-density # flags >25% (WARN) / >50% (SPARSE) trailing whitespaceIf a body page (not cover, not last page) gets a SPARSE warning, treat it as a draft defect and re-author with the merge rule.
Step 4.5 · Auto-select output format
Do not ask the user which format to export. Decide from context:
| Signal | Output | Why |
|---|---|---|
| Any document request | HTML + PDF | PDF is the default deliverable, HTML is the source |
| Slides / PPT / deck | HTML + PDF + PPTX | Presentations need a projectable format |
| "分享" / "发朋友圈" / "share" / "post" / "preview" | + PNG | Social platforms and messaging need images |
| "嵌入" / "插图" / "embed in another doc" | PNG only | Used as material inside other documents |
| User explicitly says a format | Follow the user | Explicit request overrides auto-selection |
PDF always ships for document templates. Landing pages ship as a ready-to-serve static HTML file. PPTX follows slides. PNG follows sharing context. The user should never need to think about formats.
Step 5 · Build & verify
python3 scripts/build.py --verify # build all templates + page count + font check + slides
python3 scripts/build.py --verify resume-en # single target full verification
python3 scripts/build.py landing-page # screen-first static HTML template check
python3 scripts/build.py --verify slides # single slide deck verification
python3 scripts/build.py --check-placeholders path/to/filled.html
python3 scripts/build.py --check-resume-balance path/to/resume.pdf
python3 scripts/build.py --check-density # page whitespace scanner (skips cover)
python3 scripts/build.py --check # CSS rule violations only (fast, no build)
python3 scripts/build_metadata.py --check # Codex plugin mirror + marketplace drift checkScreen verify:--check-densityis a print gate. For screen output (landing or docs pages) instead screenshot the rendered page at 375px and 1280px in every locale and scan for line widows before shipping. Seereferences/design.mdSection 11 «Responsive screenshot verification».
Source templates intentionally keep {{...}} fields. Run placeholder checks on completed documents, not on the template library.
Visual anomalies (tag double rectangle, font fallback, page break issues) -> production.md Part 4.
Maintainer-mode checks
Use these only when maintaining this repository or release package, not for ordinary document generation.
- If marketplace metadata, generated plugin mirrors, version selection, or install paths change, run
python3 scripts/build_metadata.py --check; for Codex install behavior, also smoke with an isolatedCODEX_HOME=/tmp/...usingcodex plugin marketplace add <path>,codex plugin add kami@kami, andcodex plugin list. - If
SKILL.md, templates, scripts, references, or other package inputs change and the behavior ships through the skill package, runbash scripts/package-skill.shand inspectdist/kami.zipbefore handoff. - If a GitHub release asset is refreshed, download the uploaded
kami.zipand compare ZIP entry names plus per-entry SHA-256 digests against localdist/kami.zip; page text, file size, and the container hash are not enough.
Fonts
Chinese
- Main serif: TsangerJinKai02-W04.ttf (400 weight) + TsangerJinKai02-W05.ttf (500 weight, real bold)
- Templates use dual @font-face declarations: W04 for body text, W05 for headings
- Both files are commercial fonts. Keep them available in the repository for local preview and CDN fallback, but do not bundle them inside Claude Desktop skill ZIPs
- Fallback chain baked into templates: Source Han Serif SC -> Noto Serif CJK SC -> Songti SC -> STSong -> Georgia
Japanese (best-effort)
- Uses CJK template path, no dedicated
-jatemplates yet - JP Mincho-first stack: YuMincho -> Hiragino Mincho ProN -> Noto Serif CJK JP -> Source Han Serif JP -> TsangerJinKai02 -> serif
- Visually verify line breaks, punctuation rhythm, and emphasis weight before shipping
Korean (best-effort)
- Dedicated
-kotemplates use Source Han Serif K Regular / Medium, with the real OTF family nameSource Han Serif KRkept in every fallback stack - Fallback: Noto Serif KR / Apple SD Gothic Neo / AppleMyungjo / Charter / Georgia
- The OTFs are OFL-licensed and tracked for local preview / CDN fallback, but excluded from Claude Desktop skill ZIPs to keep the package small
English
- Single serif: Charter (system-bundled, macOS/iOS), used for both headlines and body
- No separate sans:
--sans: var(--serif), one font per page - Fallback: Georgia (cross-platform) / Palatino / Times New Roman
Font files next to HTML with relative @font-face paths is the most stable setup. scripts/package-skill.sh excludes large CJK font files from the Claude Desktop ZIP, so the uploaded package stays under the 6MB package ceiling. Always upload that package-skill.sh output, never a hand-zipped checkout (the tracked CJK fonts make it too large and Claude Desktop rejects the upload).
Font auto-recovery (Claude Desktop)
Before building Chinese or Korean documents, ensure fonts are present. The script tries multiple CDN sources with retry and size validation:
bash scripts/ensure-fonts.shIt downloads to the XDG user font dir (${XDG_DATA_HOME:-~/.local/share}/fonts/kami, override with KAMI_FONT_DIR), not into the skill's assets/fonts -- that keeps the installed skill small so Claude Desktop never trips its size limit. fontconfig scans that dir by default, so WeasyPrint finds TsangerJinKai02 and Source Han Serif K there; online renders fall back to the jsDelivr @font-face URL. Run once before building. If all sources fail, the script prints per-language alternatives.
Feedback protocol
When the user gives vague visual feedback ("looks off", "太挤了", "not elegant"), do not guess. Ask back with current values:
| User says | Ask about |
|---|---|
| "太挤了" / "too cramped" | Which element? Line-height (current: X)? Padding (current: Y)? Page margin? |
| "太松了" / "too loose" | Same direction, reversed |
| "颜色不对" / "color feels wrong" | Which element? Brand blue overused? A gray reading too cool? |
| "不够好看" / "not polished" | Font rendering? Alignment? Whitespace distribution? Hierarchy unclear? |
| "看着不专业" / "unprofessional" | Content wording? Or layout (alignment, consistency)? |
Template response: "X is currently set to Y. Would you like (a) [specific alternative within spec] or (b) [another option]?"
Never say "I'll adjust the spacing" without naming the exact property and its new value.
---
When not to use this skill
- User explicitly wants Material / Fluent / Tailwind default - different design language
- Need dark / cyberpunk / futurist aesthetic (this is deliberately anti-future)
- Need saturated multi-color (this has one accent)
- Need cartoon / animation / illustration style (this is editorial)
- Web dynamic app UI (this is for print / static documents)
---
Next: apply Step 3's tier table to decide what to read, then copy the matching template and start filling.
{
"name": "kami",
"interface": {
"displayName": "Kami"
},
"plugins": [
{
"name": "kami",
"source": {
"source": "local",
"path": "./plugins/kami"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
{
"name": "kami",
"description": "Document typesetting skill for Claude Code.",
"owner": {
"name": "Tw93",
"email": "hitw93@gmail.com"
},
"plugins": [
{
"name": "kami",
"version": "1.9.0",
"description": "Typeset professional documents: resumes, one-pagers, white papers, letters, portfolios, slide decks. Warm parchment, ink-blue accent, serif-led hierarchy.",
"category": "documents",
"source": "./",
"homepage": "https://github.com/tw93/kami"
}
]
}
{
"version": "0.0.1",
"configurations": [
{
"name": "kami-site",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "8321"],
"port": 8321
}
]
}
github: ['tw93']
custom: ['https://cats.tw93.fun?name=Kami']
name: check
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
lint-and-test:
name: lint and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install test extras
run: python3 -m pip install Pygments
- name: Lint templates and check token sync
run: python3 scripts/build.py --check
- name: Check generated Codex plugin metadata
run: python3 scripts/build_metadata.py --check
- name: Run test suite
run: python3 scripts/tests/test_build.py
- name: Build and audit skill package
run: bash scripts/package-skill.sh /tmp/kami-ci.zip
verify-render:
name: render and verify
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install WeasyPrint system libs and CJK fallback fonts
# libcairo2 / libpango / libharfbuzz are required by WeasyPrint at
# runtime; fonts-noto-cjk provides a serif CJK fallback so the Chinese
# templates render with embedded glyphs even though TsangerJinKai02 is
# commercial and not shipped in CI. The verify step accepts Noto as a
# recognized fallback (see scripts/build.py fallback_present).
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
fonts-noto-cjk fonts-noto-cjk-extra
- name: Install Python deps
run: python3 -m pip install weasyprint pypdf Pygments
- name: Verify strict page-count targets
# Only the six hard-invariant templates (resume == 2, one-pager == 1
# across CN/EN/KO) run in CI. The KO pair also exercises the Korean
# render path on Linux (fonts-noto-cjk supplies the glyphs). The
# long-doc / portfolio / slides ceilings are soft and checked visually.
# KAMI_ALLOW_FALLBACK_ONLY=1: CI does not ship commercial fonts
# (TsangerJinKai02, Charter). Treat fallback-only embedding as a
# warning instead of a failure so CI can still gate page counts.
env:
KAMI_ALLOW_FALLBACK_ONLY: '1'
run: |
python3 scripts/build.py --verify one-pager
python3 scripts/build.py --verify one-pager-en
python3 scripts/build.py --verify one-pager-ko
python3 scripts/build.py --verify resume
python3 scripts/build.py --verify resume-en
python3 scripts/build.py --verify resume-ko
name: release
on:
push:
tags: ['V*']
workflow_dispatch:
inputs:
tag:
description: 'Tag to attach kami.zip to (e.g. V1.4.1)'
required: true
jobs:
attach-archive:
name: build kami.zip and attach to release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Resolve tag name
id: tag
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${GITHUB_REF#refs/tags/}"
fi
echo "name=$TAG" >> "$GITHUB_OUTPUT"
echo "Resolved tag: $TAG"
- name: Build kami.zip
run: |
bash scripts/package-skill.sh dist/kami.zip
ls -lah dist/kami.zip
- name: Ensure release exists, then attach archive
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.name }}
run: |
if ! gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG does not exist, creating placeholder"
gh release create "$TAG" --title "$TAG" --notes "Release notes pending. Edit on GitHub."
fi
gh release upload "$TAG" dist/kami.zip --clobber
echo "OK: kami.zip attached to release $TAG"
- name: Add release reactions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.name }}
run: |
# Match the house style every release carries: one of each positive
# reaction. Idempotent (re-running adds nothing) and non-fatal so a
# reaction hiccup never fails an otherwise-good release.
rid="$(gh api "repos/${{ github.repository }}/releases/tags/$TAG" --jq .id)"
for r in +1 eyes heart hooray laugh rocket; do
gh api -X POST "repos/${{ github.repository }}/releases/$rid/reactions" \
-f content="$r" --jq .content || echo "skip reaction $r"
done
echo "OK: reactions ensured on release $TAG"
# macOS
.DS_Store
# Agent local settings
.claude/settings.local.json
# Python
__pycache__/
*.pyc
.pytest_cache/
# Font build artifacts
assets/fonts/*.otf
# Template examples with placeholder content, regenerate via: python3 scripts/build.py
assets/examples/
/tmp/
/dist/*
!/dist/kami.zip
*.log
.vercel
# Slides build artifact (moved into assets/examples/ by build.py)
/output.pptx
Kami Agent Guide
Personal/global agent rules may live outside this repository. This file records Kami-specific repository maps, Working Rules, Current Risk Areas, Verification, Release Flow, and Fonts.
Project
Kami is a document-generation skill and template system. It ships editorial HTML templates, reference guides, demo assets, and a packaged skill archive.
Repository Map
SKILL.md- skill routing and operating rules.CHEATSHEET.md- quick design reference.CLAUDE.md- Claude-specific notes pointing to AGENTS.md.references/- design, writing, diagram, and production guidance.references/design.md,writing.md,production.md,diagrams.md- full specs.references/resume-writing.md- resume-specific bullet/project framing rules.references/anti-patterns.md- six-category checklist for reviewing drafts.references/mermaid.md- Mermaid diagram support: the two render paths (PDF vs browser) and the authoring pipeline.references/mermaid-theme.json- canonical Kami↔beautiful-mermaid color/font theme (kept in sync withtokens.json).references/tokens.json- canonical color tokens (drift-checked byscripts/tokens.py).references/checks_thresholds.json- rhythm / density / orphan check thresholds (loaded byscripts/checks.py).references/brand-profile.mdandreferences/brand.example.md- optional brand profile behavior and public example..claude-plugin/marketplace.json- Claude Code plugin marketplace metadata..agents/plugins/marketplace.json- generated Codex repo marketplace. Points Codex atplugins/kami; never hand-edit.plugins/kami/- generated Codex plugin tree. Mirrors the lightweight skill package underplugins/kami/skills/kami/; edit source files and runpython3 scripts/build_metadata.py.assets/templates/- document templates including browser-only landing page variants.scripts/highlight.py- Pygments-based syntax highlighting for code blocks at build time.assets/demos/- README showcase demos.assets/showcase/- README and public-site-only screenshots; excluded fromdist/kami.zip.assets/diagrams/- diagram prototypes and generated diagram assets;src/*.mmdrecords the Mermaid source of thesequence/class/erdiagrams.scripts/mermaid_normalize.py- re-themes any beautiful-mermaid SVG to the Kami palette and makes it WeasyPrint-safe (resolvescolor-mix()/var()to static hex, rewrites fonts). Pure Python, no Node; ships in the package.assets/fonts/andassets/illustrations/- bundled visual assets.styles.css- shared web-facing styles.index.html,index-zh.html,index-en.html,index-ja.html,index-ko.html,index-tw.html- public site entrypoints.robots.txt,sitemap.xml, andvercel.json- public crawler, deployment, and AI visibility files.llms.txt- AI crawler and model-facing project summary.scripts/build.py- CLI shell: build targets and dispatch to lint / verify / checks / tokens modules.scripts/verify.py- end-to-end render verification (page count, embedded fonts, advisory density scan).scripts/lint.py- template CSS lint rules and base/variant cross-template:rootconsistency check (CN↔EN and CN↔KO).scripts/tokens.py-tokens.jsondrift check across HTML templates and PPTX slide scripts.scripts/checks.py- PDF-side checks: placeholders, orphans, density, slide-deck rhythm.scripts/optional_deps.py- centralized loader for weasyprint / pypdf / PyMuPDF with consistent install hints.scripts/shared.py- shared constants and the canonicalHTML_TEMPLATESregistry used by the build scripts.scripts/ensure-fonts.sh- verified font recovery helper (portable across bash 3.2+).scripts/package-skill.sh- package builder for the release archive.scripts/build_metadata.py- codegen for Codex marketplace metadata and plugin mirror files. Run after changingSKILL.md,CHEATSHEET.md,VERSION,references/,scripts/, or shipped lightweight assets.scripts/draft-release-notes.py- bilingual release notes scaffold fromgit log.scripts/tests/test_build.py- zero-dependency test suite for build and shared helpers..github/workflows/check.yml- PR/push CI that runs--checkand the test suite..github/workflows/release.yml- tag-triggered workflow that builds and attachesdist/kami.zipto the release.dist/kami.zip- tracked release archive.
Reference docs are English-only. Language-specific output differences (CN/EN/KO) belong in templates, not duplicated reference files.
Commands
python3 scripts/build.py
python3 scripts/build.py --check
python3 scripts/build.py --verify
python3 scripts/build.py --check-placeholders path/to/filled.html
python3 scripts/build.py --check-orphans path/to/doc.pdf
python3 scripts/build.py --check-density path/to/doc.pdf
python3 scripts/build.py --check-rhythm slides slides-en
python3 scripts/build_metadata.py
python3 scripts/build_metadata.py --check
python3 scripts/tests/test_build.py
# Re-theme + WeasyPrint-safe a beautiful-mermaid SVG (no Node), then embed in a diagram shell:
python3 scripts/mermaid_normalize.py raw.svg -o clean.svg
python3 scripts/draft-release-notes.py V1.4.0..HEAD --version V1.4.1 --title "Steadier Hand"
bash scripts/ensure-fonts.sh
bash scripts/package-skill.shWorking Rules
- Style changes must update
references/design.mdand the matching template tokens. - Landing or documentation-site work follows
references/design.mdSection 11: «Documentation site» for the doc shell (sidebar rail, on-this-page TOC, borderless prev/next pager, build-time zero-JS code highlighting) and «Responsive screenshot verification» (screenshot at 375px / 1280px per locale, objective line-widow scan) before shipping. - For hosted Kami site or public landing changes, first separate generic template work from Kami's own website. Generic behavior lives in
assets/templates/landing-page*andreferences/; Kami site facts live acrossindex*.html,styles.css, README,llms.txt,robots.txt,sitemap.xml, andvercel.json. - Public facts are wider than the hero. Pricing, install path, version, release, support, analytics, FAQ, and positioning claims must move together across pages, metadata, AI files, and download links. Do not leave site-only analytics or tracking changes contradicting "no analytics" or app/package privacy copy.
- Content changes should avoid CSS churn unless layout behavior is part of the task.
- For document or template tasks, lock the output contract before editing: language, template, output format, page or length target, visual acceptance check, and verification command.
- Prefer the nearest existing template and deterministic verifier. Do not add a template, shared CSS layer, dependency, script flag, or optional mode unless the current request cannot be satisfied without it.
- New templates should copy the nearest existing template, stay aligned with
references/design.md, and add demo coverage. - Do not use graphic emoticons in docs, template comments, or script output.
- Use
OK:andERROR:for status text in scripts. - Use
scripts/ensure-fonts.shto recover required fonts with retry and size validation when local font files are missing or truncated. It downloads to the XDG user font dir (${XDG_DATA_HOME:-~/.local/share}/fonts/kami), never into the skill'sassets/fonts, so an installed Claude Desktop skill stays small; inside a repo checkout it is a no-op because the committed large fonts already satisfy the templates' relative path. - Do not bundle large CJK font files into
dist/kami.zip; package scripts should exclude them while templates keep stable local-preview paths. The skill ZIP uploaded to Claude Desktop must be thescripts/package-skill.shoutput under the 6MB package ceiling; a hand-zipped checkout includes the tracked large fonts and Claude Desktop rejects it. - Do not bundle README/public-site-only showcase screenshots into
dist/kami.zip; keep them underassets/showcase/and exclude that directory inscripts/package-skill.sh. - Keep multilingual public pages,
llms.txt,robots.txt, sitemap, JSON-LD, and FAQ content aligned when changing public positioning or install instructions. - Brand profile support is optional context. Keep public examples in
references/; do not hard-code a maintainer's private local profile content. - Slides default to WeasyPrint HTML-to-PDF templates unless the user explicitly needs editable PPTX output.
- Templates intentionally inline their CSS rather than share a
_kami.csspartial: each template must remain a single self-contained HTML file so users can copy-paste it without a build step. When fixing CSS drift, apply the same change across affected templates rather than introducing a build-time include. - All template registries live in
scripts/shared.py:HTML_TEMPLATES(PDF docs),SCREEN_TEMPLATES(browser-only), andDIAGRAM_TEMPLATES(assets/diagrams).build.pyderives its target dicts from them viabuild_targets()/screen_targets()/diagram_targets(). Update the registry, not the per-script dicts, when adding or removing a template or diagram. - Mermaid diagrams: never embed raw beautiful-mermaid SVG into a PDF-bound template. WeasyPrint cannot resolve
color-mix(), render<foreignObject>, or fetch a runtime web font, so always pipe throughscripts/mermaid_normalize.pyfirst (--checklint enforces this). It is pure Python, no Node bundled.xychart-betais browser-only (it styles via<style>class selectors); use the hand-drawn chart diagrams for PDF. Full flow inreferences/mermaid.md.
Refactor And Packaging Hard Stops
- When refactoring
scripts/build.pyor package helpers into new modules, confirm every new helper file is tracked by Git.scripts/package-skill.shpackages fromgit ls-files, so untracked modules pass local imports but disappear fromdist/kami.zip. - Any source change that adds scripts, templates, reference JSON, workflows, or package inputs must refresh and inspect
dist/kami.zip; package freshness is part of release readiness, not a later cleanup step. - Changes to
SKILL.md, templates, scripts, references, or package inputs must decide explicitly whetherdist/kami.zipneeds refresh. If the behavior is shipped through the skill package, rebuild and inspect the ZIP before handoff. - Marketplace, plugin path, version, or generated mirror changes require runtime installation proof, not metadata proof only. For Codex changes, use an isolated
CODEX_HOME=/tmp/...smoke withcodex plugin marketplace add <path>,codex plugin add kami@kami, andcodex plugin list; keep the generator, mirror tree, package audit, and install path aligned. - If
python3 scripts/build.py --verifyfails only because the host Python lacks PPTX fallback dependencies such aspython-pptx, verifyslidesandslides-enfrom a temporary venv instead of treating the environment miss as a source regression. - Do not commit one-off review reports or diagnostic snapshots as durable docs. Extract stable rules into
AGENTS.md,CLAUDE.md,SKILL.md, orreferences/and discard the stale report.
CI And Verification Discipline
- Tests that need
weasyprint/pypdf/PyMuPDFmust run in a CI job that installs those deps (currentlyverify-render). Thelint-and-testjob ships only Pygments, so afind_spec(...) is not Noneskip-guard there silently skips the test while still printingOK:. A greenlint-and-testdoes not mean the solver / render tests ran. - Edits to
.github/workflows/*.ymlshould be validated on a feature branch (push, watch the run go green) before merging tomain. Local font / dependency / runner assumptions diverge from CI more often than expected: this project has already burned commits onpipcache requiring a manifest, thefallback_presentset missing Ubuntu defaults (DejaVu / Liberation), and CI never having commercial fonts (Charter / TsangerJinKai02). - Differences between CI and host behavior are expressed as a single explicit opt-in env var (
KAMI_ALLOW_FALLBACK_ONLY=1for missing primary fonts). When a third such flag is needed, migrate toreferences/verify_profile.jsonor a--ci-modeCLI flag instead of lettingKAMI_*env vars sprawl.
Current Risk Areas
- WeasyPrint rendering is sensitive to font availability, solid hex tag backgrounds, page breaks, CJK fallback, and synthetic bold. Verify visually for template changes.
- Slide output has three paths:
slides-weasy*.htmlfor default PDF decks,slides*.pyfor editable PPTX fallback, andassets/templates/marp/slides-marp*.{md,css}for Markdown-first Marp decks. - Marp theme CSS (
assets/templates/marp/slides-marp.cssand-en) inlines a full copy of the design tokens (--parchment,--brand,--serif, rhythm modules) because Marp themes must be self-contained.build.py --sync/--checknow token-sync themarp/*.cssfiles (tokens.pyglobs them), so token-value drift fromreferences/design.md/tokens.jsonis caught, not silent. The remaining gap: the CSS lint and off-palette guard still scan only.htmltemplates, so non-token CSS in the Marp themes (new rules, off-palette colors) is not lint-checked, review those by hand. - AI/public visibility spans
index*.html,llms.txt,robots.txt,sitemap.xml, FAQ JSON-LD, README install text, diagram counts, and release archive links. scripts/shared.pycentralizes constants used by the build scripts; keep paths and target names in sync before adding templates or diagrams.dist/kami.zipis a tracked release archive. Packaging changes must update and inspect it deliberately.- Codex plugin files are generated artifacts. Do not edit
plugins/kami/or.agents/plugins/marketplace.jsondirectly; regenerate from the root source files and letpython3 scripts/build_metadata.py --checkcatch drift.
Hotspot Ownership
references/design.mdandplugins/kami/skills/kami/references/design.mdown the Kami visual system, including large landing-page and documentation-site rules. Boundary: edit only the root source, and use the plugin path only as a generated mirror. Verification:python3 scripts/build.py --checkplus screenshots for screen surfaces, thenpython3 scripts/build_metadata.py --check.styles.cssowns the hosted Kami public site shell, language switcher, gallery, and shared responsive behavior. Boundary: do not move generic template rules here. Verification: serve the site and screenshot 375px / 1280px per locale touched.assets/templates/landing-page.html,assets/templates/landing-page-en.html,assets/templates/landing-page-ko.html,plugins/kami/skills/kami/assets/templates/landing-page.html,plugins/kami/skills/kami/assets/templates/landing-page-en.html, andplugins/kami/skills/kami/assets/templates/landing-page-ko.htmlown the generic screen-first template shipped to users. Boundary: edit root templates, keep real product-site facts in filled sites, and treat plugin paths as generated mirrors. Verification:python3 scripts/build.py landing-page, browser screenshots for changed breakpoints, andpython3 scripts/build_metadata.py --check.references/production.mdandplugins/kami/skills/kami/references/production.mdown production troubleshooting, pre-ship review, and known render pitfalls. Boundary: add stable invariants only, not dated review notes. Verification:python3 scripts/build.py --check,python3 scripts/build_metadata.py --check, and the relevant render command named by the rule.assets/templates/resume.html,assets/templates/resume-ko.html,plugins/kami/skills/kami/assets/templates/resume.html, andplugins/kami/skills/kami/assets/templates/resume-ko.htmlown high-density resume layout. Boundary: preserve the two-page contract and do not fix overflow by generic shrinking first. Verification:python3 scripts/build.py --verify resumeandpython3 scripts/build.py --verify resume-ko.assets/demos/demo-resume-ko.htmlowns the Korean resume demo content, not the template contract. Boundary: regenerate demo outputs when the demo changes, but put durable resume rules in templates or references. Verification: build the affected demo and confirm page count plus rendered screenshot.scripts/tests/test_build.pyandplugins/kami/skills/kami/scripts/tests/test_build.pyown the zero-dependency test suite. Boundary: edit the root test file and regenerate the plugin mirror. Verification:python3 scripts/tests/test_build.pyandpython3 scripts/build_metadata.py --check.
High-Risk Pitfalls
See references/production.md Part 4.
1. Tag rgba double rectangle: use solid hex backgrounds. 2. Thin border plus border-radius double ring: border < 1pt with border-radius can trigger it. 3. Resume 2-page overflow: tiny font, fallback, line-height, or margin changes can break it. 4. break-inside fails inside flex: wrap content in a block wrapper. 5. height: 100vh is unreliable under @page: use explicit mm values. 6. SVG marker orient="auto" does not rotate in WeasyPrint: draw arrowheads manually. 7. Section body text should not use max-width: .manifesto, .section-lede, and similar text should fill the .page container. Exceptions: .type-sample and .footer .colophon. 8. Diagram template changes must sync to index showcase SVGs: any visual fix to assets/diagrams/*.html must also be applied to the matching mini SVG in index.html, index-zh.html, index-ja.html, index-ko.html, index-tw.html.
Demo Screenshots
All demo PNG files use 1241x1754px (first A4 portrait page at 150dpi).
For one-page and multi-page documents (one-pager / letter / resume / portfolio / long-doc / equity-report), capture page 1:
pdftoppm -r 150 -f 1 -l 1 -png <pdf> /tmp/p && cp /tmp/p-1.png <target>.pngFor landscape slides, capture the first 2 pages, resize each to 867px high, add a 20px gap, then extend to 1241px wide:
pdftoppm -r 150 -f 1 -l 2 -png <pdf> /tmp/sl
magick /tmp/sl-1.png -resize x867 /tmp/sl1.png
magick /tmp/sl-2.png -resize x867 /tmp/sl2.png
magick -size $(identify -format '%w' /tmp/sl1.png)x20 xc:'#f5f4ed' /tmp/gap.png
magick /tmp/sl1.png /tmp/gap.png /tmp/sl2.png -append /tmp/stacked.png
magick /tmp/stacked.png -gravity Center -background '#f5f4ed' -extent 1241x1754 <target>.pngVerification Details
- Expected page counts: one-pager 1, letter 1, resume 2 strict, long-doc 7 plus or minus 2, portfolio 6 plus or minus 2, slides 7 plus or minus 3, equity-report 2 to 3, changelog 1 to 2. Landing pages are browser-only HTML with no PDF page count.
scripts/build.pysets PDF/Authorfromgit config user.nameorKAMI_AUTHORonly when the template still has an author placeholder./Producerand/Creatorshould remainKami.- Demo PNGs under
assets/demos/are first-page previews at 1241x1754px. For slide demos, capture the first two landscape pages, stack them with a parchment gap, then extend to 1241x1754px. - Diagram count and names must stay aligned across
SKILL.md,CHEATSHEET.md,README.md,index*.html, andassets/diagrams/.
Verification
- Template, CSS, or script changes: run
python3 scripts/build.py --check(CSS lint + token sync + base/variant cross-template:rootconsistency, currently CN↔EN and CN↔KO) andpython3 scripts/build.py --verify. - Demo changes: regenerate the affected demo outputs and confirm page counts stay in range.
- Font issues: run
bash scripts/ensure-fonts.sh, then rebuild the affected target. - Slide rhythm or deck changes: run
python3 scripts/build.py --check-rhythm slides slides-enplus the affected render command. - Public site or AI visibility changes: check
index*.html, README,llms.txt,robots.txt,sitemap.xml, JSON-LD, FAQ, install links, and release/download links together. Serve the page and screenshot 375px / 1280px per locale, plus 320px when CTA width or mobile nav changes. - Packaging changes: run
bash scripts/package-skill.shand confirmdist/kami.zipstays small enough for release upload. Inspectunzip -l dist/kami.zipfor accidental large fonts, showcase screenshots, cache files, or missing new helper files. - Codex marketplace changes: run
python3 scripts/build_metadata.py --checkand confirmplugins/kami/.codex-plugin/plugin.jsonplus.agents/plugins/marketplace.jsonstay generated. If install behavior, version selection, or source path changed, also run an isolated Codex install smoke. - Documentation-only changes: check links and references.
Release Notes
- For public releases, keep notes concise and bilingual. Use one-to-one English and Chinese changelog items, 5 to 8 items, one sentence each.
- Generate the scaffold with
python3 scripts/draft-release-notes.py V<prev>..V<new> --version V<new> --title "<Codename>", then regroup the raw commit list into 5 to 8 product-themed bullets and translate each to Chinese. Do not paste raw commit subjects. - Match the established shape: title is
V<x.y.z> <Two-Word Codename>(e.g.V1.7.2 Cleaner Resumes), body is the centered logo block +### Changelog(English numbered list) +### 更新日志(Chinese numbered list) + the closing tagline line.
Release Flow
bash scripts/package-skill.shwrites the trackeddist/kami.ziprelease archive and excludes large TsangerJinKai / Source Han Serif K font files plus README/public-site-only showcase screenshots.dist/kami.zipshould be committed with release changes and uploaded to the latest GitHub release asset when refreshing the Claude Desktop package.- When refreshing a GitHub release asset, download the uploaded
kami.zipand compare ZIP entry names plus per-entry SHA-256 digests against localdist/kami.zip; do not rely on release-page text, file size, or container SHA alone. - README and public site download links use
https://github.com/tw93/kami/releases/latest/download/kami.zip; prefer refreshing that asset for small packaging or documentation fixes instead of creating a new tag. - Create a new version tag only when the maintainer explicitly wants a versioned release. Tag the commit that already contains the final refreshed
dist/kami.zip; do not tag a source-only commit and refresh the archive afterward. - On tag push,
.github/workflows/release.ymlbuilds and attachesdist/kami.zip, creates the release if missing, and adds the house-style reactions (+1 eyes heart hooray laugh rocket, one each). Do notgh release createby hand; let CI create the placeholder, then set the real title and notes withgh release edit V<x> --title "V<x> <Codename>" --notes-file <file>. - If reactions are ever missing (older release, CI skipped), add them manually:
rid=$(gh api repos/tw93/Kami/releases/tags/V<x> --jq .id); for r in +1 eyes heart hooray laugh rocket; do gh api -X POST repos/tw93/Kami/releases/$rid/reactions -f content="$r"; done.
Fonts
- Chinese templates use TsangerJinKai02 W04/W05. Commercial use requires the appropriate font license.
- If TsangerJinKai is unavailable, fall back through Source Han Serif SC, Noto Serif CJK SC, Songti SC, STSong, then Georgia.
- English templates use Charter serif. Japanese output uses YuMincho first, then Hiragino Mincho ProN, Noto Serif CJK JP, Source Han Serif JP, TsangerJinKai02, and generic serif.
- Korean templates use Source Han Serif K (Adobe, also distributed as Noto Serif KR by Google). Fallback chain: Source Han Serif K, Source Han Serif KR, Noto Serif KR, Apple SD Gothic Neo, AppleMyungjo, Charter, Georgia.
Source Han Serif KRis the actual family name inside the bundled OTFs and must stay in the chain so fontconfig can resolve theensure-fonts.sh-downloaded font by name on an offline Linux skill install. - Claude Desktop ZIPs do not bundle TsangerJinKai TTF or Source Han Serif K OTF files (the OTFs are OFL-licensed and git-tracked for the CDN
@font-facefallback, but excluded from the package to keep it small). Runbash scripts/ensure-fonts.shbefore building Chinese or Korean documents when fonts are missing; it drops them in the XDG user font dir (fontconfig-scanned, outside the skill), so the installed skill stays small and online renders still use the jsDelivr@font-facefallback.
<!DOCTYPE html>
<!-- ==================================================================
SLIDES DEMO · Agent development · English
Landscape A4, one slide per page, kami palette.
Content distilled from the article "Things You Don't Know About Agents"
(tw93.fun, 2026-03-21).
================================================================== -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Things You Don't Know About Agents · Slides</title>
<style>
/* Palatino is a system font; JetBrains Mono kept for code blocks */
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
@page {
size: A4 landscape;
margin: 0;
background: #f5f4ed;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--parchment: #f5f4ed;
--ivory: #faf9f5;
--near-black: #141413;
--dark-warm: #3d3d3a;
--olive: #504e49;
--stone: #6b6a64;
--brand: #1B365D;
--brand-tint: #EEF2F7;
--border: #e8e6dc;
--serif: Charter, Georgia, Palatino, serif;
--sans: var(--serif);
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
}
body {
color: var(--near-black);
font-family: var(--serif);
}
.slide {
width: 297mm;
height: 210mm;
padding: 18mm 22mm;
background: var(--parchment);
break-after: page;
position: relative;
display: flex;
flex-direction: column;
}
.slide:last-child { break-after: auto; }
.eyebrow {
font-family: var(--mono);
font-size: 9pt;
letter-spacing: 2.5pt;
text-transform: uppercase;
color: var(--stone);
}
.page-num {
position: absolute;
bottom: 10mm;
right: 22mm;
font-family: var(--mono);
font-size: 9pt;
color: var(--stone);
letter-spacing: 0.5pt;
font-variant-numeric: tabular-nums;
}
.footer-mark {
position: absolute;
bottom: 10mm;
left: 22mm;
font-family: var(--mono);
font-size: 9pt;
color: var(--stone);
letter-spacing: 1.5pt;
text-transform: uppercase;
}
/* --- Cover --- */
.cover {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32pt;
align-items: center;
}
.cover-left { display: flex; flex-direction: column; }
.cover .mark {
font-family: var(--mono);
font-size: 10pt;
color: var(--brand);
letter-spacing: 3pt;
text-transform: uppercase;
margin-bottom: 22pt;
}
.cover h1 {
font-family: var(--serif);
font-size: 42pt;
font-weight: 500;
line-height: 1.05;
letter-spacing: -1.5pt;
color: var(--near-black);
margin-bottom: 16pt;
}
.cover .sub {
font-family: var(--serif);
font-size: 15pt;
color: var(--olive);
line-height: 1.45;
max-width: 34ch;
}
.cover .line {
width: 60pt;
height: 2pt;
background: var(--brand);
margin: 20pt 0 16pt 0;
}
.cover .meta {
font-family: var(--sans);
font-size: 10pt;
color: var(--stone);
letter-spacing: 0.4pt;
}
.cover-visual { display: flex; align-items: center; justify-content: center; }
/* --- Content slide --- */
.slide-head {
margin-bottom: 10pt;
display: flex;
justify-content: space-between;
align-items: baseline;
}
.slide-title {
font-family: var(--serif);
font-size: 38pt;
font-weight: 500;
line-height: 1.1;
letter-spacing: -0.6pt;
color: var(--near-black);
max-width: 24ch;
margin-top: 6pt;
margin-bottom: 28pt;
}
.body-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 28pt;
}
.body-single {
max-width: 70ch;
flex: 1;
}
.claim {
font-family: var(--serif);
font-size: 17pt;
line-height: 1.45;
color: var(--dark-warm);
margin-bottom: 14pt;
}
.claim .hl { color: var(--brand); }
ul.pts {
list-style: none;
counter-reset: pts;
}
ul.pts li {
counter-increment: pts;
font-family: var(--sans);
font-size: 12.5pt;
line-height: 1.5;
color: var(--dark-warm);
padding-left: 18pt;
position: relative;
margin-bottom: 9pt;
}
ul.pts li::before {
content: counter(pts) ".";
position: absolute;
left: 0;
color: var(--brand);
font-weight: 500;
font-variant-numeric: tabular-nums;
}
ul.pts li .hl { color: var(--brand); font-weight: 500; }
/* kami-native callout: transparent + single brand left bar */
.callout {
background: transparent;
border-left: 2pt solid var(--brand);
padding: 8pt 0 8pt 16pt;
font-family: var(--serif);
font-size: 14pt;
line-height: 1.5;
color: var(--olive);
margin-top: 4pt;
}
.callout .hl { color: var(--brand); }
/* kami-native metrics: vertical stack, number above label.
Stacked layout keeps all 3 numbers on one baseline regardless of label
length; horizontal baseline-align breaks visually when labels wrap. */
.metrics {
display: flex;
gap: 20pt;
margin-top: 20pt;
padding-top: 12pt;
border-top: 0.3pt dotted var(--border);
}
.metric {
flex: 1;
display: flex;
flex-direction: column;
gap: 6pt;
}
.metric .v {
font-family: var(--serif);
font-size: 32pt;
font-weight: 500;
color: var(--brand);
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: -0.3pt;
}
.metric .l {
font-family: var(--sans);
font-size: 10pt;
color: var(--olive);
line-height: 1.4;
}
/* --- Diagram column --- */
.diagram-col {
display: flex;
align-items: flex-start;
justify-content: center;
margin-top: -20pt;
}
.diagram-col svg { width: 96%; }
/* --- Ending slide --- */
.ending { justify-content: center; align-items: center; text-align: center; }
.ending h1 {
font-family: var(--serif);
font-size: 52pt;
font-weight: 500;
color: var(--near-black);
letter-spacing: -0.8pt;
margin-bottom: 12pt;
}
.ending .sub {
font-family: var(--serif);
font-size: 17pt;
color: var(--olive);
max-width: 44ch;
line-height: 1.45;
}
.ending .line {
width: 60pt;
height: 1.5pt;
background: var(--brand);
margin: 22pt auto;
}
/* --- Code Card --- */
.code-card {
background: var(--ivory);
border: 1px solid #e8e5da;
border-radius: 6pt;
padding: 14pt 18pt;
font-family: var(--mono);
font-size: 10pt;
line-height: 1.55;
color: var(--near-black);
white-space: pre;
overflow: hidden;
}
.code-card .k { color: var(--brand); }
.code-card .c { color: #6b6a64; }
</style>
</head>
<body>
<!-- ═══════════ 01 · Cover ═══════════ -->
<section class="slide cover">
<div class="cover-left">
<div class="mark">Keynote · 2026</div>
<h1>Things you don't know about Agents</h1>
<div class="sub">Loops, harness, context, memory: what actually moves the needle in production.</div>
<div class="line"></div>
<div class="meta">kami slides demo · A4 landscape · 8 slides</div>
</div>
<div class="cover-visual">
<!-- Decorative loop mark: concentric arcs in brand + stone -->
<svg viewBox="0 0 260 260" xmlns="http://www.w3.org/2000/svg" style="width: 220pt;">
<circle cx="130" cy="130" r="100" fill="none" stroke="#e8e6dc" stroke-width="1"/>
<circle cx="130" cy="130" r="72" fill="none" stroke="#e8e6dc" stroke-width="1"/>
<circle cx="130" cy="130" r="44" fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
<!-- arc segments on outer ring to suggest cycle -->
<path d="M 130 30 A 100 100 0 0 1 230 130" fill="none" stroke="#1B365D" stroke-width="2.2" stroke-linecap="round"/>
<path d="M 230 130 A 100 100 0 0 1 130 230" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
<path d="M 130 230 A 100 100 0 0 1 30 130" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
<path d="M 30 130 A 100 100 0 0 1 130 30" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
<!-- center label -->
<text x="130" y="125" text-anchor="middle" font-size="10" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">Agent</text>
<text x="130" y="141" text-anchor="middle" font-size="8" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="2">LOOP</text>
</svg>
</div>
<div class="page-num">01</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 02 · Agent loop ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">01 · Agent Loop</span>
</div>
<div class="slide-title">Simple core,<br>complex surroundings</div>
<div class="body-grid">
<div>
<p class="claim">The loop is small. The <span class="hl">infrastructure around it</span> is what keeps it stable as features grow.</p>
<ul class="pts">
<li>A working Agent loop fits in about <span class="hl">20 lines</span> of code.</li>
<li>Control flow lives in the tools, not in branchy internal state.</li>
<li>Workflow vs Agent: predefined paths in code vs model picks paths at runtime.</li>
</ul>
<div class="callout">
If your loop keeps growing every sprint, you are fixing the wrong layer. The tax is paid <span class="hl">outside</span> the loop.
</div>
</div>
<div class="diagram-col">
<!-- Agent loop cycle diagram. viewBox 680×560 (same ratio as reference).
4 nodes 200×72 rx=8, centered at NESW with 12-14px inset from viewBox edges.
Ring r=192 grazes top/bottom nodes at their inner edges, passes through left/right.
Q-curve arcs: control at tangent-axis outer corner, giving true quarter-circle sweep.
Chevron marker (open path, not filled polygon). Core is the only focal element. -->
<svg viewBox="0 0 680 560" xmlns="http://www.w3.org/2000/svg">
<!-- surroundings label -->
<text x="340" y="12" text-anchor="middle" font-size="9" fill="#6b6a64" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">TOOLS · HARNESS · EVALS · MEMORY</text>
<!-- dashed ink-blue ring -->
<circle cx="340" cy="280" r="192" fill="none" stroke="#1B365D" stroke-width="1" stroke-dasharray="4 3"/>
<!-- PLAN: x=240 y=16 w=200 h=72 rx=8, center (340, 52) -->
<rect x="240" y="16" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
<text x="340" y="52" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">PLAN</text>
<!-- ACT: x=468 y=244 w=200 h=72 rx=8, center (568, 280) -->
<rect x="468" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
<text x="568" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">ACT</text>
<!-- OBSERVE: x=240 y=472 w=200 h=72 rx=8, center (340, 508) -->
<rect x="240" y="472" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
<text x="340" y="508" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">OBSERVE</text>
<!-- REFLECT: x=12 y=244 w=200 h=72 rx=8, center (112, 280) -->
<rect x="12" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
<text x="112" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">REFLECT</text>
<!-- center: focal CORE text -->
<text x="340" y="268" text-anchor="middle" dominant-baseline="central" font-size="16" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">20 LOC</text>
<text x="340" y="292" text-anchor="middle" dominant-baseline="central" font-size="10" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">CORE</text>
<!-- clockwise arcs (no marker-end; WeasyPrint ignores orient="auto").
Arrowheads drawn manually as chevron paths below each arc. -->
<!-- PLAN right-mid (440,52) → ACT top-mid (568,244) via (568,52) -->
<path d="M 440 52 Q 568 52 568 244" fill="none" stroke="#504e49" stroke-width="1.5"/>
<!-- chevron ↓ at (568, 244) -->
<path d="M 560 236 L 568 244 L 576 236" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- ACT bottom-mid (568,316) → OBSERVE right-mid (440,508) via (568,508) -->
<path d="M 568 316 Q 568 508 440 508" fill="none" stroke="#504e49" stroke-width="1.5"/>
<!-- chevron ← at (440, 508) -->
<path d="M 448 500 L 440 508 L 448 516" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- OBSERVE left-mid (240,508) → REFLECT bottom-mid (112,316) via (112,508) -->
<path d="M 240 508 Q 112 508 112 316" fill="none" stroke="#504e49" stroke-width="1.5"/>
<!-- chevron ↑ at (112, 316) -->
<path d="M 104 324 L 112 316 L 120 324" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<!-- REFLECT top-mid (112,244) → PLAN left-mid (240,52) via (112,52) -->
<path d="M 112 244 Q 112 52 240 52" fill="none" stroke="#504e49" stroke-width="1.5"/>
<!-- chevron → at (240, 52) -->
<path d="M 232 44 L 240 52 L 232 60" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="page-num">02</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 01 · Agent Loop (traced) ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">01 · Agent Loop</span>
</div>
<div class="slide-title">One turn, traced</div>
<p class="claim" style="max-width: 60ch;">The same loop, made concrete: a single user turn flows through the agent to a tool and back.</p>
<div style="flex:1; display:flex; align-items:center; justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 280" width="640" height="427" style="background:#f5f4ed">
<style>
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
</style>
<defs>
<marker id="seq-arrow" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
<polygon points="0 0, 8 2.5, 0 5" fill="#1b365d" />
</marker>
<marker id="seq-arrow-open" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
<polyline points="0 0, 8 2.5, 0 5" fill="none" stroke="#1b365d" stroke-width="1" />
</marker>
</defs>
<line class="lifeline" data-actor="U" x1="70" y1="70" x2="70" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
<line class="lifeline" data-actor="A" x1="210" y1="70" x2="210" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
<line class="lifeline" data-actor="T" x1="350" y1="70" x2="350" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
<g class="message" data-from="U" data-to="A" data-label="ask" data-line-style="solid" data-arrow-head="filled" data-self="false">
<line x1="70" y1="90" x2="210" y2="90" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
<text x="140" y="80" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">ask</text>
</g>
<g class="message" data-from="A" data-to="T" data-label="invoke(query)" data-line-style="solid" data-arrow-head="filled" data-self="false">
<line x1="210" y1="130" x2="350" y2="130" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
<text x="280" y="120" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">invoke(query)</text>
</g>
<g class="message" data-from="T" data-to="A" data-label="result" data-line-style="dashed" data-arrow-head="filled" data-self="false">
<line x1="350" y1="170" x2="210" y2="170" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
<text x="280" y="160" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">result</text>
</g>
<g class="message" data-from="A" data-to="U" data-label="answer" data-line-style="dashed" data-arrow-head="filled" data-self="false">
<line x1="210" y1="210" x2="70" y2="210" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
<text x="140" y="200" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">answer</text>
</g>
<g class="actor" data-id="U" data-label="User" data-type="participant">
<rect x="30" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
<text x="70" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">User</text>
</g>
<g class="actor" data-id="A" data-label="Agent" data-type="participant">
<rect x="170" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
<text x="210" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">Agent</text>
</g>
<g class="actor" data-id="T" data-label="Tool" data-type="participant">
<rect x="310" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
<text x="350" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">Tool</text>
</g>
</svg>
</div>
<div class="page-num">03</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 03 · Harness over models ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">02 · Harness</span>
</div>
<div class="slide-title">Harness wins<br>over hardware</div>
<div class="body-single">
<p class="claim">More expensive models bring gains <span class="hl">far smaller than expected</span>. Verification, boundaries, feedback, and fallbacks matter more than model capability.</p>
<ul class="pts">
<li>Upgrade the harness first. If accuracy does not move, then try the model.</li>
<li>Evaluation is the only honest signal. Test harness before test model.</li>
<li>Smaller model + strong harness routinely beats larger model + weak harness in production.</li>
</ul>
</div>
<div class="metrics">
<div class="metric">
<div class="v">20</div>
<div class="l">lines of code in a working Agent core loop</div>
</div>
<div class="metric">
<div class="v">4</div>
<div class="l">harness layers that matter: verify, bound, feedback, fallback</div>
</div>
<div class="metric">
<div class="v">10×</div>
<div class="l">velocity gains trace to execution discipline, not model swaps</div>
</div>
</div>
<div class="page-num">04</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 04 · Context ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">03 · Context</span>
</div>
<div class="slide-title">Density beats length</div>
<div class="body-grid">
<div>
<p class="claim">Long context windows do not fix weak context design. <span class="hl">Context Rot</span> sets in around 300–400K tokens regardless of the model.</p>
<ul class="pts">
<li>Layer the load: constant, on-demand, runtime, memory, system.</li>
<li>Index first, full content on demand. Beats dumping everything up front.</li>
<li>Stable prompt prefixes let caching actually pay off.</li>
<li>Every token that is not load-bearing is diluting signal.</li>
</ul>
</div>
<div class="diagram-col">
<!-- Context: flat vs layered accuracy comparison -->
<svg viewBox="0 0 280 230" xmlns="http://www.w3.org/2000/svg">
<!-- Background -->
<rect width="280" height="230" fill="#f5f4ed"/>
<!-- Title -->
<text x="140" y="18" text-anchor="middle" font-size="8.5" fill="#6b6a64" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">LOADING STRATEGY vs ACCURACY</text>
<!-- Y axis label -->
<text x="14" y="130" text-anchor="middle" font-size="8" fill="#504e49" font-family="Charter, Georgia, serif" transform="rotate(-90 14 130)">Accuracy</text>
<!-- Y axis line -->
<line x1="36" y1="30" x2="36" y2="180" stroke="#504e49" stroke-width="0.8"/>
<!-- Y axis ticks -->
<line x1="30" y1="56" x2="36" y2="56" stroke="#504e49" stroke-width="0.8"/>
<text x="26" y="59" text-anchor="end" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">85%</text>
<line x1="30" y1="120" x2="36" y2="120" stroke="#504e49" stroke-width="0.8"/>
<text x="26" y="123" text-anchor="end" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">53%</text>
<!-- X axis -->
<line x1="36" y1="180" x2="256" y2="180" stroke="#504e49" stroke-width="0.8"/>
<!-- bar 1: flat loading -->
<rect x="56" y="120" width="68" height="60" fill="#e8e6dc" stroke="none"/>
<text x="90" y="112" text-anchor="middle" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">53%</text>
<text x="90" y="198" text-anchor="middle" font-size="8.5" fill="#504e49" font-family="Charter, Georgia, serif">Flat</text>
<text x="90" y="209" text-anchor="middle" font-size="7.5" fill="#6b6a64" font-family="'JetBrains Mono', monospace">loading</text>
<!-- bar 2: layered loading (brand) -->
<rect x="156" y="56" width="68" height="124" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
<text x="190" y="48" text-anchor="middle" font-size="8" fill="#1B365D" font-family="Charter, Georgia, serif" font-weight="500">85%</text>
<text x="190" y="198" text-anchor="middle" font-size="8.5" fill="#1B365D" font-family="Charter, Georgia, serif" font-weight="500">Layered</text>
<text x="190" y="209" text-anchor="middle" font-size="7.5" fill="#1B365D" font-family="'JetBrains Mono', monospace">loading</text>
<!-- dotted line at 85 -->
<line x1="36" y1="56" x2="256" y2="56" stroke="#1B365D" stroke-width="0.6" stroke-dasharray="3 3"/>
</svg>
</div>
</div>
<div class="page-num">05</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 05 · Tools + Memory ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">04 · Tools & Memory</span>
</div>
<div class="slide-title">Put state outside<br>the context</div>
<div class="body-grid">
<div>
<p class="claim">Tools should match Agent goals, not underlying API shapes. Memory lives on disk, not in the window.</p>
<ul class="pts">
<li><span class="hl">ACI principle</span>: Agent-Computer Interface - design for what the Agent wants to do, not for the HTTP verb.</li>
<li>A bad tool description looks like a model failure until you re-read the description.</li>
<li>File-based state survives restarts. In-context state does not.</li>
</ul>
</div>
<div>
<p class="claim" style="font-size: 14pt;"><span class="hl">Four kinds of memory</span></p>
<ul class="pts">
<li><span class="hl">Working</span>: the context window - fast, expensive, temporary.</li>
<li><span class="hl">Procedural</span>: SKILL.md files - how to behave, loaded lazily.</li>
<li><span class="hl">Episodic</span>: JSONL logs - what happened, appendable.</li>
<li><span class="hl">Semantic</span>: MEMORY.md - what to remember across sessions, consolidated at thresholds.</li>
</ul>
<div class="callout">
Cross-session consistency needs explicit consolidation, not hope.
</div>
</div>
</div>
<div class="page-num">06</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 06 · Code Card demo ═══════════ -->
<section class="slide">
<div class="slide-head">
<span class="eyebrow">05 · Code Style</span>
</div>
<div class="slide-title">Pseudocode over syntax</div>
<div class="body-grid">
<div>
<p class="claim">Comments should outnumber code lines. The reader sees <span class="hl">logic</span>, not a language tutorial.</p>
<ul class="pts">
<li>Write the intent first, then the implementation detail below it.</li>
<li>Variable names describe what a thing <span class="hl">is</span>, not what type it has.</li>
<li>One concept per block. Split ruthlessly.</li>
</ul>
</div>
<div style="display:flex;flex-direction:column;gap:14pt;">
<div class="code-card"><span class="c"># resolve tool call or decide to stop</span>
<span class="k">function</span> agent_step(context, tools):
<span class="c"># model picks the next action</span>
action = <span class="k">model.think</span>(context)
<span class="c"># terminal condition: model says done</span>
<span class="k">if</span> action.type == <span class="k">"stop"</span>:
<span class="k">return</span> action.result
<span class="c"># delegate to the right tool</span>
result = tools[action.name](action.args)
<span class="k">return</span> agent_step(context + result, tools)</div>
</div>
</div>
<div class="page-num">07</div>
<div class="footer-mark">Agent Engineering</div>
</section>
<!-- ═══════════ 07 · Closing ═══════════ -->
<section class="slide ending">
<h1>Protocol first.<br>Then parallelism.</h1>
<div class="line"></div>
<div class="sub">Fix your evals before you tweak the Agent. Most of what looks like model failure is infrastructure noise in disguise.</div>
<div class="page-num">08</div>
<div class="footer-mark">End of Deck</div>
</section>
</body>
</html>
<!DOCTYPE html>
<!-- ==================================================================
CHANGELOG · English · parchment design system
1-2 page A4 release notes / version changelog
================================================================== -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mole · v1.7.1 Release Notes</title>
<meta name="author" content="Tw93">
<meta name="description" content="Mole v1.7.1 release notes: accessibility, a faster status board, and deeper cleanup, uninstall, and updates.">
<meta name="keywords" content="Mole, macOS, release notes, changelog, Mac utility">
<meta name="generator" content="Kami">
<style>
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
@page {
size: A4;
margin: 20mm 22mm 22mm 22mm;
background: #f5f4ed;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--parchment: #f5f4ed;
--ivory: #faf9f5;
--near-black:#141413;
--dark-warm: #3d3d3a;
--olive: #504e49;
--stone: #6b6a64;
--brand: #1B365D;
--border: #e8e6dc;
--border-soft:#e5e3d8;
--tag-bg: #E4ECF5;
--breaking-bg:#f0e0d8;
--breaking-fg:#8b4513;
--serif: Charter, Georgia,
Palatino, "Times New Roman", serif;
--sans: var(--serif);
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
Consolas, Monaco, monospace;
}
html, body { background: var(--parchment); }
@media screen {
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm; }
}
body {
color: var(--near-black);
font-family: var(--serif);
font-size: 10.5pt;
line-height: 1.55;
widows: 3;
orphans: 3;
}
strong { font-weight: 500; }
.hl { color: var(--brand); font-weight: 500; }
/* HEADER - left-aligned editorial opener, matches the one-pager. */
.header {
margin-bottom: 20pt;
padding-bottom: 13pt;
border-bottom: 0.5pt solid var(--border);
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20pt;
}
.title-block { flex: 1; }
.eyebrow {
display: inline-flex;
align-items: center;
gap: 7pt;
font-family: var(--sans);
font-size: 9pt;
color: var(--brand);
letter-spacing: 1.5pt;
text-transform: uppercase;
font-weight: 500;
margin-bottom: 8pt;
}
.eyebrow::before {
content: "";
width: 8pt;
height: 1.5pt;
border-radius: 0.75pt;
background: var(--brand);
flex-shrink: 0;
}
h1 {
font-family: var(--serif);
font-size: 29pt;
font-weight: 500;
color: var(--near-black);
line-height: 1.1;
letter-spacing: -0.5pt;
margin-bottom: 7pt;
}
.subtitle {
font-size: 11.5pt;
color: var(--olive);
line-height: 1.45;
max-width: 150mm;
}
.meta {
font-size: 10pt;
color: var(--stone);
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
/* SECTION HEAD - brand left bar */
h2 {
font-family: var(--serif);
font-size: 14pt;
font-weight: 500;
color: var(--near-black);
margin: 20pt 0 10pt 0;
border-left: 2.5pt solid var(--brand);
border-radius: 1.5pt;
padding-left: 9pt;
break-after: avoid;
}
h2:first-of-type { margin-top: 0; }
code { font-family: var(--mono); font-size: 0.92em; }
/* CHANGE LIST - numbered with brand-colored markers and a bold lead-in.
Numbers carry sequence without a repeated decorative glyph; the section
bars stay the only brand marks. Each section restarts at 1. */
ol.changes { margin: 0; padding-left: 18pt; }
ol.changes li {
margin-bottom: 7pt;
padding-left: 3pt;
line-height: 1.55;
break-inside: avoid;
}
ol.changes li:last-child { margin-bottom: 0; }
ol.changes li::marker { color: var(--brand); font-weight: 500; }
ol.changes li strong { font-weight: 500; color: var(--near-black); }
ol.changes li .hl { color: var(--brand); font-weight: 500; }
/* ACKNOWLEDGEMENTS - a quiet labelled note, not a filled card, so the page
stays flat. The small brand label is the only marker it needs. */
.thanks {
margin-top: 18pt;
font-size: 9.5pt;
line-height: 1.55;
color: var(--olive);
break-inside: avoid;
}
.thanks-label {
font-family: var(--sans);
font-size: 9pt;
color: var(--brand);
letter-spacing: 1pt;
text-transform: uppercase;
font-weight: 500;
margin-bottom: 4pt;
}
.thanks .hl { color: var(--brand); font-weight: 500; }
.changelog-footer {
margin-top: 13pt;
padding-top: 8pt;
border-top: 0.3pt dotted var(--border);
font-size: 9pt;
color: var(--stone);
line-height: 1.5;
display: flex;
justify-content: space-between;
font-variant-numeric: tabular-nums;
letter-spacing: 0.3pt;
}
</style>
</head>
<body>
<div class="header">
<div class="title-block">
<div class="eyebrow">Release Notes</div>
<h1>Mole v1.7.1</h1>
<div class="subtitle">Accessibility throughout, a faster status board, and deeper cleanup, uninstall, and updates.</div>
</div>
<div class="meta">June 13, 2026</div>
</div>
<h2>Highlights</h2>
<ol class="changes">
<li><strong>Accessibility throughout</strong>: VoiceOver labels, smoother keyboard paths, and Reduce Motion across the app and the website, guided by feedback from <span class="hl">users with visual impairments</span>.</li>
<li><strong>A faster status board</strong>: the dashboard opens from your last snapshot while fresh data loads, process rows appear sooner, and power and temperature readings line up better.</li>
<li><strong>Deeper software and uninstall</strong>: Electron apps update inside Mole alongside Sparkle, Homebrew, and the App Store, and uninstall now connects startup items and package receipts.</li>
<li><strong>Clean covers more, still review-first</strong>: WeChat caches, oversized live logs, Xcode artifacts, and project <code>build</code> and <code>dist</code> folders are easy to review, group, or exclude.</li>
<li><strong>Fan control on Apple Silicon</strong>: presets now apply reliably, including M4-class Macs where the thermal controller had kept fan writes locked.</li>
<li><strong>License and devices</strong>: device lists load faster, you can release every Mac at once, and moving a license to a new machine is handled inside the app.</li>
</ol>
<h2>Fixes & polish</h2>
<ol class="changes">
<li><strong>Menu bar</strong>: the icon is restored when Control Center drops it, and the popover stays compact on shorter screens.</li>
<li><strong>Clean</strong>: AI tool caches such as ChatGPT, Codex, and Antigravity can be cleaned again.</li>
<li><strong>Privacy</strong>: camera and microphone use is detected as it starts, with fewer false alerts.</li>
<li><strong>Planets</strong>: rendered larger and more natural, with a remastered NASA Blue Marble Earth and a steadier spin.</li>
<li><strong>Analyze and Doctor</strong>: disk analysis uses less CPU on many-core Macs, and Doctor reports carry more system state for clearer issue reports.</li>
</ol>
<div class="thanks">
<div class="thanks-label">Acknowledgements</div>
Thanks to the Mole users with visual impairments whose feedback shaped this release's accessibility work, and to <span class="hl">@_huawuque</span> and <span class="hl">@liujiayi1111</span> for surfacing real-world issues before the stable build.
</div>
<div class="changelog-footer">
<span>Mole (鼴): a quiet keeper for your Mac.</span>
<span>mole.fit</span>
</div>
</body>
</html>
<!DOCTYPE html>
<!-- ==================================================================
LETTER · 中文 · parchment design system
A4 正式信件 / 推荐信
================================================================== -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>推荐信 · 陈知远</title>
<meta name="author" content="林川">
<meta name="description" content="一封推荐工程师陈知远的正式推荐信。">
<meta name="keywords" content="推荐信, 工程师, 正式信件">
<meta name="generator" content="Kami">
<style>
@font-face {
font-family: "TsangerJinKai02";
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "TsangerJinKai02";
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@page {
size: A4;
margin: 25mm 25mm 25mm 25mm;
background: #f5f4ed;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--parchment: #f5f4ed;
--near-black:#141413;
--dark-warm: #3d3d3a;
--olive: #504e49;
--stone: #6b6a64;
--brand: #1B365D;
--ivory: #faf9f5;
--border: #e8e6dc;
--border-soft:#e5e3d8;
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
--sans: var(--serif);
}
html, body { background: var(--parchment); }
@media screen {
body { max-width: 210mm; margin: 0 auto; padding: 25mm; }
}
body {
color: var(--near-black);
font-family: var(--serif);
font-size: 11.5pt;
line-height: 1.55;
letter-spacing: 0.3pt;
}
.letterhead {
text-align: right;
margin-bottom: 30pt;
font-size: 10pt;
line-height: 1.55;
color: var(--olive);
}
.letterhead .sender-name {
font-size: 13pt;
font-weight: 500;
color: var(--near-black);
margin-bottom: 4pt;
}
.letterhead a {
color: var(--dark-warm);
text-decoration: none;
}
.date {
text-align: right;
font-size: 10.5pt;
color: var(--olive);
margin-bottom: 24pt;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.recipient {
margin-bottom: 24pt;
line-height: 1.5;
}
.recipient .to {
font-size: 10pt;
color: var(--stone);
letter-spacing: 0.5pt;
text-transform: uppercase;
margin-bottom: 3pt;
}
.recipient .name {
font-size: 12pt;
font-weight: 500;
color: var(--near-black);
}
.recipient .org {
font-size: 10.5pt;
color: var(--olive);
}
.subject {
margin-bottom: 24pt;
border-left: 2.5pt solid var(--brand);
border-radius: 1.5pt;
padding-left: 8pt;
}
.subject .label {
font-family: var(--sans);
font-size: 9pt;
color: var(--brand);
letter-spacing: 1pt;
text-transform: uppercase;
margin-bottom: 3pt;
}
.subject .title {
font-size: 15pt;
font-weight: 500;
color: var(--near-black);
line-height: 1.3;
}
.salutation {
margin-bottom: 18pt;
font-size: 12pt;
color: var(--near-black);
}
.body-content p {
margin-bottom: 14pt;
text-align: justify;
text-justify: inter-ideograph;
line-height: 1.55;
text-indent: 2em;
}
.body-content p:first-child { text-indent: 2em; }
.body-content p.no-indent { text-indent: 0; }
.body-content .hl {
color: var(--brand);
font-weight: 500;
}
.closing {
margin-top: 30pt;
line-height: 1.55;
}
.closing .regards {
margin-bottom: 30pt;
color: var(--near-black);
}
.closing .signature {
font-size: 14pt;
font-weight: 500;
color: var(--near-black);
margin-bottom: 3pt;
}
.closing .signoff-meta {
font-size: 10pt;
color: var(--olive);
}
.attachments {
margin-top: 36pt;
padding-top: 10pt;
border-top: 0.5pt dotted var(--border);
font-size: 9.5pt;
color: var(--stone);
font-family: var(--sans);
line-height: 1.55;
}
.attachments .label {
color: var(--brand);
margin-right: 6pt;
font-weight: 500;
}
</style>
</head>
<body>
<div class="letterhead">
<div class="sender-name">林川</div>
<div>云栖科技 · 平台工程部 技术总监</div>
<div>010-8000 1234 · <a href="mailto:linchuan@example.com">linchuan@example.com</a></div>
</div>
<div class="date">2026 年 6 月 14 日</div>
<div class="recipient">
<div class="to">致</div>
<div class="name">招聘委员会</div>
<div class="org">星核实验室 · 研发中心</div>
</div>
<div class="subject">
<div class="label">关于</div>
<div class="title">推荐陈知远担任高级后端工程师</div>
</div>
<div class="salutation">尊敬的招聘委员会:</div>
<div class="body-content">
<p>
我谨以诚挚的态度,推荐陈知远先生加入贵实验室研发团队。过去四年他在我负责的平台工程部任后端工程师,是少有的既能把架构想清楚、又能把代码写扎实的人。
</p>
<p>
他最让我看重的,是在复杂系统中定位根因的能力。去年核心结算服务间歇性超时,多位工程师排查两周无果,是他坚持加日志、抓运行时证据,最终定位到一处连接池配置的边界问题,<span class="hl">将接口 P99 延迟从 1.8 秒降到 120 毫秒</span>。
</p>
<p>
他也是一位可靠的协作者:主导的服务拆分横跨三个团队,既能写清楚设计文档说服评审,也愿在上线当晚守到最后一台机器迁移完成。我相信他的工程判断力与责任心,会让他在更具挑战的课题中迅速成长。如需了解更多细节,欢迎随时与我联系。
</p>
</div>
<div class="closing">
<div class="regards">
此致<br>敬礼!
</div>
<div class="signature">林 川</div>
<div class="signoff-meta">
云栖科技 · 平台工程部 技术总监<br>
2026 年 6 月 14 日
</div>
</div>
<div class="attachments">
<span class="label">附件</span>
① 陈知远个人简历 · ② 核心项目技术总结
</div>
</body>
</html>
<!DOCTYPE html>
<!-- ==================================================================
ONE-PAGER · English · parchment design system
Product brief with a hero product shot
================================================================== -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mole · Product Brief</title>
<meta name="author" content="Tw93">
<meta name="description" content="Mole folds five Mac maintenance tools into one quiet, buy-once binary.">
<meta name="keywords" content="Mole, macOS, cleanup, uninstall, disk analyzer, Mac utility">
<meta name="generator" content="Kami">
<style>
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
@page {
size: A4;
margin: 15mm 18mm 15mm 18mm;
background: #f5f4ed;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--parchment: #f5f4ed;
--ivory: #faf9f5;
--near-black:#141413;
--dark-warm: #3d3d3a;
--olive: #504e49;
--stone: #6b6a64;
--brand: #1B365D;
--border: #e8e6dc;
--border-soft:#e5e3d8;
--serif: Charter, Georgia,
Palatino, "Times New Roman", serif;
--sans: var(--serif);
}
html, body { background: var(--parchment); }
@media screen {
body { max-width: 210mm; margin: 0 auto; padding: 15mm 18mm; }
}
body {
color: var(--near-black);
font-family: var(--serif);
font-size: 10pt;
line-height: 1.45;
}
/* HEADER - quiet editorial opener: brand tick eyebrow + clean title,
anchored by one hairline rule. No full-height left bar. */
.header {
margin-bottom: 14pt;
padding-bottom: 11pt;
border-bottom: 0.5pt solid var(--border);
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20pt;
}
.title-block { flex: 1; }
.eyebrow {
display: inline-flex;
align-items: center;
gap: 7pt;
font-size: 9pt;
color: var(--brand);
letter-spacing: 1.5pt;
text-transform: uppercase;
margin-bottom: 7pt;
font-weight: 500;
}
.eyebrow::before {
content: "";
width: 8pt;
height: 1.5pt;
border-radius: 0.75pt;
background: var(--brand);
flex-shrink: 0;
}
h1 {
font-family: var(--serif);
font-size: 26pt;
font-weight: 500;
color: var(--near-black);
line-height: 1.12;
letter-spacing: -0.3pt;
margin-bottom: 5pt;
}
.subtitle {
font-size: 11.5pt;
color: var(--olive);
line-height: 1.4;
}
.meta {
font-size: 9pt;
color: var(--stone);
text-align: right;
line-height: 1.5;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.lead {
font-size: 12pt;
line-height: 1.5;
color: var(--dark-warm);
margin-bottom: 13pt;
}
.lead .hl { color: var(--brand); font-weight: 500; }
.block-intro {
font-size: 10.5pt;
line-height: 1.5;
color: var(--olive);
margin-bottom: 12pt;
}
.block-intro .hl { color: var(--brand); font-weight: 500; }
/* PRODUCT SHOT - the single visual anchor. */
.product-shot {
margin-bottom: 14pt;
break-inside: avoid;
}
.product-shot .frame {
height: 116mm;
overflow: hidden;
border-radius: 5pt;
box-shadow: 0 1.5pt 6pt rgba(20, 20, 19, 0.16);
}
.product-shot .frame img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
.product-shot figcaption {
margin-top: 7pt;
font-size: 8.5pt;
color: var(--stone);
line-height: 1.45;
text-align: center;
}
.product-shot figcaption .hl { color: var(--brand); font-weight: 500; }
.metrics {
display: flex;
gap: 16pt;
margin-bottom: 14pt;
padding: 0 0 11pt 0;
border-bottom: 0.3pt dotted var(--border);
}
.metric {
flex: 1;
display: flex;
align-items: baseline;
gap: 5pt;
break-inside: avoid;
}
.metric-value {
font-family: var(--serif);
font-size: 21pt;
font-weight: 500;
color: var(--brand);
line-height: 1;
font-variant-numeric: tabular-nums;
white-space: nowrap;
letter-spacing: -0.3pt;
}
.metric-label {
font-size: 9pt;
color: var(--olive);
line-height: 1.3;
white-space: nowrap;
}
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18pt;
}
section { break-inside: avoid; }
h2 {
font-family: var(--serif);
font-size: 14pt;
font-weight: 500;
color: var(--near-black);
margin-bottom: 5pt;
}
p { line-height: 1.45; }
ul.dash {
list-style: none;
margin: 4pt 0 0 0;
padding: 0;
}
ul.dash li {
position: relative;
padding-left: 14pt;
margin-bottom: 3.5pt;
line-height: 1.4;
}
ul.dash li:last-child { margin-bottom: 0; }
ul.dash li::before {
content: "";
position: absolute;
left: 0;
top: 5pt;
width: 5pt;
height: 1.5pt;
border-radius: 0.75pt;
background: var(--brand);
}
ul.dash li strong { color: var(--near-black); font-weight: 500; }
.footer {
margin-top: 14pt;
padding-top: 6pt;
border-top: 0.3pt dotted var(--border);
font-size: 9pt;
color: var(--stone);
display: flex;
justify-content: space-between;
font-variant-numeric: tabular-nums;
letter-spacing: 0.3pt;
}
</style>
</head>
<body>
<div class="header">
<div class="title-block">
<div class="eyebrow">Product Brief</div>
<h1>Five Mac tools, one quiet binary</h1>
<div class="subtitle">Mole cleans, uninstalls, optimizes, analyzes, and monitors, then gets out of your way.</div>
</div>
<div class="meta">
Tw93<br>
2026.06.14<br>
v1.7.1
</div>
</div>
<p class="lead">
Mac maintenance scattered into five subscription apps, each wanting a menu-bar slot and a yearly fee. Mole folds them back into <span class="hl">one quiet binary you buy once</span>.
</p>
<figure class="product-shot">
<div class="frame"><img src="demo-mole-clean.jpg" alt="Mole clean view showing an earth and a scan button"></div>
<figcaption>One window, five tools. Mole <span class="hl">replaces CleanMyMac, App Cleaner, Sensei, DaisyDisk, and iStat Menus</span>.</figcaption>
</figure>
<div class="metrics">
<div class="metric">
<div class="metric-value">5</div>
<div class="metric-label">tools, one binary</div>
</div>
<div class="metric">
<div class="metric-value">$19</div>
<div class="metric-label">no subscription</div>
</div>
<div class="metric">
<div class="metric-value">2</div>
<div class="metric-label">Macs per license</div>
</div>
<div class="metric">
<div class="metric-value">0</div>
<div class="metric-label">menu-bar clutter</div>
</div>
</div>
<p class="block-intro">
Each tool gets its own page and a single job, with no browser tab and no menu-bar widget in sight. The result is a utility you <span class="hl">set up once and barely notice</span> afterward.
</p>
<div class="two-col">
<section>
<h2>Why Mole</h2>
<ul class="dash">
<li>One-time <strong>$19</strong>, no yearly fee.</li>
<li>No menu-bar widget or upsell.</li>
<li>No telemetry, runs fully offline.</li>
<li>Native on Apple Silicon and Intel.</li>
<li>Supports macOS 14 and later.</li>
</ul>
</section>
<section>
<h2>What's inside</h2>
<ul class="dash">
<li><strong>Clean</strong> clears caches, logs, and leftovers.</li>
<li><strong>Uninstall</strong> removes apps and every trailing file.</li>
<li><strong>Optimize</strong> tunes startup and memory load.</li>
<li><strong>Analyze</strong> shows where disk space really goes.</li>
<li><strong>Status</strong> tracks CPU, disk, and network live.</li>
</ul>
</section>
</div>
<div class="footer">
<span>Mac utilities · macOS 14+ · Apple Silicon & Intel</span>
<span>mole.fit</span>
</div>
</body>
</html>
<!DOCTYPE html>
<!-- ==================================================================
DIAGRAM · Architecture (kami palette)
Components + connections in a system. One focal node, everything else
in warm neutrals. Drop the <svg> block into a long-doc or portfolio
figure element to embed.
================================================================== -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Architecture · kami diagram</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--parchment: #f5f4ed;
--ivory: #faf9f5;
--near-black: #141413;
--olive: #504e49;
--stone: #6b6a64;
--brand: #1B365D;
--brand-tint: #EEF2F7;
--border: #e8e6dc;
--serif: Charter, Georgia, Palatino, serif;
--sans: var(--serif);
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
}
body {
font-family: var(--sans);
background: var(--parchment);
color: var(--near-black);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 3rem 2rem;
}
.frame { max-width: 1000px; width: 100%; }
.eyebrow {
font-family: var(--mono);
font-size: 0.7rem;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--stone);
margin-bottom: 0.5rem;
}
h1 {
font-family: var(--serif);
font-size: 1.9rem;
font-weight: 500;
letter-spacing: -0.3pt;
line-height: 1.15;
margin-bottom: 1.6rem;
}
svg { width: 100%; min-width: 860px; display: block; }
.caption {
font-family: var(--serif);
font-size: 0.95rem;
color: var(--olive);
margin-top: 1rem;
max-width: 52ch;
}
</style>
</head>
<body>
<div class="frame">
<p class="eyebrow">Architecture · kami diagram</p>
<h1>{{System name}} in production</h1>
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
</pattern>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#504e49"/>
</marker>
<marker id="arrow-brand" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#f5f4ed"/>
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
<!-- Arrows (behind nodes) -->
<line x1="168" y1="240" x2="220" y2="240" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
<line x1="364" y1="240" x2="416" y2="240" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
<line x1="576" y1="224" x2="628" y2="180" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
<line x1="576" y1="256" x2="628" y2="300" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
<!-- Arrow labels (masked rect prevents line bleed-through) -->
<rect x="172" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
<text x="196" y="230" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">HTTPS</text>
<rect x="368" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
<text x="392" y="230" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">SSR</text>
<rect x="560" y="180" width="56" height="12" rx="2" fill="#f5f4ed"/>
<text x="588" y="190" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">READ</text>
<rect x="560" y="288" width="56" height="12" rx="2" fill="#f5f4ed"/>
<text x="588" y="298" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">QUERY</text>
<!-- Node 1: Reader (external) -->
<rect x="40" y="208" width="128" height="64" rx="6" fill="#f5f4ed"/>
<rect x="40" y="208" width="128" height="64" rx="6" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
<text x="62" y="226" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">USER</text>
<text x="104" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Reader</text>
<text x="104" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">Browser</text>
<!-- Node 2: Edge / CDN (cloud) -->
<rect x="220" y="208" width="144" height="64" rx="6" fill="#f5f4ed"/>
<rect x="220" y="208" width="144" height="64" rx="6" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
<text x="242" y="226" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">EDGE</text>
<text x="292" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">CDN</text>
<text x="292" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">cache · SSL</text>
<!-- Node 3: Origin (FOCAL - brand accent) -->
<rect x="416" y="208" width="160" height="64" rx="6" fill="#f5f4ed"/>
<rect x="416" y="208" width="160" height="64" rx="6" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
<text x="438" y="226" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">ORIGIN</text>
<text x="496" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">App Server</text>
<text x="496" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">render · route</text>
<!-- Node 4: Store (content) -->
<rect x="628" y="128" width="144" height="64" rx="6" fill="#f5f4ed"/>
<rect x="628" y="128" width="144" height="64" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
<text x="650" y="146" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">BUNDLE</text>
<text x="700" y="166" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Content</text>
<text x="700" y="182" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">*.mdx · assets</text>
<!-- Node 5: Database (store) -->
<rect x="628" y="288" width="144" height="64" rx="6" fill="#f5f4ed"/>
<rect x="628" y="288" width="144" height="64" rx="6" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
<text x="650" y="306" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">STORE</text>
<text x="700" y="326" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Database</text>
<text x="700" y="342" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">postgres · vectors</text>
<!-- Legend strip -->
<line x1="40" y1="390" x2="920" y2="390" stroke="#DEDED7" stroke-width="0.8"/>
<text x="40" y="406" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
<rect x="40" y="422" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
<text x="60" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Focal · origin</text>
<rect x="170" y="422" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
<text x="190" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Backend · bundle</text>
<rect x="320" y="422" width="14" height="10" rx="2" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
<text x="340" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Store</text>
<rect x="420" y="422" width="14" height="10" rx="2" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
<text x="440" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Cloud</text>
<rect x="510" y="422" width="14" height="10" rx="2" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
<text x="530" y="430" fill="#504e49" font-size="8.5" font-family="inherit">External</text>
<line x1="620" y1="428" x2="650" y2="428" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
<text x="658" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Standard flow</text>
<line x1="780" y1="428" x2="810" y2="428" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
<text x="818" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Primary path</text>
</svg>
<p class="caption">Focal rule: one ink-blue node per diagram, marking the component the reader should look at first. Every other box stays in warm neutrals so the accent actually means something.</p>
</div>
</body>
</html>
classDiagram
class Order {
+String id
+Date createdAt
+total() Money
}
class Customer {
+String name
}
class LineItem {
+int qty
}
Customer "1" --> "*" Order : places
Order "1" *-- "*" LineItem : contains
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE_ITEM : contains
PRODUCT ||--o{ LINE_ITEM : "appears in"
sequenceDiagram
participant U as 用户
participant A as API
participant D as 数据库
U->>A: 提交请求
A->>D: 查询记录
D-->>A: 返回结果
A-->>U: 响应数据
# {{PRODUCT_NAME}} - Full Knowledge Base
<!-- Placeholders to fill before publishing:
{{PRODUCT_NAME}}, {{TAGLINE_LONG_ONE_SENTENCE}}, {{AUTHOR_NAME}}, {{AUTHOR_URL}},
{{SOURCE_REPO_URL}}, {{OS_REQUIREMENT}}, {{ONE_PARAGRAPH_POSITIONING}},
{{ONE_PARAGRAPH_PRICE_TERMS_REFUND_LICENSE_SEATS}}, {{FEATURE_*_NAME}}, {{ONE_PARAGRAPH_FEATURE_*}},
{{COMPETITOR_A}}, {{COMPETITOR_B}}, {{GENERIC_TOOL}}, {{TWO_OR_THREE_SENTENCES}},
{{ANSWER_*_SENTENCES}}, {{COMMON_TOOL_USERS_ALREADY_USE}}, {{PRICE_AMOUNT}}, {{SITE_ORIGIN}} -->
> {{TAGLINE_LONG_ONE_SENTENCE}}
This is the long-form companion to llms.txt. Keep llms.txt as the short summary; put feature details, FAQ, and comparison here so AI assistants get accurate answers without having to scrape the site.
---
## Overview
{{PRODUCT_NAME}} is {{ONE_PARAGRAPH_POSITIONING}}.
Author: {{AUTHOR_NAME}} ({{AUTHOR_URL}}).
Source: {{SOURCE_REPO_URL}}.
Platform: {{OS_REQUIREMENT}}.
## Pricing
{{ONE_PARAGRAPH_PRICE_TERMS_REFUND_LICENSE_SEATS}}
## Features
### {{FEATURE_1_NAME}}
{{ONE_PARAGRAPH_FEATURE_1}}. Cover: what it does, when to use it, the most common output it produces, and any limit the user should know.
### {{FEATURE_2_NAME}}
{{ONE_PARAGRAPH_FEATURE_2}}
### {{FEATURE_3_NAME}}
{{ONE_PARAGRAPH_FEATURE_3}}
Repeat one subsection per feature. Aim for 3-7 features. Skip marketing adjectives - LLMs index facts, not enthusiasm.
## How {{PRODUCT_NAME}} differs
### vs {{COMPETITOR_A}}
{{TWO_OR_THREE_SENTENCES}}. State the concrete differentiator (workflow, output, distribution model, price). Avoid "we are better" framing.
### vs {{COMPETITOR_B}}
{{TWO_OR_THREE_SENTENCES}}.
### vs general-purpose tools (e.g. {{GENERIC_TOOL}})
{{TWO_OR_THREE_SENTENCES}}.
## FAQ
### What is {{PRODUCT_NAME}} for?
{{ANSWER_3_SENTENCES}}.
### Who is it not for?
{{ANSWER_2_SENTENCES}}. Be specific about workflows that are out of scope, so AI assistants do not over-recommend.
### How does it integrate with {{COMMON_TOOL_USERS_ALREADY_USE}}?
{{ANSWER_2_SENTENCES}}.
### What does the {{PRICE_AMOUNT}} buy?
{{ANSWER_2_SENTENCES}} including seat count, updates, and refund window.
### How do I get support?
{{ANSWER_1_SENTENCE}} with a real contact channel.
## Links
- Website: {{SITE_ORIGIN}}
- Website (Chinese): {{SITE_ORIGIN}}/zh/
- Documentation: {{SITE_ORIGIN}}/docs
- Help: {{SITE_ORIGIN}}/help
- Source: {{SOURCE_REPO_URL}}
- Releases: {{SITE_ORIGIN}}/releases
1.9.0
Related skills
How it compares
Use kami for styled document deliverables from markdown instead of generic markdown-to-HTML conversion without design hierarchy.
FAQ
What does kami do?
Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN uses TsangerJinKai02,
When should I use kami?
Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN uses TsangerJinKai02,
Is kami safe to install?
Review the Security Audits panel on this page before installing in production.