
Oma Hwp
- 17 installs
- 41 repo stars
- Updated August 4, 2026
- gracefullight/stock-checker
Convert Korean HWP, HWPX, and HWPML documents to Markdown or JSON using kordoc, preserving structure for LLM/RAG use.
About
Converts Korean HWP-family word processor files to Markdown or structured JSON, extracting headings, tables, lists, images, footnotes, and hyperlinks. A developer uses it to prepare Korean government or enterprise documents for LLM context and RAG.
- Handles .hwp, .hwpx, and .hwpml via kordoc
- Preserves headings, nested tables, lists, and footnotes for RAG
Oma Hwp by the numbers
- 17 all-time installs (skills.sh)
- Ranked #450 of 688 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gracefullight/stock-checker --skill oma-hwpAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 17 |
|---|---|
| repo stars | ★ 41 |
| Last updated | August 4, 2026 |
| Repository | gracefullight/stock-checker ↗ |
What it does
Convert Korean HWP, HWPX, and HWPML documents to Markdown or JSON using kordoc, preserving structure for LLM/RAG use.
Files
HWP Skill - HWP / HWPX / HWPML to Markdown Conversion
Scheduling
Goal
Convert Korean HWP-family documents into readable Markdown or structured JSON while preserving document structure for LLM context, RAG, government-document review, or enterprise document processing.
Intent signature
- User asks to convert, parse, read, extract, or transform
.hwp,.hwpx, or.hwpml. - User mentions Korean word processor files, Hangul documents, government forms, or "한글 파일".
- User needs headings, tables, nested tables, lists, images, footnotes, or hyperlinks extracted from HWP-family files.
When to use
- Converting Korean HWP documents (
.hwp,.hwpx,.hwpml) to Markdown - Preparing Korean government/enterprise documents for LLM context or RAG
- Extracting structured content (tables, headings, lists, images) from HWP
- User says "convert this HWP", "parse hwpx", "HWP to markdown", "한글 파일"
When NOT to use
- PDF files -> use
oma-pdf(OCR + Tagged PDF specialization) - XLSX / DOCX files -> out of scope; run
bunx kordocdirectly if needed (note:oma-docsis the documentation-drift skill, not a converter) - Generating or editing HWP documents -> out of scope
- Already-text files -> use Read tool directly
Expected inputs
input_path:.hwp,.hwpx, or.hwpmlfile pathoutput_pathoroutput_dir: optional explicit output targetformat: optional output format, defaultmarkdownpage_range: optional page or section rangekordoc_version: optional pinned kordoc version
Expected outputs
- Markdown output next to the input file or in the requested directory
- Optional JSON output when requested
- Post-processed Markdown with flattened GFM tables and stripped Private Use Area glyphs by default
- A short report with output path, detected source format, and conversion issues
Dependencies
bunandbunxbunx kordoc@latestor configured pinned kordoc versionresources/flatten-tables.tsfor Markdown cleanup- Local filesystem access to input and output paths
Control-flow features
- Branches by file extension, output target, format, page range, encryption/DRM state, and post-processing requirements
- Calls external CLI tools through
bunxandbun run - Reads local HWP-family files and writes local Markdown or JSON output
- Routes non-HWP inputs to other skills instead of stretching this skill's scope
Structural Flow
Entry
1. Confirm the input path exists. 2. Confirm the extension is .hwp, .hwpx, or .hwpml. 3. Resolve output path or directory and default filename. 4. Check that bun is available.
Scenes
1. PREPARE: Validate path, extension, size, output target, and requested format. 2. ACQUIRE: Detect source format and runtime availability. 3. ACT: Run kordoc with explicit output target and requested options. 4. VERIFY: Post-process Markdown and inspect structure for headings, tables, lists, images, and footnotes. 5. FINALIZE: Report output path, source format, and any conversion limitations.
Transitions
- If the input is
.pdf, stop and route tooma-pdf. - If the input is
.xlsxor.docx, explain that this skill does not advertise those formats. - If
bunis unavailable, stop and ask the user to install Bun. - If Markdown is produced, run
resources/flatten-tables.tsunless the caller explicitly needs HTML tables or PUA glyphs preserved. - If output is empty or garbled, consult
resources/troubleshooting.md.
Failure and recovery
| Failure | Recovery |
|---|---|
bun or bunx unavailable | Ask user to install Bun |
| Unsupported or mismatched format | Check extension and magic bytes, then route or stop |
| Encrypted or DRM-locked document | Report limitation and request an accessible copy when needed |
| Empty Markdown output | Treat as possible scanned-image content and recommend OCR outside this skill |
| Complex merged tables | Accept flattened Markdown or HTML fallback as best effort |
| Stale kordoc cache | Use bunx kordoc@latest or configured pinned version |
Cannot find module "turndown" from flatten-tables.ts | Run bun install in .agents/skills/oma-hwp/resources/ (its node_modules is gitignored and absent on fresh clones) |
Exit
- Success: output file exists and structure is readable after post-processing.
- Partial success: output exists with explicitly reported table, glyph, encryption, or fidelity limitations.
- Failure: no reliable output is produced and the blocking cause is reported.
Logical Operations
Actions
| Action | SSL primitive | Evidence |
|---|---|---|
| Validate file path and extension | VALIDATE | Input preflight in execution protocol |
| Check runtime availability | VALIDATE | bun --version |
| Select output target and format | SELECT | Output behavior and config |
| Run converter | CALL_TOOL | bunx kordoc@latest |
| Write output artifact | WRITE | Markdown or JSON output |
| Flatten tables and strip PUA glyphs | CALL_TOOL | resources/flatten-tables.ts |
| Inspect extraction quality | VALIDATE | Verification step |
| Report result | NOTIFY | Final user-facing summary |
Tools and instruments
kordoc: primary HWP-family conversion CLIflatten-tables.ts: post-processing for GFM tables and Hancom PUA cleanupbun/bunx: runtime and CLI executor
Canonical command path
bunx kordoc@latest "{input_path}" -o "{output_path}"
# fresh clone: run `bun install` in .agents/skills/oma-hwp/resources/ first (node_modules is gitignored)
bun ".agents/skills/oma-hwp/resources/flatten-tables.ts" "{output_path}"For batch conversion, use an explicit output directory:
bunx kordoc@latest "{input_pattern}" -d "{output_dir}"Resource scope
| Scope | Resource target |
|---|---|
LOCAL_FS | Input HWP-family files and generated outputs |
PROCESS | bunx kordoc and bun run subprocesses |
MEMORY | Format decisions, validation notes, and final report |
Preconditions
- Input file exists and is readable.
- Output location is writable or can be created.
bunis installed.kordoccan parse the document or fail with a reportable error.
Effects and side effects
- Creates Markdown or JSON output files.
- May flatten merged-cell tables, trading cell fidelity for Markdown compatibility.
- Strips Private Use Area characters by default because they render as blanks without Hancom fonts.
- Does not intentionally modify the source HWP-family document.
Guardrails
1. Always pass @latest or an explicit pinned version to avoid stale bunx cache. 2. Always pass an explicit output target when the user expects a file. 3. Do not add custom security defenses around kordoc's ZIP, XML, SSRF, or XSS defenses. 4. Report missing tables, garbled text, empty output, encrypted segments, and best-effort DRM extraction. 5. Keep full CLI details in resources/execution-protocol.md and troubleshooting branches in resources/troubleshooting.md.
Supported Formats
| Format | Extension | Notes |
|---|---|---|
| HWP 5.x binary | .hwp | Full support (incl. DRM-locked via kordoc's rhwp-algorithm port) |
| HWPX | .hwpx | Full support incl. nested tables, merged cells |
| HWPML | .hwp (XML variant) | Auto-detected by signature |
kordoc also parses PDF / XLSX / DOCX. Those are intentionally outside this skill's scope; see "When NOT to use".
References
- Execution protocol:
resources/execution-protocol.md - Troubleshooting:
resources/troubleshooting.md - Configuration:
config/hwp-config.yaml - Upstream: https://github.com/chrisryugj/kordoc
- Related:
../oma-pdf/SKILL.md(use for.pdfinputs)
# oma-hwp configuration
# HWP / HWPX / HWPML to Markdown conversion settings
format: markdown # "markdown" (default) or "json"
# kordoc version policy
# - "latest": always use latest (recommended for active kordoc development)
# - "pinned": use the version in `version.pinned`
version:
channel: latest
pinned: "2.4.0"
# Output behavior
output:
# When no output directory is specified:
# "same_dir" = output next to the input file
# "cwd" = output in current working directory
default_location: same_dir
# Officially supported formats (advertised scope).
# kordoc supports more formats, but this skill only covers the Korean word
# processor family. Route other inputs to the appropriate skill:
# - .pdf -> oma-pdf
# - .xlsx / .docx -> out of scope (run `bunx kordoc ...` manually; oma-docs is the doc-drift skill, not a converter)
supported_formats:
- hwp
- hwpx
- hwpml
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "oma-hwp-postprocess",
"dependencies": {
"turndown": "^7.2.4",
"turndown-plugin-gfm": "^1.0.2",
},
"devDependencies": {
"@types/node": "^25.9.1",
"@types/turndown": "^5.0.6",
},
},
},
"packages": {
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
"@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
"@types/turndown": ["@types/turndown@5.0.6", "", {}, "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg=="],
"turndown": ["turndown@7.2.4", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ=="],
"turndown-plugin-gfm": ["turndown-plugin-gfm@1.0.2", "", {}, "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg=="],
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
}
}
HWP Conversion - Execution Protocol
Step 0: Validate Input
1. Confirm the input file path exists 2. Check extension is one of .hwp, .hwpx, .hwpml
- If
.pdf-> hand off tooma-pdf - If
.xlsx/.docx-> not in scope; advise user to runbunx kordoc@latest <file>directly
3. Check file size (wc -c or ls -lh); warn if >100MB 4. Determine output location:
- If user specified output path (
-oor-d) → use it - Otherwise → same directory as the input file
5. Determine output filename: {input_basename}.md
Step 1: Check Runtime
bun --versionIf bun is not available, ask the user to install Bun (https://bun.sh). bunx is required.
Step 2: Convert
Important: Without-oor-d, kordoc prints the Markdown to stdout and does not create a file. Always pass an explicit output path to match this skill's "same directory as input" convention.
Standard conversion (single file, write next to input)
bunx kordoc@latest "{input_path}" -o "{output_path}"
# {output_path} = "{dirname(input_path)}/{basename_without_ext(input_path)}.md"Output directory (multiple files)
bunx kordoc@latest "{input_pattern}" -d "{output_dir}"Print to stdout (piping / preview)
bunx kordoc@latest "{input_path}"
# No file written; useful when piping to another toolPage / section range
bunx kordoc@latest "{input_path}" -p "{range}"JSON output (structured intermediate form)
bunx kordoc@latest "{input_path}" --format jsonNotes:
- Default format is
markdown. Pass--format jsononly when you need the structured AST. --silentsuppresses progress output, useful in automation / piping contexts.
Step 2.5: Post-process kordoc output (default)
resources/flatten-tables.ts cleans up two kordoc artifacts that hurt downstream use:
1. HTML `<table>` blocks: kordoc emits these when a table has colspan / rowspan because GFM cannot represent merged cells. Converted to GFM pipe tables via turndown-plugin-gfm. 2. Private Use Area characters: HWP references Hancom-font-specific glyphs via U+E000-U+F8FF / U+F0000-U+FFFFD / U+100000-U+10FFFD code points. Without the Hancom font these render as blanks or tofu squares. Silently stripped.
node "{skill_resources}/flatten-tables.ts" "{output_path}"{skill_resources}=.agents/skills/oma-hwp/resources- Ensure
bun installhas been run once inside that directory (it installsturndown+turndown-plugin-gfmlocally) - Merged cells get flattened during the conversion (accepted trade-off)
- Skip this step only if the caller explicitly needs HTML tables or PUA characters preserved (rare)
Step 3: Verify
1. Read the generated Markdown file 2. Verify structure:
- Headings preserved (
#,##, etc.) - Tables rendered (GFM pipe syntax, or HTML
<table>fallback for merged cells) - Lists maintained (bullets, numbered)
- Images referenced (relative paths or inline)
- Footnotes linked via
[^id]
3. If the file is empty or clearly garbled, consult troubleshooting.md 4. If output directory is temporary, move the file to the target location 5. If the user needs the content in-conversation, read and present it
Step 4: Report
Tell the user:
- Output file path
- Source format detected (HWP5 / HWPX / HWPML)
- Any issues encountered (encrypted segments, missing tables, best-effort DRM extraction)
- Page/section count if available
Error Recovery
| Error | Recovery |
|---|---|
bunx / bun not found | Ask user to install Bun: `curl -fsSL https://bun.sh/install \ |
지원하지 않는 파일 형식 / unsupported file format | Confirm extension matches actual content; check magic bytes |
암호화된 문서 / encrypted document | Ask user for password; current kordoc CLI may not accept it inline (document as limitation) |
| Empty Markdown output | Likely a scanned-image-only HWP; suggest OCR pipeline (out of this skill's scope) |
| Broken / missing tables | Complex merged-cell tables fall back to HTML <table>; accept as best-effort |
| kordoc crash or stack trace | Ensure @latest is passed (bunx cache can be stale); if persistent, capture fixture and file issue upstream at https://github.com/chrisryugj/kordoc/issues |
| Slow conversion on large files | Use -p to narrow the page range; process sections separately |
Pin vs Latest
- Default:
bunx kordoc@latest(always latest). Gets upstream fixes automatically.@latestis required because a barebunx kordoccan reuse a stale cached version indefinitely. - Reproducibility: pin with
bunx kordoc@2.4.0 ...or similar. Record the pinned version inconfig/hwp-config.yamlunderversion.pinnedand setversion.channel: pinned.
Scope Reminder
If the user hands you a .pdf / .xlsx / .docx with this skill activated, do not proceed. Redirect:
.pdf→ switch tooma-pdf.xlsx/.docx→ ask the user to runbunx kordoc@latest <file>directly; this skill does not advertise those formats
kordoc technically supports them, but routing keeps skill scopes clean.
#!/usr/bin/env node
/**
* flatten-tables.ts: post-process kordoc output:
* 1. convert HTML <table> blocks to GFM pipe tables
* 2. strip Private Use Area characters (Hancom font-specific glyphs)
*
* (1) kordoc emits HTML <table> when a table has colspan/rowspan because GFM
* cannot represent merged cells. This script trades merge-cell fidelity
* for a pure-Markdown output.
* (2) HWP references Hancom-font-specific glyphs via Private Use Area code
* points (U+E000-U+F8FF, U+F0000-U+FFFFD, U+100000-U+10FFFD). Without
* the Hancom font these render as blanks or tofu squares; stripping is
* the pragmatic default for AI / plain-MD consumption.
*
* Usage: node flatten-tables.ts <file.md> [<file.md>...]
*/
import { constants } from "node:fs";
import { access, readFile, writeFile } from "node:fs/promises";
import TurndownService from "turndown";
import { tables } from "turndown-plugin-gfm";
const td = new TurndownService({
headingStyle: "atx",
codeBlockStyle: "fenced",
emDelimiter: "*",
bulletListMarker: "-",
});
td.use(tables);
const files = process.argv.slice(2);
if (files.length === 0) {
console.error("Usage: node flatten-tables.ts <file.md> [<file.md>...]");
process.exit(1);
}
const TABLE_BLOCK = /<table[\s\S]*?<\/table>/g;
const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/gu;
async function main(): Promise<void> {
for (const path of files) {
try {
await access(path, constants.F_OK);
} catch {
console.error(`[flatten-tables] not found: ${path}`);
process.exitCode = 1;
continue;
}
const src = await readFile(path, "utf8");
let tableCount = 0;
let out = src.replace(TABLE_BLOCK, (match: string) => {
tableCount += 1;
return `\n\n${td.turndown(match).trim()}\n\n`;
});
let puaCount = 0;
out = out.replace(PUA, () => {
puaCount += 1;
return "";
});
if (tableCount === 0 && puaCount === 0) {
console.log(`[flatten-tables] ${path}: nothing to change`);
continue;
}
await writeFile(path, out, "utf8");
const parts: string[] = [];
if (tableCount) parts.push(`${tableCount} table(s) flattened`);
if (puaCount) parts.push(`${puaCount} PUA char(s) stripped`);
console.log(`[flatten-tables] ${path}: ${parts.join(", ")}`);
}
}
main().catch((err: unknown) => {
console.error("[flatten-tables]", err);
process.exit(1);
});
{
"name": "oma-hwp-postprocess",
"private": true,
"type": "module",
"dependencies": {
"turndown": "^7.2.4",
"turndown-plugin-gfm": "^1.0.2"
},
"devDependencies": {
"@types/node": "^25.9.1",
"@types/turndown": "^5.0.6"
}
}
oma-hwp Troubleshooting
Diagnosing conversion issues
1. "지원하지 않는 파일 형식" / unsupported file format
- Verify the extension matches the content. HWP5 files should start with
D0 CF 11 E0(OLE compound). HWPX starts with50 4B 03 04(ZIP). - Check with:
xxd "{file}" | head -1 - If the extension is wrong, rename and retry.
- If the file is genuinely a different format (e.g., Word, Excel), use the appropriate skill.
2. Encrypted or password-protected HWP
- kordoc reports
암호화된 문서/ "encrypted document". - General password-locked HWP: the CLI currently has no inline password flag. Ask the user to decrypt the file first (via Hancom Office) and retry.
- DRM "distribution-only" HWP (배포용): kordoc ports rhwp's AES-128 ECB algorithm and often succeeds. If it still fails, note as best-effort and continue with other files.
3. Empty Markdown output
Most likely causes:
- HWP contains only scanned images without a text layer → needs OCR (out of scope).
- kordoc bug on a specific document → try
bunx kordoc@latest; if still empty, report upstream with a sample.
4. Missing or broken tables
- Simple tables: GFM pipe syntax.
- Tables with
colspan/rowspan: kordoc falls back to HTML<table>. This is expected and correct. - Nested tables: large nested tables become separate blocks; small ones get flattened into the parent cell.
- If tables are completely absent, confirm the source file actually contains tables (sometimes they are images).
5. Hyperlinks look wrong
kordoc sanitizes links (XSS defense). If a legitimate link is stripped, verify the original URL scheme: only http://, https://, mailto:, and relative paths are preserved by default.
6. Image extraction issues
- kordoc extracts images from ZIP entries (HWPX) and HWP5
BinDatastreams. - Output is typically written as
{input_basename}.assets/next to the Markdown file. - If images are missing, check whether the source file truly embeds them (vs linking externally).
7. Slow conversion / timeout on huge files
- Use
-pto narrow to a page/section range. - Split processing across page ranges and concatenate results.
- If a specific PDF-embedded HWPX is slow, consider oma-pdf for the PDF portion.
8. kordoc crash / stack trace
1. Upgrade: bunx kordoc@latest (bust the bunx cache if needed) 2. Capture a minimal reproducible fixture 3. File an issue: https://github.com/chrisryugj/kordoc/issues 4. Meanwhile, if only a subset of the document triggers the crash, use -p to skip it
9. Reproducibility vs freshness tradeoff
bunx kordoc@latestalways pulls latest. Fixes land fast, but outputs may drift. A barebunx kordocreuses the bunx cache and can be arbitrarily old; always include@latestor a pinned version.- For long-running projects, pin a version: edit
config/hwp-config.yaml:
version:
channel: pinned
pinned: "2.4.0"Then invoke via bunx kordoc@2.4.0 ... in commands.
kordoc limitations (upstream-owned)
- Inline password entry for encrypted HWP: not yet supported.
- OCR of scanned HWPs: out of kordoc's scope; it does not perform OCR.
- Perfect fidelity of complex shapes / drawings: Markdown cannot represent drawing primitives; expect them to be dropped or replaced by placeholder markers.
When to route elsewhere
| Symptom | Route to |
|---|---|
Input is .pdf | oma-pdf |
Input is .xlsx / .docx | bunx kordoc@latest directly (skill not advertising) |
| Need OCR of scanned documents | Out of scope; use a dedicated OCR pipeline |
| Need to author / fill HWPX | bunx kordoc@latest fill ... directly (skill not advertising) |
{
"compilerOptions": {
"strict": true,
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["node"],
"noEmit": true
},
"include": ["*.ts", "*.d.ts"]
}
declare module "turndown-plugin-gfm" {
import type TurndownService from "turndown";
export function tables(turndownService: TurndownService): void;
export function gfm(turndownService: TurndownService): void;
export function highlightedCodeBlock(turndownService: TurndownService): void;
export function strikethrough(turndownService: TurndownService): void;
export function taskListItems(turndownService: TurndownService): void;
}