edit2docs
AI-agent-native document engine — DOCX · XLSX · PPTX. English-first, with first-class Korean support.
edit2docs generates complete Office documents from a one-line intent and
chat-edits existing files — Word reports, Excel workbooks, PowerPoint decks —
always producing natively editable OOXML (real paragraphs, real cells,
real charts — never screenshots of them). One engine, four surfaces: import
it, hand it to an agent, plug it into an MCP client, or run it as a service.
pip install edit2docs # library + agent tools + local MCP
pip install "edit2docs[server]" # + the hosted multi-tenant service
from edit2docs import generate_doc, edit_doc
generate_doc("Executive briefing on Q3 sales", output="deck.pptx")
r = edit_doc("deck.pptx", "Make slide 3's title more assertive")
print(r.reply) # the editor explains what it changed
Ecosystem
| Repo | What it is |
|---|---|
| edit2docs (this repo) | The engine: library · agent tools · MCP · hosted FastAPI service |
| edit2docs-web | Web studio for the hosted service — upload, generate, chat-edit with a live addressable preview, per-op edit highlighting, EN/KO UI. Next.js 15 / React 19 / Tailwind |
| ppt-master | Upstream project (MIT) the PPTX core is derived from — synced through v3.1 |
| edit2ppt | Sister project; the deck pipeline and hosted service originate there |
A production deployment of engine + studio runs behind
hr_blog2.0's compose stack — its
edit2docs-server/ and edit2docs-web/ service dirs are a working reference
for wiring both containers behind nginx.
The seven verbs
Every surface exposes the same seven format-dispatched verbs — the file
extension picks the engine. Deterministic verbs need no API key; generative
ones are BYOK (api_key=... or ANTHROPIC_API_KEY).
| verb | what it does | LLM? |
|---|---|---|
generate_doc |
intent (+ optional sources / PPTX template) → complete document | ✳ |
edit_doc |
one natural-language edit turn; untouched content stays byte-identical | ✳ |
preview_doc |
.pptx → per-slide SVG · .docx/.xlsx → markdown | — |
render_doc |
any format → page PNGs / PDF / SVGs — no LibreOffice, no subprocess | — |
analyze_doc |
structure outline with the exact addresses set_doc_text / edit_chart need (incl. a charts list) |
— |
set_doc_text |
deterministic targeted edits — lossless (charts / images / styles / formulas survive) | — |
edit_chart |
deterministically edit a native chart's data or title — rewrites the chart and its embedded workbook | — |
Lossless editing. The deterministic edit verbs run on
contextifier's raw OOXML layer:
edits are surgical and untouched package parts stay byte-identical, so a
chart, pivot table, sparkline, inline image or cached formula is never
collateral damage of a text edit. The PPTX chat editor (edit_doc on a
deck) likewise preserves native charts and tables on slides it
regenerates, instead of flattening them into pictures.
1 · Python library
Generate — the output extension picks the engine
from edit2docs import generate_doc
generate_doc("Q3 performance report", output="report.docx")
generate_doc("Quarterly sales summary", output="sales.xlsx", sources=["raw.pdf"])
generate_doc("Executive briefing on Q3 sales", output="deck.pptx",
template="brand.pptx", # optional user PPTX template
deck_mode="template_restyle", # "new" | "template_restyle" | "template_extend"
pages=(8, 12)) # target page range (pptx)
generate_doc("3분기 실적 보고서", output="report.docx", lang="ko-KR") # any language, same call
Full signature: generate_doc(intent, *, output, api_key=None, sources=None, template=None, deck_mode="new", pages=(8, 12), lang="en-US", model=...) →
GenerateResult(path, page_count, design_spec, warnings).
sources accepts PDF / DOCX / DOC / PPTX / XLSX / HTML / EPUB / IPYNB paths —
each is converted to markdown and given to the writer as reference material.
Edit — one chat turn, everything else byte-identical
from edit2docs import edit_doc
r = edit_doc("report.docx", "Add a 'deployment complete' item to the progress section")
print(r.reply) # what the editor did, in your language
print(r.operations) # the applied ops, e.g. [{"action": "insert_after", ...}]
r = edit_doc("deck.pptx", "이 문서 내용을 반영해서 3번 슬라이드를 고쳐줘",
sources=["notes.pdf"], lang="ko-KR",
chat_history=[{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."}])
The planner sees a numbered outline of your document, plans the minimal operations, and the deterministic engine applies them — untouched paragraphs, cells and slides survive byte-for-byte. If planning fails, the reply says so honestly instead of pretending (no silent no-ops).
Inspect & edit deterministically (no LLM, no key)
from edit2docs import analyze_doc, set_doc_text, preview_doc, render_doc
info = analyze_doc("report.docx")
# {"format": "docx", "outline": [
# {"para": 0, "style": "Heading 1", "text": "Q3 Report"},
# {"table": 0, "row": 1, "col": 2, "text": "142"}, ...]} ← addresses
set_doc_text("report.docx", [
{"para": 0, "new_text": "Q3 Final Report"}, # docx: replace / insert_after / delete
])
set_doc_text("sales.xlsx", [
{"sheet": "Sales", "cell": "B3", "value": 142}, # xlsx: set_cell / append_rows / add_sheet
])
set_doc_text("deck.pptx", [
{"slide": 0, "shape_id": 2, "para": 0, "new_text": "New title"}, # pptx
])
# Edit a native chart's data or title — the chart stays a real, editable
# PowerPoint/Excel chart (its embedded workbook is rewritten too).
from edit2docs import edit_chart, list_charts
list_charts("deck.pptx") # [{"chart": 0, "kind": "bar", "title": ..., "series": [...]}]
edit_chart("deck.pptx", [
{"chart": 0, "title": "Q3 Sales"},
{"chart": 0, "categories": ["Q1", "Q2", "Q3"],
"series": [{"name": "Sales", "values": [120, 135, 150]}]},
])
preview_doc("deck.pptx", out_dir="previews") # per-slide self-contained SVGs
render_doc("report.docx", to="pdf") # page PNGs / a PDF / raw SVGs
render_doc("deck.pptx", to="png", dpi=200) # resvg raster — no LibreOffice
Async variants exist for the generative verbs: async_generate_doc,
async_edit_doc (use inside an existing event loop).
2 · Agent tools (function calling)
The same seven verbs as Anthropic tool-use schemas plus a dispatcher:
import anthropic
from edit2docs.agent_tools import ANTHROPIC_TOOLS, run_tool
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
tools=ANTHROPIC_TOOLS,
messages=[{"role": "user", "content": "Fix the title of slide 3 in deck.pptx"}],
)
for block in msg.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input) # sync; run_tool_async also exists
3 · Local MCP server (zero infra)
pip install edit2docs ships an edit2docs-mcp stdio server exposing all six
verbs over local files:
// Claude Desktop / Claude Code / Cursor
{
"mcpServers": {
"edit2docs": {
"command": "edit2docs-mcp",
"env": { "ANTHROPIC_API_KEY": "sk-ant-..." } // only generative tools need it
}
}
}
Then just talk: "generate a 10-page deck about our roadmap as ~/decks/roadmap.pptx, then render it to PDF".
4 · Hosted service
pip install "edit2docs[server]"
edit2docs serve # FastAPI on :8000 — standalone mode
Standalone mode needs zero external infra: SQLite + local-fs storage + an inline job queue, auto-bootstrapped on first start. Add Postgres / Redis / S3 via env vars when you outgrow it.
| REST endpoint | purpose |
|---|---|
POST /v1/assets · GET /v1/assets/{id} |
upload / fetch documents (200 MB cap) |
POST /v1/jobs/generate-deck · /v1/jobs/edit-deck |
queue generative jobs (any of the 3 formats) |
GET /v1/jobs/{id} · GET /v1/jobs/{id}/events |
job status · SSE progress stream (stages + per-operation live-edit events with addressable targets) |
POST /v1/preview |
pptx → per-slide SVGs · docx/xlsx → addressable HTML |
POST /v1/text-edits |
deterministic targeted edits |
GET /health |
liveness + mode report |
/mcp · /mcp-sse |
the same verbs over MCP (Streamable HTTP / SSE) |
Anthropic keys are BYOK per request (X-Anthropic-API-Key header) — never
persisted. Errors come back bilingual: message follows the request's
Accept-Language, with message_en / message_ko always present.
Key env vars (prefix EDIT2DOCS_):
| var | default | notes |
|---|---|---|
EDIT2DOCS_DEFAULT_LANG |
en-US |
set ko-KR to make a deployment Korean-by-default |
EDIT2DOCS_DATA_DIR |
/data/edit2docs |
standalone SQLite + file storage root |
EDIT2DOCS_DATABASE_URL |
(sqlite) | e.g. postgresql+asyncpg://... |
EDIT2DOCS_REDIS_URL |
(inline queue) | enables the arq worker queue |
EDIT2DOCS_S3_* |
(local fs) | endpoint / bucket / keys for S3-compatible storage |
EDIT2DOCS_AUTH_DEV_API_KEY |
(anonymous) | single bearer token for small deployments |
EDIT2DOCS_MAX_UPLOAD_SIZE_BYTES |
200 MB | match your reverse proxy |
EDIT2DOCS_MODEL_{PLANNER,WRITER,STRATEGIST,EXECUTOR} |
(request model) | per-role model override — run cheap planner/writer turns on a smaller model without touching callers |
EDIT2DOCS_STRATEGIST_SOURCE_CHAR_CAP |
60000 | cap per source doc fed to the deck strategist (0 = uncapped) |
The web studio
edit2docs-web is the official
frontend for this service: drag-and-drop upload, generation with staged SSE
progress, and a co-editing studio where the chat edits your document while the
canvas highlights the exact paragraph / cell / slide each operation
touches (the preview HTML carries data-e2d-* addresses; PPTX slides carry
data-e2p-*). English-first UI with a KO/EN toggle. Point it at the engine
with EDIT2DOCS_SERVER_INTERNAL_URL + EDIT2DOCS_SERVER_API_KEY.
How each format works
- DOCX — the writer LLM emits a constrained markdown document; a
deterministic renderer (python-docx) turns it into styled Word. Edits are
paragraph-addressed operations (
replace/insert_after/delete, table cells bytable/row/col). The hosted preview is a native, addressable HTML rendering — every paragraph carriesdata-e2d-para, every celldata-e2d-cell, the same addresses the outline and the live-edit op stream use — with real merged cells, alignment, colors, images, footnotes and page breaks. - XLSX — the designer LLM emits a YAML sheet spec (sheets / headers /
rows / number formats, formulas allowed); openpyxl renders styled sheets.
Edits are
set_cell/append_rows/add_sheetwith staleness guards. The hosted preview is a spreadsheet-style grid (column letters, row numbers, merged ranges, cached formula results), every cell stampeddata-e2d-cell="B3"— exactly the addressset_celltakes. - PPTX — the full multi-stage pipeline: strategist → per-page SVG → native DrawingML, user-PPTX templates (restyle / extend), chat-edit with slide recompose, per-paragraph text edits incl. table cells, optional Edge-TTS narration. Exported text is paragraph-merged (edits as real paragraphs, not per-line boxes).
Native charts & tables (PPTX)
SVG groups marked data-pptx-native="chart|table" export as real,
editable PowerPoint objects — a chart XML part with an embedded Excel
workbook (double-click it in PowerPoint and edit the data), or a native
<a:tbl> table — instead of drawn shapes:
<g id="sales_chart" data-pptx-native="chart">
<metadata data-pptx-native="chart">
{ "name": "sales_chart",
"x": 125, "y": 141, "width": 1000, "height": 440,
"type": "bar",
"categories": ["Q1", "Q2", "Q3"],
"series": [{ "name": "Sales", "values": [120, 135, 150] }] }
</metadata>
<!-- fallback shapes, used when native export is off -->
</g>
Opt in with ExportRequest(native_objects=True) (tools/export.py).
Supported chart types: bar / column / line / area / pie / doughnut /
of-pie / radar (classic), scatter / bubble (XY), and box-whisker / funnel /
histogram / pareto / sunburst / treemap / waterfall (chartEx). The quality
checker validates marker payloads before export.
Every LLM planner follows the same contract: fenced reply + edit_plan
blocks, one retry with a format reminder, and an honest reply (instead of a
silent no-op) when planning fails.
Languages
English is the default (lang="en-US"); Korean is a first-class citizen,
not an afterthought — Hangul-aware text widths, per-run OOXML lang
attributes detected from the actual script, Korean font stacks
(Pretendard / Malgun), a complete Korean message catalog, localized chat
replies and live-edit labels. Flip any call with lang="ko-KR", any request
with Accept-Language: ko-KR, or a whole deployment with
EDIT2DOCS_DEFAULT_LANG=ko-KR. zh-CN / zh-TW / ja-JP get the same script
detection and font-stack treatment.
Development
git clone https://github.com/CocoRoF/edit2docs && cd edit2docs
uv venv .venv && uv pip install -e ".[server,dev]"
.venv/bin/python -m pytest tests/ # 769 tests
.venv/bin/python -m ruff check src/edit2docs --exclude src/edit2docs/core
Version history
| version | highlights |
|---|---|
| v0.15.1 | ships the full arrange_doc implementation (0.15.0 tagged only the version bump — the verb, engine and wiring were left out; use 0.15.1) |
| v0.15.0 | arrange_doc — deterministic STRUCTURAL edits: duplicate / move / delete whole slides (.pptx) and sheets (.xlsx), rename sheets. No key, byte-preserving (a copy adds parts; untouched slides/sheets stay byte-identical). On contextifier 0.5.0's new raw primitives. 9th agent verb + doc_guide('arrange') (incomplete on PyPI — superseded by 0.15.1) |
| v0.14.1 | dependency fix — cap mcp < 2.0 (the 2.0 major dropped mcp.server.fastmcp; an uncapped floor crash-looped the hosted server) |
| v0.10–0.14 | build_doc (deterministic generation, no LLM) · read_doc_xml/set_doc_xml (direct OOXML editing, create/delete parts) · agent surface consolidated to 8 verbs · hierarchical doc_guide (progressive-disclosure skill map) · themed decks (deterministic design in one build_doc call) |
| v0.9.0 | token optimization — prompt-cache restructuring (edit retries read the cached prefix ~10× cheaper; per-page executor spec_lock cached once, not re-sent), fan-out cache warm-up, unbounded-input caps (strategist sources, edit outline windowing), retry-severity tiering, per-role model tiering, streaming, honest cache accounting + per-stage cost |
| v0.8.0 | lossless editing on contextifier's raw OOXML layer — set_doc_text/edit_doc no longer destroy charts, images, sparklines, styles or cached formulas; PPTX chat-edit preserves native charts/tables; new edit_chart verb (data + title, embedded workbook synced) |
| v0.7.0 | upstream sync (ppt-master v2.7 → v3.1, 3 waves): native chart/table export, paragraph-merge editability, PowerPoint repair-prompt fixes, checker hardening · English-first flip with full Korean support |
| v0.5–0.6 | render_doc — native page rendering to PNG/PDF/SVG for all 3 formats (resvg + PyMuPDF, no LibreOffice) |
| v0.4.0 | addressable native previews (data-e2d-*) — preview, outline and editor share one address space |
| v0.3.0 | live edit streaming — per-operation SSE events with addressable targets |
| v0.2.x | multi-format hosted API + full-format hardening |
| v0.1.0 | multi-format engine: the core verbs across DOCX/XLSX/PPTX |
License
Apache-2.0. The PPTX core under src/edit2docs/core/ is derived
from ppt-master (MIT, © Hugo He) via
edit2ppt and kept in sync (currently
through upstream v3.1); the original MIT terms for those portions are
preserved in NOTICE and
LICENSE.ppt-master.MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file edit2docs-0.15.1.tar.gz.
File metadata
- Download URL: edit2docs-0.15.1.tar.gz
- Upload date:
- Size: 5.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e12b2bdc1af99ad34978a9f4fd8b250082f68f8da14858a369c4531f6c8f8aa6
|
|
| MD5 |
851a354892ceef3d506c71a372c9319a
|
|
| BLAKE2b-256 |
12fe01b9587be35de8231a2c5cbad0d071da6e2f138804502294b96b59292302
|
Provenance
The following attestation bundles were made for edit2docs-0.15.1.tar.gz:
Publisher:
publish.yml on CocoRoF/edit2docs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edit2docs-0.15.1.tar.gz -
Subject digest:
e12b2bdc1af99ad34978a9f4fd8b250082f68f8da14858a369c4531f6c8f8aa6 - Sigstore transparency entry: 2353496386
- Sigstore integration time:
-
Permalink:
CocoRoF/edit2docs@413b87db41506e86bf7eff1ba84df18bfbbad78a -
Branch / Tag:
refs/tags/v0.15.1 - Owner: https://github.com/CocoRoF
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@413b87db41506e86bf7eff1ba84df18bfbbad78a -
Trigger Event:
release
-
Statement type:
File details
Details for the file edit2docs-0.15.1-py3-none-any.whl.
File metadata
- Download URL: edit2docs-0.15.1-py3-none-any.whl
- Upload date:
- Size: 10.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a7dc2c385de660a616247c7395250d7e3849b934cf72cc9797ca31c5c29046a
|
|
| MD5 |
416ed713bc441c0ef7c401ae226cc4a3
|
|
| BLAKE2b-256 |
3618699e84cd0c925e86657103c8e17901f67a1fc7378a8fc4b2a4850c4f65b2
|
Provenance
The following attestation bundles were made for edit2docs-0.15.1-py3-none-any.whl:
Publisher:
publish.yml on CocoRoF/edit2docs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
edit2docs-0.15.1-py3-none-any.whl -
Subject digest:
7a7dc2c385de660a616247c7395250d7e3849b934cf72cc9797ca31c5c29046a - Sigstore transparency entry: 2353496505
- Sigstore integration time:
-
Permalink:
CocoRoF/edit2docs@413b87db41506e86bf7eff1ba84df18bfbbad78a -
Branch / Tag:
refs/tags/v0.15.1 - Owner: https://github.com/CocoRoF
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@413b87db41506e86bf7eff1ba84df18bfbbad78a -
Trigger Event:
release
-
Statement type: