Skip to content

feat!: release DeckOps 4.0.0 — probe, render, extract and convert - #2

Merged
iguangfei merged 12 commits into
mainfrom
feat/4.0-ops
Oct 20, 2026
Merged

iguangfei merged 12 commits into
mainfrom
feat/4.0-ops

Conversation

@iguangfei

Copy link
Copy Markdown
Member

Summary

DeckOps 4.0.0 replaces parse/IR artifacts with four atomic capabilities:

  • probe — local and free through the optional @deckflow/deckprobe@2.7.0 engine; no credentials, no request, the schema-2 report printed unchanged.
  • render — pages and slides to images (PPTX/PPT, PDF, Keynote, DOCX via PDF, HTML, Markdown) and decks to video.
  • extract — the structured document as JSON (--to json) or its images (--to assets).
  • convert — Markdown.

The IR never leaves the server: the SDK and CLI receive Markdown, JSON and signed result files, and the cloud reuses one parse per file for 7 days without another charge. One declarative route table (src/ops/routes.ts) maps verb × input × target to cloud tasks. Convert and extract need the server's document.* tasks, gated by GET /tools/capabilities; against an older server they fail with unsupported while render keeps working. There is no fallback to the 3.0 protocol.

Breaking. The 3.x → 4.0 command and SDK migration table is in CHANGELOG.md.

Verification

  • pnpm check: 13 files / 213 tests, typecheck (Node + browser + standalone consumer), build, browser export (39,079 bytes gzip, budget 42,987), boundaries, and the built CLI contract over real HTTP against the wire fixture.
  • pnpm check:package: tarball 359 KiB; --omit=optional install 4.42 MiB (probe reports unsupported); default install 8.98 MiB with probe working; cold start P95 64 ms.
  • End to end against the real backend (built CLI, tests/local-case):
    1. probe — local, 90 ms, no request.
    2. convert PDF — 27 s, first parse, 15 credits.
    3. extract --to json on the same file — reusedParse: true, 0 credits.
    4. extract --to assets and a rerun of convert — reusedParse: true, 0 credits.
    5. convert PPTX --split-pages — 99 slides, 5 s.
    6. 9.8 MB PPTX — multipart upload, 12 s.
    7. render PPTX --pages 1-2 — 27 s; the server renders all 99 slides and bills 100 (expected).
    8. Written Markdown and JSON hold only relative paths (no storage keys, no signed URLs); the cache keeps only 3 sha256 → fileId entries and the submission journal empties after completion.
  • Browser contract for platform-slide-editor: M1–M17 and W1–W4 are implemented, each with a test in tests/browser/client.test.ts; tests/browser/smoke.js runs the same flow in a real browser.

🤖 Generated with Claude Code

iguangfei and others added 12 commits October 20, 2026 10:57
…yped task errors

The transport now covers every task the 4.0 route table submits:

- GET /tools/capabilities, cached per API root at module level for the server's
  five-minute max-age; a 404 means a pre-4.0 server with no document tasks.
- Signed result URLs are fetched without credentials, with gateway retries and
  error messages that never include the signature.
- Waits fail with TaskFailedError / TaskTimeoutError carrying the task id;
  translateError turns them into backend_error with details.reason failed or
  timeout. DeckOpsError gains details and isDeckOpsError().
- insufficientPackage and 402 map to quota_error with the server body kept in
  details; a 402 no longer drops its body. documentSourceInvalid is input_error.
- tasks.start(), and URL-input document tasks are created without fileIds.

The 3.0 parse client moves to client/legacy*.ts over cloud/legacy-transport.ts so
the Node CLI and browser entry keep working until they move to the ops client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ops/ turns (verb, input family, target) into a cloud task chain:

- routes.ts: the 4.0 rows only, as data: render image/video over convertor.*,
  convert markdown over document.convert, extract json/assets over
  document.extract; requires gates document routes (and URL render) on the
  server capabilities; webp appends image.convertWebp per frame.
- params.ts is the single option → task-params mapping; plan.ts validates every
  option against the route and the input format and names the nearest
  supported combinations when one is missing.
- chain.ts runs multi-step chains by downloading and re-uploading the
  intermediate result, applies page selection before per-frame steps, and can
  continue a submitted task by id; results.ts normalizes file tuples and the
  document delivery shape into RemoteFile and rewrites Markdown image links.

client/ exposes createClientWith() → { render, convert, extract, tasks, files }:
one timeout budget per call, aborts rejecting with signal.reason, progress
phases upload / task / download, in-memory fileId reuse by object and SHA-256,
and one re-upload when the server has forgotten a reused file id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fake-cloud now serves /tools/capabilities (404 for legacy-server cases),
document.convert / document.extract in the RFC §5.6 delivery shape with summary-only
detail results, convertor.* and image.convertWebp file tuples, signed result files,
insufficientPackage, and forgotten file ids.

tests/ops: one case per route row over real HTTP, the old-server gate, capability
caching, quota_error details, failed and timed-out tasks, abort reasons, progress
order, upload reuse, stale file ids, resume and the low-level tasks/files API;
plus table, planning-error and parameter-mapping unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeckOps 4 drops the parse verb and the artifact directory; the server owns the parse and
reuses it by file hash.

- node/: the 3.0 artifact model (manifest v3, read/parse/convert ops, localization)
  is gone. cache.ts keeps ~/.cache/deckops/index.json with sha256 → fileId and the
  submission journal (written before dispatch and on creation, dropped only after
  the outputs are written; a rerun continues the task instead of paying again;
  password and multi-task runs are not journaled). ops.ts runs every verb under one
  --timeout budget and writes results: Markdown with images under assets/ beside it
  (never overwriting another document's asset, md5-verified), JSON with its assets,
  frames named by page. source.ts reads and freezes the source within 256 MiB.
- probe: ops/probe.ts loads the optional @deckflow/deckprobe@2.7.0 (probeFile with
  backend auto, deadline, input limit, signal); stdin goes through a temp file; it
  never resolves credentials or reaches the network; without the package it is
  unsupported with an install hint. The schema-2 report is printed unchanged.
- cli/: probe / render / extract / convert, a bare source = convert -o -, --json
  envelopes with tasks[], formats from the route table; parse, --format, --mode
  fail as usage errors that name the replacement. Error and exit codes are unchanged.
- Uploads are verified with PUT /files/:id/status/uploaded on servers with
  capabilities v1 before an id is cached or used; fileUploadInvalid is input_error.
  The runtime can keep fileIds across processes (Node does, without passwords).
- The 3.0 parse client, parse facade and legacy transport are deleted; the browser
  entry now exposes the ops client. Browser gzip budget rebased to 42,856 (measured
  38,960 with render/extract and the route table).
- check-boundaries: DeckProbe only as an optional dependency and only in
  ops/probe.ts, which may not import cloud/, auth/, config/; ops/routes.ts must be
  declarative; no node: imports in ops/ either; no probe engine in the browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The strict profile (--omit=dev --omit=optional) keeps the 3.0 budget — 12 MiB, no
native binaries, no install scripts, cold start — and requires `deckops probe` to fail
as unsupported. The default profile records the size with the probe engine and requires
`deckops probe` to produce a schema-2 report from the installed package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDF assets are named p1_i0000.png in every document, so a shared assets/ beside the
output made the second document in a directory fail. Markdown and extracted JSON now
place their images in <stem>.assets/ (staged in a temporary sibling that replaces the
directory only when every download verified), links and JSON references point there,
and --split-pages pages share it. A rerun replaces only its own directory; the
conflict error is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- createClient takes authUuid (a UUID v4, sent as X-Auth-UUID; otherwise localStorage
  df_uuid) and rejects anything else as usage_error.
- Tests over real HTTP for the remaining contract items: concurrent 401s share one
  refresh, an explicit and a stored device id, abort rejecting with the caller's reason
  (an AbortError DOMException without one), and an import with no side effects that
  works without window or localStorage.
- tests/browser/smoke.js runs the same contract in a real browser against the ops API
  (17 checks, cross-origin CORS, verified and multipart uploads, rendered frame blobs).
- Browser gzip budget: measured 39,025 bytes at 4.0.0, plus 10 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README is organized by the four verbs, the files they write, the --json envelopes, the
  Node SDK and the browser SDK contract.
- The Agent Skill teaches the probe → extract → convert → render workflow, asks before
  uploading for the cloud verbs only, and replaces references/artifact.md with probe.md.
- CHANGELOG 4.0.0 lists the 3.x → 4.0 command and SDK migration table.
- src/README.md states the layering rules for ops/ and the local, free probe.
- Version 4.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`deckops doc.pdf | head -c 10` closed stdout before DeckOps wrote, and the
unhandled EPIPE printed a Node stack and exited 1. A closed stdout now ends
every verb and the default entry with exit 0 and no output. A closed stderr
loses only diagnostics: the run continues, so stdout stays complete and a
failure keeps its exit code (a login keeps waiting instead of ending as a
silent success).

check:read spawns the built CLI with each pipe closed before it writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tasks.get() now passes through the server's credits, consumedMS, createdAt
and completedAt, so callers can reconcile usage per task. The wire fixture
serves them like the task detail does: credits and createdAt from creation,
completedAt and consumedMS once the task ends.

Browser gzip budget: measured 39,079 bytes, plus 10 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe tests and the package check read tests/test-data/test.pdf, a
local sample that .gitignore keeps out of the repository, so every CI job
failed with input_error. They now probe tests/generated/test.pdf from
scripts/generate-fixtures.mjs (one page, no metadata), and check:package
generates it first. The package check asks for page_count and expects 1:
the default targets include the title and author this fixture lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iguangfei
iguangfei merged commit cd730df into main Oct 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant