Mmcp.market

office-transform skill

by CherryHQ·CherryHQ/cherry-studio·52k stars·AGPL-3.0

Derive new Office files from structural selections without touching the original. Use when the user points at part of a spreadsheet, Word document, PDF, or PowerPoint deck (a worksheet range, paragraph, page, slide, shape, or a pasted selection-ref block) and wants it extracted, converted, or edited — the result is always a NEW file; the source file is never modified. Covers xlsx range extraction to csv/markdown/xlsx, docx paragraph extraction and text replacement, pdf page extraction, pptx slide/shape/table-cell extraction and editing, and targeted xlsx cell edits.

A100/100content scan

Is the office-transform skill safe?

Clean: nothing in its files matched our rules. We read 12 files in the folder on 2026-09-28.

No findings.

Install the office-transform skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/CherryHQ/cherry-studio.git /tmp/cherry-studio
mkdir -p ~/.claude/skills
cp -r /tmp/cherry-studio/resources/skills/office-transform ~/.claude/skills/office-transform
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Office Transform

Turn a structural selection of an Office/PDF document into a new derived file.

Two invariants hold for every operation:

write to the source path or overwrite an existing file. Never work around this with ad-hoc shell edits to the original.

  1. The source file is read-only. Every result is a new file; both scripts refuse to

worksheet + A1 range, body-level paragraph ordinal, one-based page number — never screen or DOM positions.

  1. Anchors are structural. Selections address the document's own coordinates —

When to use which tool

mcpcherry-toolsto_markdown instead (see the cherry-tool-guide skill); it is lossy but built for reading.

  • Only need to read a document to summarize or answer questions → use

copy → this skill.

  • The user wants a file out — an extracted range, a converted fragment, an edited

Input: selection references

Chat surfaces may hand you a fenced selection-ref block:

{"path": "/abs/report.xlsx", "anchor": {"format": "xlsx", "sheet": "Sheet1", "range": "A1:C10"}, "excerpt": "…", "fileStamp": {"size": 1024, "mtimeMs": 1700000000000}}

The anchor object is exactly what the scripts take via --anchor.

Freshness check. fileStamp.mtimeMs is milliseconds since the Unix epoch, floored to whole milliseconds. Do not compare it to stat's default output: stat -f %m (macOS) and stat -c %Y (GNU) give whole seconds, so multiplying by 1000 loses the sub-second part and never matches. Read whole milliseconds the way the app wrote them:

uv run python -c "import os,sys;st=os.stat(sys.argv[1]);print(st.st_size, st.st_mtime_ns//1_000_000)" '/abs/report.xlsx'

Treat the file as changed when the size differs, or when the mtimes differ by more than 2 ms. The tolerance is small on purpose: both sides floor the same nanosecond timestamp to milliseconds, so they agree exactly, and the couple of milliseconds only covers the rounding of a double that can no longer represent current epoch milliseconds exactly. A wider window would hide real edits — a same-size change made within it would read as unchanged, which is why the anchor check below is not optional. On a change, tell the user the file changed since they selected and ask them to re-select — never silently re-anchor.

Anchor check. Before a patch-copy edit, also verify the anchor still points where the user thinks: extract the anchored region and compare its text with the reference's excerpt, normalizing both sides the same way (NFC, collapse each whitespace run to one space, trim). The two are not equal and are not meant to be — the excerpt is what the user selected, while the extract is the whole region an edit would replace. A selection sitting inside one region makes the excerpt the shorter side; a selection running past that region anchors to where it starts, which makes it the longer one. So the test is containment, whichever way round fits: the excerpt appears inside the extract, or the extract's tail is where the excerpt begins. Only when neither holds has the anchor stopped pointing at the selection — then stop and tell the user, never edit at a mismatched anchor and never go searching for a "close enough" location. Two cases need care because the two sides are not directly comparable as-is:

writes csv or md. Compare cell values, not the raw file — read the csv with a csv reader and join the fields with single spaces before normalizing, so , and | never count as a difference. That join drops the cell boundaries on both sides, so a match proves the text is unchanged, not the grid — the same words re-split across cells still reads as a match. The freshness check above is what catches that; never skip it on a passing anchor check. Number formats are the second asymmetry: the excerpt holds what Excel displays, because that is what the user pointed at (1,234.50, 45.67%), while extraction writes the stored value (1234.5, 0.4567). Neither side is wrong, so a formatted cell never matches literally. Compare those cells by value, and leave them out of the joined strings so the containment test runs on the text cells alone. Dates, times and durations are the same asymmetry seen from the other side: extraction writes one fixed shape (2024-01-03, 2024-01-03 15:04:05, 26:30:00) rather than the cell's format, so treat a cell that differs only in date or time presentation as a format difference too. A difference that is only the number format is not a moved anchor, and stopping on one sends the user back to re-select a selection that never moved.

  • xlsx: the excerpt is tab-separated cells and newline-separated rows, while extraction

it rewrites the whole paragraph. Extract the paragraph without charRange as well, and read "Edit docx" below before writing.

  • docx with charRange: the slice is only part of what patch-copy compares and replaces —

Users may also describe the region in words ("sheet 2, columns A through C"); build the anchor JSON yourself, confirming the worksheet name or paragraph if ambiguous.

Anchor shapes:

Operations

Scripts live in this skill's scripts/ directory; resolve paths relative to this skill folder. The library-edit recipes routed to below live in references/ beside them. Python dependencies are per-format and provided at invocation time via uv run --with (the bundled-shell idiom — do not pip install globally).

Which route an edit takes follows from the library that can write the format: openpyxl drops charts and drawings on a round-trip, which is why xlsx edits go through patch-copy, while python-pptx keeps XML it does not understand, which is why pptx edits go through the library.

Extract — pull the anchored region into a new file

Always single-quote paths — real documents have spaces in their names (Q1 report.xlsx). If a path itself contains a single quote, close and reopen the quoting around it: '/abs/Bob'\''s deck.pptx'.

uv run --with openpyxl python scripts/office_extract.py \
  --file '/abs/report.xlsx' \
  --anchor '{"format":"xlsx","sheet":"Sheet1","range":"A1:C10"}' \
  --out '/abs/report-q1-range.csv'

The output format is inferred from --out's extension:

The docx pin is not optional. Patch-copy's expectText gate compares a paragraph read with python-docx against the same paragraph read by the script's own paragraph_text, which reproduces python-docx's Paragraph.text element for element. That equivalence was checked against 1.x; a release that changes what .text spells would make the gate refuse paragraphs nobody edited. Quote the specifier — > and < are redirects to a shell.

xlsx extraction reads computed values (data_only), so formula cells yield their last saved result. docx extraction to docx carries text only, not run styling.

Patch-copy — derive an edited copy, standard library only

uv run python scripts/office_patch_copy.py \
  --file '/abs/report.xlsx' \
  --edits '{"format":"xlsx","sheet":"Sheet1","cells":{"B2":42,"C3":"hello"}}' \
  --out '/abs/report-updated.xlsx'

OOXML packages are ZIPs of XML parts. Patch-copy copies every part byte-for-byte and re-serializes only what an edit reaches — the target worksheet or word/document.xml, plus the workbook bookkeeping noted below for xlsx — so styles, charts, images, and macros in untouched parts survive exactly. Edit shapes:

strings, and booleans; an existing formula in an edited cell is replaced by the value. Patch-copy does not recalculate, so a formula reading an edited cell keeps the value it last cached; every write sets fullCalcOnLoad in xl/workbook.xml so Excel recomputes those on open. Replacing a formula also drops xl/calcChain.xml (a recalculation cache Excel rebuilds), along with the content-type and relationship entries that would otherwise point at a part no longer there; keeping a chain entry for a cell that no longer has a formula makes Excel report the derived file as damaged. Cells in a shared, array, or data-table formula group are refused — the expression lives in one member and the others only reference it, so overwriting a member would strip the formula from cells you never named. Rewrite such a range with openpyxl. Coordinates outside the worksheet grid (past XFD or row 1048576) are refused too.

  • {"format":"xlsx","sheet":"S","cells":{"B2":42,"C3":"text","D4":true}} — numbers,

the paragraph keeps its paragraph style and the first run's character style; extra run-level styling within that one paragraph is flattened into the new text. text must be the complete new paragraph. The whole body paragraph is replaced, and charRange does not narrow that — feeding back a charRange slice as text silently discards the rest of the sentence. The output is exactly w:p > [w:pPr] + w:r > [w:rPr] + w:t, so a paragraph holding anything that shape cannot carry is refused, not silently stripped. That covers bookmarks, comment anchors and fields (their start/end can pair across paragraphs, and rewriting one half unbalances the document), images, embedded objects, footnote/endnote references, hyperlinks, tracked changes and moves, content controls, equations — and anything else not on the short allow-list, including elements from namespaces that did not exist when this was written. A dropped w:del would even accept a pending deletion on the user's behalf. A page or column break () is refused for a quieter reason: it carries no characters, so the extract reads the text on either side of it as one string and the anchor check cannot see that the rewrite would delete it. A bare line break still passes. To edit such a paragraph, see "Edit docx" below — do not reach for Paragraph.text, which destroys exactly the same content, only silently. paraId (optional) is resolved before the ordinal; a disagreement between the two is an error, never a silent pick. expectText (optional but strongly recommended) is a hard gate: the target paragraph's current text must match it after whitespace normalization or the edit is refused. Take its value from an extract of the same paragraph taken without charRange — the gate compares the whole paragraph, and a selection-ref excerpt is truncated at 2000 chars and may span more than the edit target.

  • {"format":"docx","replacements":[{"paragraph":3,"text":"new text","paraId":"502E8D33","expectText":"old text"}]} —

other than tab, newline and carriage return are refused. Text extracted from a deck can carry them (python-pptx maps a soft line break to \x0B), so strip them before feeding extracted text back in as an edit value. A docx paragraph refuses tab, newline and carriage return as well: WordprocessingML spells those and , and the single w:t this rewrite emits cannot carry them. Extracting a paragraph that holds them gives you \t / \n, so editing that string and writing it back would delete the elements while reading identically — split the content across separate body paragraphs, or see "Edit docx" below.

  • Any text written into a cell or paragraph must be storable in XML: control characters

Text comparisons on both sides of this skill use one normalization rule, identical to the renderer's normalizeSelectionText: NFC-normalize, collapse every whitespace run to a single space, trim the ends.

Generate — write ad-hoc library code for new documents

Generation (a fresh deck, workbook, or document — from scratch or from data you just extracted) has no source file to protect, so there is no fixed script: write a short Python program against the matching library (python-pptx, openpyxl, python-docx) and run it via uv run --with python. The two skill invariants still apply: write to a path nothing occupies yet — open it with "xb" rather than handing the library a name, since every one of these save() methods overwrites — and verify the output by reopening it before reporting success.

Edit docx — edit runs, never Paragraph.text

When patch-copy refuses a paragraph, python-docx can still edit it — but only run by run. Never assign paragraph.text = "...": it inflicts exactly the loss patch-copy refused, minus the refusal. save(path) overwrites, so open the destination with "xb".

Read references/docx-edit.md before writing any python-docx edit.

Edit pptx — use python-pptx, saving to a new path

pptx edits do not go through officepatchcopy.py. Never assign .text at paragraph or shape level: it rebuilds that subtree as one unformatted run. Edit runs, and open the destination with "xb".

Read references/pptx-edit.md before writing any python-pptx edit.

Output conventions

report.xlsx → report-updated.xlsx, report-q1-range.csv, spec-p3.txt.

  • Name derived files after the source with an operation suffix:

written path on success — report it to the user.

  • Write into the session workspace (or where the user asked). Both scripts print the

resolved against whatever working directory the shell happens to be in.

  • --file and --out must both be absolute; a relative path is refused rather than

More skills from CherryHQ/cherry-studio

  • Acherry-assistant-guide从当前安装包查询 Cherry Studio 产品信息并排查运行问题。当用户询问功能、路由、快捷键、Provider、语言、Agent、频道、定时任务、Code CLI、当前版本,或报告运行错误、连接失败、配置异常并需要诊断时触发。
  • Acherry-browserInteract with the user's visible Agent browser in Cherry Studio. Use for page navigation, authenticated websites, screenshots, forms, clicks, and browser debugging. Check live browser tools first; browser control requires the Browser setting and an available Agent pane.
  • Acherry-electron-devDevelop, fix, and profile Cherry Studio in a tracked Electron instance. Use for everyday implementation, UI and interaction work, bug fixing, runtime debugging, DevTools inspection, lag or jank investigation, CPU and memory monitoring, leak checks, and startup-performance analysis; reuse a verified workspace instance across instructions and launch or replace one only when required.
  • Acherry-pr-testTest Cherry Studio PRs by resolving and checking out a PR, statically inspecting its changes, running interactive UI tests against a safely tracked Electron instance through CDP, producing a structured report, cleaning up only the owned test instance, and restoring the original branch.
  • Acherry-regression-testRun Cherry Studio critical-path system regression tasks through the repository-owned Playwright E2E workflow. Use for full regression, release acceptance, development-branch system validation, or a named cherry-regression-test task on GitHub-hosted macOS and Windows runners.
  • Acherry-skill-marketplace当用户明确要求搜索、安装、查看、卸载或创建 Skill,或内置 Skill / 工具出现能力缺口、无法完成当前任务时触发。通过 `mcp__skills__search_skills` 搜索并用 `mcp__skills__install_skill` 安装;已安装 Skill 的查看和删除通过产品清单导航到 Skills UI;没有合适结果时调用内置 `skill-creator` 创建并验证自定义 Skill,再继续原任务。普通任务仍先尝试内置能力。
  • Acherry-studio-feedbackUse when Cherry Studio 用户希望报告、提交或整理 BUG、UI/UX 问题或功能建议,但未明确要求创建 GitHub Issue。
  • Acherry-tool-guideCherry Studio first-party tool and bundled-shell routing for general agents. For straightforward local work in shell-capable sessions, run JS/TS with `bun <file>` and one-off JS tools with `bun x`; run Python with `uv run [--with <pkg>] python` and one-off Python CLIs with `uvx`; search with `rg`. Load this guide before changing project dependencies, deciding whether a tool should be ephemeral or reusable, reading or converting local Office/PDF files, coordinating or delegating across Agent Sessions, or using Cherry-owned web/browser, knowledge, persistent memory, schedules/notifications, IM channels, image generation, artifact reporting, managed CLI, skill, or MCP-server-registration capabilities—even if the user names no tool. Consult it before shell/file workarounds; live tool schemas are authoritative.
  • Aclaude-automation-recommenderAnalyze a codebase and recommend Claude Code automations (hooks, subagents, skills, plugins, MCP servers). Use when user asks for automation recommendations, wants to optimize their Claude Code setup, mentions improving Claude Code workflows, asks how to first set up Claude Code for a project, or wants to know what Claude Code features they should use.
  • Acode-mate-antigravityRuns Antigravity CLI headlessly for repository analysis and coding tasks. Use when the user asks to delegate work to Antigravity CLI or compare its result with another coding agent.
  • Acode-mate-claude-codeRuns Claude Code non-interactively for code analysis and implementation tasks. Use when the user asks to delegate repository work to Claude Code or compare its result with another coding agent.
  • Acode-mate-codexRuns Codex CLI non-interactively for code analysis and implementation tasks. Use when the user asks to delegate repository work to Codex or obtain a second coding-agent result.

All agent skills → · MCP servers