Office documents become agent-ready knowledge.
OAKit gives agents and automation systems a reliable way to read, understand, preserve, preview, and create supported PowerPoint content through a consistent structured model, with Excel and Word on the same product path. It owns the difficult OOXML work—ZIP packages, relationships, inheritance, units, media, malformed input, and producer differences—so agent workflows can operate on meaningful document data instead of raw XML.
Project status: pre-stable (
0.0.x). Implemented PowerPoint capabilities include bounded reading, producer-verified text-profileC3creation, byte-exactR0portable hand-offs, producer-verifiedR3plain-text and text transform editing, runtime-verified native shape/image/table/group/chartC2creation, native transform/image-crop/literal-chart-data plus table/nested-group plain-textR2editing, and Office-free SVG/PNG previews. Excel and Word remain product direction rather than completed APIs.
OAKit is exercised against a transient corpus of 30 complex templates selected from SlidesMania. The controlled gate downloads each source from its SlidesMania page, strict-parses and renders every slide without Office, imports and exports it through Google Slides, deletes the temporary presentation, then repeats strict parsing and rendering on the exported PPTX.
Run 32045412714 verified 30 decks, 733 slides, and 9,285 source elements with 100% minimum text and element retention. Attribution was preserved in every export and all 30 temporary Google presentations were deleted. The tracked evidence manifest contains only source-page links, hashes, and metrics—no SlidesMania templates or rendered reproductions, in accordance with the SlidesMania template license.
The remaining warnings are explicit and narrow: 6,733 output text runs may use a substitute when their authored font is unavailable, while 199 tables retain their structure and layout but lack complete cell typography metadata in the portable model. The same audit reports zero approximation warnings for shapes, fills, and media. This gate proves robust opening, semantic retention, producer compatibility, and Office-free rendering; it does not claim pixel-identical rendering across machines with different installed fonts.
- PowerPoint usage guide: detailed Node.js, browser, CLI, API entry-point, recursive extraction, portable JSON, editing, creation, rendering, write-verification, security, and agent workflow examples.
- Architecture: package boundaries, data flow, invariants, extension points, and reliability model.
- PowerPoint round-trip plan: fidelity profiles, supported edit scope, and progression toward broader native support.
- PowerPoint remaining-limit audit: implemented boundaries, native feature gaps, deliberate security policies, and the evidence required to close each row.
Office files are not single documents internally. They are ZIP packages made of interconnected XML parts, relationships, themes, layouts, media, charts, and vendor-specific extensions. That representation is a poor tool boundary for an AI agent.
OAKit turns those internals into bounded, deterministic application data that is easier to:
- summarize, classify, index, and search;
- inspect slide structure and extract semantic content;
- build document-aware tools and agent actions;
- validate generated changes before writing a file;
- run consistently in Node.js and modern browsers;
- process untrusted uploads with explicit diagnostics and resource limits.
OAKit is model- and framework-neutral. It does not require a particular LLM, agent runtime, tool-calling protocol, or vector database.
| Format | Read | Create | Edit | Preserve | Preview |
|---|---|---|---|---|---|
PowerPoint (.pptx) |
Yes | Text C3 + native shape/image/table/group/chart C2 | Text R3 + native transforms/crop/table/group text/chart data R2 | Byte-exact R0 | SVG/PNG |
Excel (.xlsx) |
No | No | No | No | No |
Word (.docx) |
No | No | No | No | No |
The runtime reports C2 after deterministic package construction, strict
reparse, semantic comparison, and Office-free rendering. The declared
pptx-create-text-v1 capability is certified at effective C3 by controlled
PowerPoint, LibreOffice, and Google Slides save/reopen evidence. Likewise, the
runtime's R2 verification is certified at effective R3 for the
pptx-roundtrip-text-v1 plain-text and text-transform operations. R0 means an
unchanged source package is restored byte for byte through runtime or portable
JSON. The pptx-create-native-v1 and pptx-roundtrip-native-v1 profiles add
runtime-verified non-text shape/image/table/group/chart creation and slide-owned,
including nested, shape/image/table/group/chart transforms plus bounded native
image crop, literal chart-data, table-cell, and nested-group text edits; they do
not yet claim producer-elevated C3/R3.
These profiles do not claim arbitrary PPTX editing. They also do not claim full reconstruction from normalized JSON or pixel-identical rendering.
The PowerPoint reader currently handles:
- slide order, size, backgrounds, layouts, masters, themes, and notes;
- rich text, paragraphs, lists, links, fonts, and text fitting;
- preset and custom shapes, connectors, fills, borders, shadows, and groups;
- images, cropping, filters, embedded audio, and embedded video;
- tables, charts, SmartArt diagrams, and Office Math;
- transitions, relationship resolution, diagnostics, and resource limits.
OOXML has a very large extension surface. Unsupported optional structures may be omitted with a diagnostic rather than represented inaccurately.
The target npm package is @evoelsewhere/oakit:
pnpm add @evoelsewhere/oakitnpm install @evoelsewhere/oakitOn macOS or Linux, install the command-line interface from the EvoElsewhere tap after the first formula release:
brew install evoelsewhere/tap/oakitThe Homebrew formula installs the oakit executable. Use npm or pnpm when the
programmatic JavaScript API is required.
The npm release and Homebrew formula are distributed independently. When a registry release is not available for the desired version, use the repository directly for development.
The package installs the oakit executable for deterministic Office-to-JSON
workflows in terminals, scripts, CI jobs, and agent sandboxes. The current CLI
accepts PowerPoint (.pptx) input.
Install the published CLI globally from npm:
npm install --global @evoelsewhere/oakit
oakit --versionIt can also be run without a global installation:
npx --package @evoelsewhere/oakit oakit deck.pptx --prettyJSON is written to stdout by default, making the command suitable for pipes:
oakit deck.pptx > deck.jsonUse the explicit convert command and --output when writing a file directly:
oakit convert deck.pptx --output deck.json --prettyBoth command forms are equivalent. OAKit refuses to overwrite the input document with JSON output.
Render one or more slides as PNG files plus a structured manifest:
oakit render deck.pptx \
--output previews \
--render-format png \
--slides 1,3 \
--scale 1Use --render-format svg for self-contained vector output. The output directory
contains deterministic slide-N.png or slide-N.svg files and
manifest.json. The manifest records each filename, byte length, MIME type,
dimensions, source slide number, and approximation warnings so an agent can
inspect the preview without inferring metadata from filenames.
Rendering runs in-process and does not require Microsoft Office, LibreOffice, Google Slides, a headless browser, or a conversion service. PNG rendering is a Node.js CLI capability; SVG rendering uses the same browser-neutral renderer exposed by the public API.
Create portable JSON that carries the strict semantic preview, integrity metadata, and complete source package:
oakit snapshot deck.pptx --output deck.oakit.json --prettyRestore it only after its JSON shape, canonical Base64, source hash, semantic preview, and consistency metadata have been verified:
oakit restore deck.oakit.json --output restored.pptxA successful no-op restore is an R0 hand-off: restored.pptx is byte-for-byte
identical to deck.pptx, including package parts OAKit does not interpret.
The portable document preview is integrity-bound to the source. Editing that
preview directly is rejected; authorized changes are represented as bound,
hash-protected operations.
Inspect the snapshot's document for a stable run key, schedule an edit, then
restore the edited portable snapshot:
oakit edit-text deck.oakit.json \
--target slide-1-element-1-run-1 \
--value "Updated by an agent" \
--output edited.oakit.json \
--pretty
oakit restore edited.oakit.json --output edited.pptxThe portable JSON continues to carry the original PPTX bytes plus an operation log; it never hides an already-edited package behind stale operations. Restore checks the source hash and exact text precondition, patches only the owning slide part, verifies all untouched payloads byte-for-byte, strict-parses the result, and compares the full semantic preview. The current edit profile is deliberately narrow: one plain DrawingML text node in a slide-owned text shape. Fields, line breaks, multiple runs, compatibility extensions, signed packages, and macro-enabled packages fail closed with a typed error.
Move, resize, rotate, or flip that text element with a partial transform. Fields not provided are copied from the bound preview:
oakit transform-text edited.oakit.json \
--target slide-1-element-1 \
--x=-10 \
--width 500 \
--rotation 15 \
--flip-horizontal true \
--output transformed.oakit.jsonTransform editing requires one simple DrawingML a:xfrm owned by the target
text shape. Group coordinate children and compatibility extensions fail closed.
PowerPoint preserves the declared values exactly in the native producer gate;
LibreOffice's controlled save/reopen gate allows at most 0.2 point of geometry
quantization while requiring rotation and flip state to remain exact.
All three commands run in-process without Office software or a conversion
service. snapshot and edit-text can write JSON to stdout. restore requires
an output file so binary data is never mixed with terminal text.
Use - as the input path and provide the format explicitly:
cat deck.pptx | oakit - --format pptx --document-only > deck.json
cat deck.pptx | oakit snapshot - --format pptx > deck.oakit.json
cat deck.oakit.json | oakit edit-text - --target slide-1-element-1-run-1 --value "Updated" > edited.oakit.json
cat edited.oakit.json | oakit restore - --output edited.pptx--format pptx is required for stdin because there is no filename extension
from which to infer the format.
Usage: oakit [convert] <input.pptx|-> [options]
oakit render <input.pptx|-> --output <directory> [options]
oakit snapshot <input.pptx|-> [--output <file>]
oakit edit-text <input.json|-> --target <run-key> --value <text> [options]
oakit transform-text <input.json|-> --target <element-key> [options]
oakit restore <input.json|-> --output <file.pptx>
Convert options:
-o, --output <file> Write JSON to a file instead of stdout
--strict Reject malformed optional OOXML content
--pretty Format JSON with two-space indentation
--document-only Omit format metadata and diagnostics
--image-mode <mode> Image output: none (default) or base64
Render options:
-o, --output <directory> Write slide files and manifest.json
--render-format <format> png (default) or svg
--slides <list> One-based comma-separated slide numbers
--scale <number> Positive decimal output scale (default: 1)
Snapshot options:
-o, --output <file> Write portable JSON instead of stdout
--pretty Format portable JSON with two-space indentation
Edit text options:
-o, --output <file> Write edited portable JSON instead of stdout
--target <run-key> Stable text run key from the portable document
--value <text> Replacement text; use --value=-5 for leading -
--pretty Format portable JSON with two-space indentation
Transform text options:
-o, --output <file> Write edited portable JSON instead of stdout
--target <element-key> Stable text element key from the portable document
--x <number> Set horizontal position; use --x=-10 when negative
--y <number> Set vertical position; use --y=-10 when negative
--width <number> Set positive width
--height <number> Set positive height
--rotation <number> Set rotation in degrees
--flip-horizontal <bool> Set horizontal flip: true or false
--flip-vertical <bool> Set vertical flip: true or false
--pretty Format portable JSON with two-space indentation
Restore options:
-o, --output <file> Required PowerPoint output path
PPTX input options:
--format <pptx> Input format; required when reading stdin
-h, --help Show help
-v, --version Show the installed OAKit version
The default convert output is an envelope that keeps format and recovery
information available to automation:
{
"format": "pptx",
"document": {
"slides": []
},
"diagnostics": []
}Use --document-only when a downstream tool accepts only the normalized
document model. Use --strict when partial recovery is not acceptable. Images
are omitted by default to keep agent context and pipeline output bounded;
enable --image-mode base64 only when the binary representation is required.
Audio and video payloads are never emitted by the CLI.
Errors are written as single-line JSON to stderr without a stack trace:
{
"error": {
"code": "unsupported-format",
"message": "Unsupported Office format: docx"
}
}| Exit code | Meaning |
|---|---|
0 |
Requested conversion, hand-off, render, or help passed |
1 |
Input, validation, rendering, or output failed |
2 |
Invalid command-line usage |
The CLI processes one document per invocation. Resource-limit failures remain fatal in tolerant mode, matching the programmatic API's security boundary.
import { readFile } from 'node:fs/promises';
import { parsePptxWithDiagnostics } from '@evoelsewhere/oakit';
const input = await readFile('./quarterly-review.pptx');
const { document, diagnostics } = await parsePptxWithDiagnostics(input, {
imageMode: 'none',
errorMode: 'tolerant',
});
console.log({
slideCount: document.slides.length,
size: document.size,
fonts: document.usedFonts,
diagnostics,
});Node.js Buffer extends Uint8Array, so bytes returned by readFile can be
passed directly to OAKit.
import { parsePptx } from '@evoelsewhere/oakit/pptx';
const picker = document.querySelector<HTMLInputElement>('#presentation');
const file = picker?.files?.[0];
if (file) {
const presentation = await parsePptx(file, {
imageMode: 'both',
videoMode: 'blob',
audioMode: 'blob',
});
console.log(presentation.slides);
}Use the diagnostic API when an agent must distinguish usable partial output from a clean parse:
import { parsePptxWithDiagnostics } from '@evoelsewhere/oakit';
export async function inspectPresentation(bytes: Uint8Array) {
const result = await parsePptxWithDiagnostics(bytes, {
imageMode: 'none',
videoMode: 'none',
audioMode: 'none',
errorMode: 'tolerant',
});
return {
kind: 'presentation' as const,
document: result.document,
diagnostics: result.diagnostics,
};
}Document text is untrusted content. An agent host must keep it in the data portion of its prompt or tool result and must not treat instructions embedded in a document as trusted system or developer instructions.
Both entry points expose the same reader:
import {
parsePptx,
parsePptxWithDiagnostics,
PptxParseError,
} from '@evoelsewhere/oakit';
import { parsePptx as parsePptxFormat } from '@evoelsewhere/oakit/pptx';The format-specific entry point is preferred when an application only needs PowerPoint support.
type PptxInput = ArrayBuffer | Uint8Array | Blob;interface PptxParseOptions {
imageMode?: 'base64' | 'blob' | 'both' | 'none';
videoMode?: 'blob' | 'none';
audioMode?: 'blob' | 'none';
errorMode?: 'tolerant' | 'strict';
limits?: PptxResourceLimits;
}| Option | Default | Behavior |
|---|---|---|
imageMode |
base64 |
Return data URLs, object URLs, both, or neither. |
videoMode |
none |
Create object URLs for supported embedded video. |
audioMode |
none |
Create object URLs for supported embedded audio. |
errorMode |
tolerant |
Recover with diagnostics or reject malformed OOXML. |
limits |
Safe defaults | Bound archive, XML, media, and slide processing. |
interface PptxDocument {
size: {
width: number;
height: number;
};
themeColors: string[];
usedFonts: string[];
slides: PptxSlide[];
}Positions and dimensions use points. Each slide separates authored elements from inherited layout and master content:
interface PptxSlide {
fill: Fill;
elements: PptxElement[];
layoutElements: PptxElement[];
note: string;
transition?: SlideTransition | null;
}Elements use a discriminated type field:
type |
Content |
|---|---|
text |
Positioned rich-text HTML and text layout |
shape |
Shape metadata and an SVG-compatible path when available |
image |
Package reference, selected representation, crop, and filters |
video |
Embedded or external reference and optional object URL |
audio |
Embedded reference and optional object URL |
table |
Cells, merges, dimensions, fills, and borders |
chart |
Normalized series, labels, colors, and options |
diagram |
SmartArt drawing elements and logical text |
math |
Parsed LaTeX and an optional fallback image |
group |
Nested elements in the group coordinate space |
Text is returned as an escaped HTML fragment. Applications that inject document HTML into a page should still apply their own sanitizer as defense in depth.
createPptx accepts the versioned scene model, validates it strictly, and
returns deterministic package bytes with a C2 runtime fidelity report. The
creation profiles support source-free slides containing structured text,
native rect/roundRect/ellipse shapes, signature-checked PNG/JPEG images, and
structured native tables with exact grids, cell text, fills, borders, and merges,
plus nested groups with explicit outer and child coordinate spaces; they reject
unsupported scene elements rather than emitting a guessed package.
The release capability matrix certifies the exact
pptx-create-text-v1 profile at effective C3.
import { writeFile } from 'node:fs/promises';
import { createPptx, type PptxSceneDocument } from '@evoelsewhere/oakit';
const scene: PptxSceneDocument = {
layouts: [],
masters: [],
media: [],
schemaVersion: 2,
size: { height: 540, width: 960 },
slides: [
{
elements: [
{
authored: {
transform: { height: 80, width: 600, x: 40, y: 40 },
},
key: 'title',
resolved: { hidden: false },
text: {
body: { anchor: 'center', wrap: true },
paragraphs: [
{
children: [
{
key: 'title-run',
properties: { bold: true, fontSize: 28 },
text: 'Agent-ready presentation',
type: 'run',
},
],
key: 'title-paragraph',
},
],
},
type: 'text',
},
],
key: 'slide-1',
},
],
themes: [],
};
const created = await createPptx(scene);
await writeFile('created.pptx', created.data);
console.log(created.report.level); // C2OAKit can turn parsed slides into self-contained SVG in Node.js or a browser. Node.js callers can also create PNG bytes through the dedicated Node entry point. Neither path launches or requires Microsoft Office, LibreOffice, Google Slides, a headless browser, or a network service.
import { readFile } from 'node:fs/promises';
import { renderPptxToSvg } from '@evoelsewhere/oakit';
import { renderPptxToPng } from '@evoelsewhere/oakit/pptx/node';
const input = await readFile('./deck.pptx');
const svg = await renderPptxToSvg(input, {
scale: 1,
slideNumbers: [1],
});
const png = await renderPptxToPng(input, {
scale: 1,
slideNumbers: [1],
});
console.log(svg.slides[0]?.data, png.slides[0]?.data);Every rendered slide includes bytes, MIME type, dimensions, its one-based slide number, and structured warnings for visual approximations. SVG output escapes document text, embeds only validated raster data URLs, and never follows an external reference. PNG rasterization uses the generated self-contained SVG.
For a JSON-safe, byte-exact agent hand-off, use the separate round-trip API:
import {
parsePptxRoundTripJson,
readPptxRoundTrip,
replacePptxRoundTripText,
setPptxRoundTripGroupTransform,
setPptxRoundTripImageTransform,
setPptxRoundTripShapeTransform,
setPptxRoundTripTableTransform,
setPptxRoundTripTextTransform,
serializePptxRoundTripJson,
writePptxRoundTrip,
} from '@evoelsewhere/oakit';
const runtime = await readPptxRoundTrip(input);
const textEdited = await replacePptxRoundTripText(runtime, {
targetKey: 'slide-1-element-1-run-1',
value: 'Updated by an agent',
});
const edited = await setPptxRoundTripTextTransform(textEdited, {
targetKey: 'slide-1-element-1',
value: {
flipHorizontal: false,
flipVertical: false,
height: 80,
rotation: 15,
width: 500,
x: 40,
y: 40,
},
});
const portable = await serializePptxRoundTripJson(edited);
const wireValue: unknown = JSON.parse(JSON.stringify(portable));
const restored = await parsePptxRoundTripJson(wireValue);
const output = await writePptxRoundTrip(restored);
console.log(output.report.level); // R2The portable envelope validates its exact shape, canonical Base64, source digest, package inventory, semantic preview, operation log, and consistency hashes. Changing preview fields or operation values directly is detected as tampering rather than treated as an edit.
Rendering is a portable visual aid for agents, not a claim of pixel-identical
PowerPoint layout. Exact R0 package preservation and preview rendering are
separate operations: a preview never changes the source package used by the
portable JSON round trip.
Render limits are validated before work begins or immediately after encoding the bounded result:
| Render limit | Default |
|---|---|
| Slides per request | 1,000 |
| Elements per slide | 10,000 |
| Scale | 8 |
| Pixels per slide | 32 MiPixel |
| SVG bytes per slide | 128 MiB |
| PNG bytes per slide | 256 MiB |
Callers processing public uploads should lower these limits to their own latency and memory budgets.
parsePptx returns the document directly. parsePptxWithDiagnostics returns:
interface PptxParseResult {
document: PptxDocument;
diagnostics: PptxDiagnostic[];
}Tolerant mode may omit malformed optional content while recording a structured
diagnostic. Strict mode rejects malformed XML, unsafe relationships, invalid
values, and missing required parts with PptxParseError.
Resource-limit violations always reject, including in tolerant mode. They represent a security boundary, not a recoverable fidelity problem.
OAKit treats every uploaded package as untrusted input. The reader:
- rejects unsafe package and relationship paths;
- rejects malformed XML structures and forbidden declarations;
- validates numeric values before conversion;
- does not execute macros, scripts, media, or hyperlinks;
- does not fetch external relationships;
- filters supported hyperlink protocols;
- bounds compressed input, ZIP entries, expanded bytes, XML complexity, embedded media, and slide count.
Default limits:
| Limit | Default |
|---|---|
| Compressed input | 100 MiB |
| Non-directory ZIP entries | 10,000 |
| Total declared expansion | 256 MiB |
| One expanded package part | 64 MiB |
| One expanded XML part | 16 MiB |
| XML nesting depth | 128 |
| XML elements per part | 250,000 |
| XML elements per package | 1,000,000 |
| One expanded media part | 64 MiB |
| Slides | 1,000 |
For public uploads, also isolate parsing in a worker or process and enforce an outer timeout and memory limit.
When a blob mode is enabled, OAKit calls URL.createObjectURL. The caller owns
the returned URLs and must release them:
URL.revokeObjectURL(element.blob);Remember to traverse nested group and diagram elements when releasing media.
- Node.js 20, 22, and 24;
- modern browsers with
BlobandURL.createObjectURLsupport; - ESM and CommonJS;
- declarations and source maps.
pnpm install
pnpm check| Command | Purpose |
|---|---|
pnpm dev |
Rebuild in watch mode |
pnpm test |
Run deterministic unit, integration, and property tests |
pnpm test:browser |
Run the public API suite in Chromium |
pnpm test:corpus |
Verify PowerPoint and LibreOffice documents |
pnpm test:corpus:large |
Include the large Google Slides corpus |
pnpm test:fuzz |
Fuzz SVG/PNG safety using reproducible recorded seeds |
pnpm test:mutation |
Measure whether tests detect behavioral mutations |
pnpm test:mutation:module -- relationships |
Run one incremental focused mutation module |
pnpm test:package |
Smoke-test package exports and the bundled CLI |
pnpm test:render:e2e |
Prove Office-free portable edit and SVG/PNG rendering |
pnpm test:render:memory |
Measure SVG/PNG memory at 1, 25, and 100 slides |
pnpm test:producer:powerpoint |
Run macOS PowerPoint save/reopen evidence |
pnpm test:producer:google-slides |
Run controlled Google Slides import/export evidence |
pnpm typecheck |
Run strict type checking |
pnpm lint |
Run ESLint |
pnpm format:check |
Verify formatting |
pnpm build |
Build ESM, CommonJS, declarations, and source maps |
pnpm check |
Run the required pull-request quality gates |
The fast CI matrix runs on Node.js 20, 22, and 24 plus Chromium. Producer corpus and mutation suites run in the reliability workflow. Pull requests run the seven patch modules independently with focused tests and incremental caches. Release Reliability forces every mutant, including static mutants, and balances general file shards from recorded elapsed Stryker time.
Read docs/architecture.md before changing parser ownership, public models, resource handling, or format boundaries. Development rules for coding agents and contributors live in AGENTS.md.
- stabilize the normalized PowerPoint model;
- expand real-producer fidelity and adversarial corpus coverage;
- expand mutation-tested, part-preserving PowerPoint operations;
- introduce Excel and Word as isolated format domains;
- expand higher-level document operations suitable for agent tools;
- keep the core independent of model vendors and agent frameworks.
Capabilities are documented only after their public API, implementation, and tests exist.
MIT © 2026 EvoElsewhere.

