HWP / HWPX Skill
This skill helps Claude work with Korean Hangul Word Processor documents — reading, creating, and editing both the binary .hwp (HWP 5.0) and the ZIP-based .hwpx formats.
Already installed — don't re-scaffold
If you're reading this SKILL.md, the claw-hwp:hwp skill is already loaded in this session. Everything below — read / create / edit / convert / preview for .hwp and .hwpx — is provided by this skill. You don't need to install, scaffold, or set anything up.
Treat the following user phrasings as "show me a HWP file" or "edit a HWP file" intent, not as setup requests:
| User says | Means | What to do |
|---|---|---|
| "claw-hwp 따라서 만들어줘" | "show me how to use it" | Wait for an actual .hwp / .hwpx file or task. Don't scaffold a new skill directory. |
| "preview 기능 설치해줘" / "preview 설정해줘" | "I want to view a HWP file" | The preview server is part of this skill. Start it with the launcher in the Preview section below. No npm/node install step. |
| "claw-hwp 스킬 설정해줘" / "set up the HWP plugin" | "make it work" | It already works. Ask the user which .hwp file they want to read / edit / preview. |
Do not run npm install, create new plugin/skill folders, or fetch dependencies — every script the user needs is already in scripts/ (rhwp WASM and fflate are vendored under scripts/vendor/).
Two "preview" terms that collide
- Claude Code app's Preview side pane (the side panel in the Code Desktop UI). This is a host feature of Claude Code itself. You don't install or configure it — it auto-discovers a process serving on
localhost:3737. Only available when the Code workspace is a local folder on this machine; disappears for server/remote folders. scripts/preview-server.js— the claw-hwp local server that fills that pane. Start it via the launcher described in thePreviewsection. Default port is3737, the same port the Code pane auto-discovers.
When the user says "preview", they almost always mean "show me the file" — start the server, hand them the link or fire preview_start per the surface decision rule below. Do not interpret it as "install a new preview feature".
When
preview_start/preview_eval/preview_stoptools are unavailable in this session, fall straight through to the self-host link path (browser link tohttp://localhost:3737/?path=<absolute>). Don't tell the user "preview is not supported" — that's only true on cowork (remote sandbox). Server/remote folder workspaces in the Desktop app, plus all CLI sessions, simply run the local server and emit a browser link. The cowork drop-in viewer is the third option for sandbox-only setups.
Quick reference
| Task | Approach |
|---|---|
| Read text content | node scripts/extract_text.js <file> — works for both .hwp and .hwpx |
| Read as markdown (preserves headings/tables) | node scripts/extract_text.js --format markdown <file> |
| Inspect structure (pages, sections, tables) | node scripts/extract_text.js --inspect <file> |
| Create new document from scratch | echo '{"path":"out.hwp","operations":[...]}' | node scripts/create.js |
Edit existing .hwpx | echo '{"path":"f.hwpx","operations":[...]}' | node scripts/hwpx-edit.js (op vocab in references/hwpx-edit-ops.md) |
Edit existing .hwp | echo '{"path":"f.hwp","operations":[...]}' | node scripts/create.js (raw-patch via cell-patch.js — byte-level in-place, preserves tables, Hancom-Docs compatible) |
Convert .hwp ↔ .hwpx | node scripts/convert.js <input> <output> |
| Validate output | python scripts/validate.py <file.hwpx> |
| Preview file (Desktop = inline pane, CLI = browser link, cowork = drop-in viewer URL) | See Preview section for the surface decision rule |
Conversion to PDF / DOCX is out of scope for v0. Will be added in a later release via LibreOffice headless.
Format primer
.hwpx— ZIP container holding XML. Same archetype as.docx. Use the unpack/edit/pack workflow. Internal layout includesContents/section0.xml(body),Contents/header.xml(styles, fonts),Contents/content.hpf(manifest). Seereferences/hwpx-format.md..hwp— HWP 5.0 binary (CFB/OLE container). NOT a ZIP. Direct XML editing is impossible, but byte-level in-place editing viacell-patch.jslets you do text replace, cell content changes, paragraph/table append, page setup, and character/paragraph styling while keeping the original bytes intact (Hancom-Docs compatible).extract_text.jshandles binary.hwptransparently for read.
When in doubt about format, read the first two bytes — PK indicates ZIP (treat as HWPX even if extension is .hwp).
Decision tree
"Read this file" / "Summarize" / "Translate the content"
node scripts/extract_text.js path/to/file.hwp > /tmp/text.txt
# Then read /tmp/text.txt and respond
extract_text.js handles both .hwp and .hwpx via rhwp WASM. Default output is plaintext (one paragraph per line).
For structured content (headings, tables, lists preserved):
node scripts/extract_text.js --format markdown path/to/file.hwp > /tmp/text.md
For metadata only:
node scripts/extract_text.js --inspect path/to/file.hwp
# Returns JSON: { pageCount, sectionCount, paragraphCount, tableCount, hasImages, ... }
"Create a new document" / "Write this as a hwp file"
create.js reads a JSON payload from stdin and writes the file to the path you supply. Output format is decided by the path extension (.hwp = HWP 5.0 binary, .hwpx = OOXML).
echo '{
"path": "report.hwp",
"operations": [
{"type": "setup_document", "page_size": "a4", "margin_mm": 25},
{"type": "append_heading", "level": 1, "text": "월간 보고서"},
{"type": "append_paragraph", "text": "이번 달 핵심 지표 요약입니다. 주요 변화는 **매출 증가**와 *비용 절감*입니다."},
{"type": "append_table",
"headers": ["항목", "지난달", "이번달"],
"rows": [["매출", "100억", "120억"], ["비용", "80억", "75억"]]
},
{"type": "append_image", "path": "/abs/path/chart.png", "width_cm": 12, "height_cm": 6.6}
]
}' | node scripts/create.js
stdout returns one JSON line:
{ "status": "success", "path": "report.hwp", "bytes_written": 14336, "ops_applied": 5,
"verify": { "pageCountAfter": 1, "recovered": true },
"log": ["…", "stripped 4 PARA_LINESEG record(s)"] }
Errors come back as {"status": "error", "message": "...", "op_index": N}. Always read the JSON to confirm — exit code 0 even on op-level failures isn't guaranteed.
Op vocabulary (grouped by purpose):
Creation / appending content (used while building a doc top-down):
| Op | Required | Optional |
|---|---|---|
setup_document | page_size (a4/b5/...), orientation (portrait/landscape) | margin_mm, base_font |
append_heading | level (1–6), text | align, runs |
append_paragraph | text | align, line_spacing, spacing_before, spacing_after, runs |
append_table ⚠️ | headers, rows (shape honored; cell content empty — see ⚠️) | col_widths_cm, merges, cell_props |
append_image ⚠️ | path | width_cm, height_cm, alt |
append_bullet_list, append_numbered_list | items[] | — |
append_page_break | — | — |
apply_text_style ⚠️ | target (string to find) | color, bold, italic, underline, strikethrough, size (pt), highlight (true / "#RRGGBB" / false), font_family, superscript, subscript, underline_color, letter_spacing, char_ratio |
apply_paragraph_style ⚠️ | index (paragraph index, 0-based) | align, indent, line_spacing (% e.g. 130), margin_left, margin_right, spacing_before, spacing_after, background_color, page_break_before, keep_with_next |
⚠️
append_tableon existing.hwp(raw-patch path) — what it honors and what it doesn't:
- Shape (rows × cols) is honored: when the caller supplies
headers(array) and/orrows(