
The Story of Redesigning Claude Code Skills — Integrating xlsx/pptx/docx into ms-office-suite
This page has been translated by machine translation. View original
Introduction
I created an ms-office-suite skill using Claude Code's custom skill feature, which bundles processing for Excel, PowerPoint, and Word files.
Originally it was split into 3 separate skills — xlsx, pptx, and docx — but while reviewing the design, the idea came up that "merging everything into one skill might be cleaner," so I reorganized the structure. This article introduces the design decisions behind that and the final structure.
Background: The Original Structure
At first, Office file operations were built as 3 independent skills:
.claude/skills/
├── xlsx/
│ ├── SKILL.md
│ └── scripts/
├── pptx/
│ ├── SKILL.md
│ └── scripts/
├── docx/
│ ├── SKILL.md
│ └── scripts/
└── shared/ ← Utilities shared by the 3 skills
├── pack.py
├── unpack.py
├── validate.py
└── libreoffice.py
This worked well enough, but some concerns surfaced while using it.
The Awkwardness of shared/
The problem with the shared/ folder is that it isn't a skill at all. Since there's no SKILL.md, it can't be called as a /shared command — it just floats inside .claude/skills/. The SKILL.md files of the 3 skills each contained a path variable like SHARED_DIR = .claude/skills/shared, creating an implicit dependency.
Revisiting the Design
The first question was whether to merge the 3 skills into one.
Initially I considered keeping them separate due to concerns about "context window cost," but thinking it through, each skill's SKILL.md was around 50 lines, making all three together about 150 lines. Merging them wouldn't make much of a difference.
The real issue was routing. For tasks like "read an xlsx" or "convert a pptx," having all 3 skill descriptions loaded every time is inefficient.
The structure I arrived at is this:

.claude/skills/ms-office-suite/
├── SKILL.md ← Thin router (~30 lines)
├── xlsx.md ← Detailed instructions for .xlsx (read when needed)
├── pptx.md ← Detailed instructions for .pptx (read when needed)
├── docx.md ← Detailed instructions for .docx (read when needed)
└── scripts/
├── xlsx/
│ ├── read_xlsx.py
│ └── write_xlsx.py
├── pptx/
│ ├── read_pptx.py
│ └── write_pptx.py
├── docx/
│ ├── read_docx.py
│ └── write_docx.py
└── shared/
├── pack.py
├── unpack.py
├── validate.py
└── libreoffice.py
Key Point: Making SKILL.md a Thin Router
SKILL.md contains only the routing logic per file extension:
---
name: ms-office-suite
description: Read, create, and edit Microsoft Office files (.xlsx, .pptx, .docx).
---
# Microsoft Office Suite Skill
Route by file extension:
| Extension | Instructions | Scripts | Package |
|-----------|-------------|---------|---------|
| `.xlsx` | Read `SKILLS_DIR/xlsx.md` | `scripts/xlsx/` | openpyxl |
| `.pptx` | Read `SKILLS_DIR/pptx.md` | `scripts/pptx/` | python-pptx |
| `.docx` | Read `SKILLS_DIR/docx.md` | `scripts/docx/` | python-docx |
**After identifying the file type, read the corresponding .md file.**
This way, when handling a .xlsx file, only SKILL.md (~30 lines) + xlsx.md (~40 lines) are loaded. The details for pptx and docx are never read.
Key Point: Separating Format-Specific .md Files
Here's what xlsx.md looks like (excerpt):
# Excel (.xlsx)
## Available Operations
### Read / Extract
```bash
uv run --with openpyxl SKILLS_DIR/scripts/xlsx/read_xlsx.py "$ARGUMENTS"
Edit (Unpack → Edit → Repack)
- Unpack:
uv run SKILLS_DIR/scripts/shared/unpack.py <file.xlsx> - Edit: Modify XML files directly
- Repack:
uv run SKILLS_DIR/scripts/shared/pack.py <dir> <output.xlsx> - Validate:
uv run SKILLS_DIR/scripts/shared/validate.py <output.xlsx>
Format-specific information (libraries to use, script paths, notes) is consolidated here.
### Key Point: Moving `shared/` to `scripts/shared/`
By moving `shared/` to `scripts/shared/`, the structure now communicates the intent — "this is a shared script library, not a skill" — without needing any explanation.
## Contents of the Shared Scripts
There are 4 utilities in `scripts/shared/`:
| Script | Role |
|-----------|------|
| `unpack.py` | Extracts an Office file (ZIP) into an XML tree |
| `pack.py` | Repacks edited XML back into an Office file (with auto-repair) |
| `validate.py` | Checks ZIP integrity and XML well-formedness |
| `libreoffice.py` | Format conversion and formula recalculation via headless LibreOffice |
Since Office files are ZIP + XML, unpacking and editing the XML directly allows finer control than libraries like python-pptx. pack.py also includes auto-repair for common XML breakage such as incorrect durableId values.
## How It Works in Practice
For example, to read an xlsx file:
```bash
uv run --with openpyxl .claude/skills/ms-office-suite/scripts/xlsx/read_xlsx.py "requirements.xlsx" --summary
If there are multiple sheets, you can specify one with --sheet:
uv run --with openpyxl .claude/skills/ms-office-suite/scripts/xlsx/read_xlsx.py "requirements.xlsx" --sheet "User Requirements"
To extract text from a pptx:
uv run --with python-pptx .claude/skills/ms-office-suite/scripts/pptx/read_pptx.py "review-meeting.pptx"
Summary
Here's a summary of the key points from this design change:
- Benefits of merging: The skill list is cleaner, and the awkward
shared/entry is gone - Lazy-loading context: Making SKILL.md a thin router means format-specific instructions are only read when needed
- Structure communicates intent: The
scripts/shared/location makes it clear that "this is a shared script library, not a skill"
Whether to go with "one skill per feature" or "group related features into one skill" in Claude Code skill design depends on the use case. This time, merging was natural because there was a common domain of "Office file operations." On the other hand, forcing entirely independent tools into one skill would just complicate routing, so that judgment call is still necessary.
I hope this is useful for anyone building Claude Code skills that work with Office files.

