This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch add-skills-for-users in repository https://gitbox.apache.org/repos/asf/tika.git
commit ca0ede08c376a6dd557ef1ab067d0d4a17de015f Author: tallison <[email protected]> AuthorDate: Wed Aug 19 18:02:47 2026 -0400 add skills to help agents use tika --- .skills/file-forensics/SKILL.md | 400 ++++++++++++++++++++++ .skills/file-forensics/demo/README.md | 21 ++ .skills/file-forensics/demo/budget.xlsx | Bin 0 -> 8396 bytes .skills/file-forensics/demo/contract.pdf | Bin 0 -> 64872 bytes .skills/file-forensics/demo/memo.docx | Bin 0 -> 52399 bytes .skills/file-forensics/demo/quarterly-report.docm | Bin 0 -> 17322 bytes .skills/file-forensics/file-forensics-config.json | 46 +++ .skills/file-to-markdown-docker/SKILL.md | 223 ++++++++++++ .skills/file-to-markdown/SKILL.md | 316 +++++++++++++++++ AGENTS.md | 15 + README.md | 25 +- 11 files changed, 1044 insertions(+), 2 deletions(-) diff --git a/.skills/file-forensics/SKILL.md b/.skills/file-forensics/SKILL.md new file mode 100644 index 0000000000..6b9ac53f3d --- /dev/null +++ b/.skills/file-forensics/SKILL.md @@ -0,0 +1,400 @@ +--- +name: file-forensics +description: > + Examine what a file claims about itself and what it actually contains — + powered by Apache Tika. True content-based type detection (extensions lie), + provenance claims (authors, dates, creating application), revision and + tamper signals (PDF incremental updates, tracked changes, hidden slides, + zip integrity), hidden and embedded content (attachments, macros), + risk indicators (PDF JavaScript actions, encryption), and content digests. + Evidence gathering, not verdicts: Tika reports what the file asserts and + what parsing observed; it does not attribute authorship or validate + signatures. Use for triaging suspicious files, provenance questions, + e-discovery-style review, or "is this file what it claims to be." +--- + +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +# File forensics with Apache Tika + +**What this can and cannot tell you.** Tika cannot tell you who wrote a +file. It can tell you — exhaustively — what the file *claims* about itself +and what it actually *contains*, and those claims are the evidence: a +`dc:creator` value is an assertion some software recorded, not an identity; +a template-default author, an inconsistency between claimed dates, or a +conspicuously *missing* field is as much a finding as a present one. Report +what the file says; let the human draw conclusions. + +**The one rule for reading Tika's output:** the key prefix tells you *who is +asserting each fact*. + +- **`tk:*`** — Tika's own parse-time observations, which the file cannot + forge into place: magic-byte-detected type, embedded-item structure, + encryption status, digests of the actual bytes. +- **Everything else** (`dc:`, `pdf:docinfo:`, `extended-properties:`, + `xmp:`, ...) — the file's claims about itself, recorded by whatever + software touched it, editable by anyone with a hex editor. + +Disagreement between the two classes is where findings live. + +**And one rule with no exceptions: no date in a file is an observation.** +Creation, modification, and print timestamps — including the per-revision +timestamps inside PDF incremental updates — are written by software under +the control of whoever produced the file, and can be set to anything. Tika +can observe that a revision layer *exists*, how large it is, and what it +*contains*; when it was written is only ever claimed. Report timestamps as +"the file records...", never "this happened at...". + +Setup (getting tika-app or a tika-server) is covered by the +`file-to-markdown` companion skill; this skill assumes one is available and +uses the same invocation shapes. **Parse suspect files in isolation — this +is not optional for forensics work.** tika-server and `tika-app -f` both +parse in crash-isolated forked processes; for hostile-file triage prefer the +Docker route with the input mounted read-only (see `file-to-markdown-docker`) +so the parse can neither modify the evidence nor touch anything else. + +The one-command forensics rig — the stock Tika image started with this +skill's config (which IS the unlock on the server surface: none of these +switches are on by default there): + +```bash +docker run -d --rm --name tika-forensics -p 127.0.0.1:9998:9998 \ + -v "$(pwd)/file-forensics-config.json:/file-forensics-config.json:ro" \ + apache/tika:latest-full -c /file-forensics-config.json +curl -T suspect.file http://localhost:9998/rmeta > suspect.rmeta.json +docker stop tika-forensics # when done +``` + +Guaranteed OCR, read-only config, process isolation, explicit named +configuration — in one command. + +## Workflow: capture once, then converse over the JSON + +This skill is **not** "run Tika and read the output into context." It's two +phases: + +**Phase 1 — capture** (once per file): parse to disk, with a digest, and +make a compact metadata-only view for the conversation: + +```bash +java -jar tika-app.jar --config=file-forensics-config.json -J suspect.file > suspect.rmeta.json +# (--digest=sha256 is only needed if you are NOT using the config; +# the config's built-in digester already covers it) +# or, against the docker rig from the isolation section above: +# curl -T suspect.file http://localhost:9998/rmeta > suspect.rmeta.json + +jq 'map(del(."tk:content"))' suspect.rmeta.json > suspect.meta.json +``` + +`suspect.rmeta.json` is the full evidence record — a JSON array where entry +0 is the file and entries 1+ are everything embedded in it, content +included. `suspect.meta.json` is the same array with the (possibly huge) +extracted text stripped out: small enough to inspect freely. + +**Phase 2 — investigate conversationally.** Each question the user asks +becomes a targeted query against the saved files, and only the answering +values enter the conversation: + +```bash +jq 'length' suspect.meta.json # how many embedded items? +jq '.[0] | keys' suspect.meta.json # what fields exist at all? +jq '.[].["tk:embedded-resource-type"]' suspect.meta.json +jq '.[0]."pdf:incremental-update-count"' suspect.meta.json +grep -o 'Normal.dotm' suspect.meta.json # quick claim checks +jq '.[2]."tk:content"' suspect.rmeta.json # content of ONE entry, + # only when asked +``` + +Never load the full rmeta JSON into context — a document with a large text +body or many attachments makes it enormous, and the conversation only ever +needs slices. The saved files also make the session auditable: the evidence +the answers came from is on disk, unchanged, re-queryable. + +## The forensics config: turn on what default parsing leaves off + +This skill ships `file-forensics-config.json` (in this skill's directory): +one config for every surface — its `"server": {}` element is required for +tika-server's `-c` and harmlessly ignored by tika-app; don't remove it. +the named, explicit parse configuration for investigation work. Use it on +every surface, every time — an examination should be able to state exactly +what configuration produced its output, and "whatever that tool defaults +to" is not that. It also makes results identical across surfaces, because +the defaults are NOT the same everywhere: + +**What it changes per surface:** + +- **tika-server, pipes mode, or the library:** the config is the difference + between seeing revision history/macros and not — none of these switches + are on there. +- **tika-app single-file mode:** the CLI quietly applies its own convenience + config (it prints a banner saying so) that already enables the marquee + items — `parseIncrementalUpdates`, `extractMacros`, `extractActions`, + `extractInlineImages`. On tika-app, `file-forensics-config.json` still adds + `extractFontNames`, `extractScripts`, `includeMissingRows`, + deleted/moved-content for legacy `.doc` (on `.docx` the convenience + config already includes it, so testing this switch on a docx shows no + diff — that's expected), and pins `accessCheckMode` to + `DONT_CHECK` (tika-app's convenience config sets a more restrictive + mode). A bare `tika-app -J` will already show PDF revisions and macros — + the config's job there is making that explicit and reproducible rather + than unlocking it. + +The full switch list: + +- `pdf-parser.parseIncrementalUpdates` — each prior revision of an + incrementally-updated PDF is parsed as its own embedded document, so you + can read what the file said *before* the last save(s) +- `pdf-parser.extractActions` — full detail of automatic actions/JavaScript +- `pdf-parser.extractFontNames` — font inventory (a provenance fingerprint: + fonts betray the producing toolchain) +- `pdf-parser.extractInlineImages` — every image drawn in the PDF becomes + an embedded entry: extractable to disk with `-Z`, OCR-able, and part of the + inventory. (Repeated images are deduplicated by default — + `extractUniqueInlineImagesOnly` — which is usually what you want.) +- Office (`office-parser` + `ooxml-parser` — set on both; + 4.x config is per-component): `extractMacros` (macro code as MACRO-typed + embedded entries), `includeDeletedContent` and `includeMoveFromContent` + (tracked-change deletions and moved-away text in the output), + `includeMissingRows` (spreadsheet row gaps) +- `jsoup-parser.extractScripts` — script bodies in HTML (including HTML + email bodies and HTML attachments) appear in output instead of being + silently dropped: JavaScript in a document is something a reviewer should + see + +Some defaults are already forensics-friendly and are deliberately NOT +changed: overlapping/duplicate text is kept +(`suppressDuplicateOverlappingText: false` — text hidden under other text +stays visible), annotation/AcroForm/bookmark text is extracted, and +`accessCheckMode: DONT_CHECK` means Tika extracts regardless of the file's +claimed copy/extract restrictions while still *recording* those claims +under `access-permission:*` — the restriction flags are themselves +evidence. + +```bash +java -jar tika-app.jar --config=file-forensics-config.json -J suspect.file > suspect.rmeta.json +``` + +The config also configures a SHA-256 digester, so `tk:digest:SHA-256` +appears on every entry on **every** surface — no per-command flag to forget. + +If a parser name doesn't bind on your build, `--list-parser-names` prints +the registered names. Expect bigger output and slower parses than default +config — that's the point. + +## Identity: is it what it claims to be? + +```bash +java -jar tika-app.jar -d suspect.file # content-based media type, extension ignored +``` + +**Anchor on `tk:content-type-magic-detected`** — what the leading bytes +said, computed by Tika from content alone. The top-level `Content-Type` is +the *routing* type, and on tika-server a caller-supplied `Content-Type` +header influences it by design — so on the HTTP surface that field can +reflect upstream input, accidental or adversarial, not just Tika's own +conclusion. An extension or routed type that disagrees with +`tk:content-type-magic-detected` is a classic finding (a "pdf" that is a +zip; an "xlsx" that is plain-text CSV). Caller-supplied types are tracked +under `tk:content-type-hint`, `tk:content-type-override`, and +`tk:content-type-parser-override` (which of these appears depends on how +the type was supplied) — all input, none evidence. + +## Provenance claims + +The file's own story about its origin — read it as testimony, not fact: + +- `dc:creator` and related Dublin Core fields — claimed author(s) +- `pdf:docinfo:creator-tool`, `pdf:docinfo:producer` — what claims to have + made the PDF (e.g. a phishing PDF "authored" by a word processor that + doesn't match its claimed corporate origin) +- `extended-properties:*` (Office) — creating application/version, template + (`Normal.dotm` = stock Word), total edit time, claimed created/modified + timestamps +- **`tk:orig-resource-name`** — for xlsx, the absolute path where the file + was last saved (Excel records it in the workbook): drive letters, network + shares, and `C:\Users\<name>\...` usernames leak here. (The `tk:` prefix + here means Tika surfaced it; the *value* is still the file's own record — + treat it as a claim like the rest of this section.) + +Findings look like: dates that precede the claimed creating tool's release; +edit time of 0 on a "carefully drafted" document; a template name from an +organization other than the claimed author's; identical creator strings +across supposedly independent documents. + +## Revision and tamper signals + +- **`pdf:incremental-update-count`** (emitted by default in 4.x) — a PDF + above 0 has been modified after its initial write; each increment is a + save layered on top of the previous file. Signed-then-modified PDFs show + here. `pdf:eof-offsets` lists the byte offset of each revision's end. +- **Reading the prior versions themselves:** with the forensics config's + `parseIncrementalUpdates` on, each earlier revision appears as its own + entry in the rmeta array, typed `tk:embedded-resource-type: VERSION`, with + `pdf:incremental-update-number` giving its place in history — **0-indexed + from the earliest**; the final state is entry 0 of the array and carries + no update number. Each VERSION entry is the complete file as it existed at + that save (the bytes up to that revision's EOF offset), so its `tk:content` + is what the document said *then* — diff a version's content against entry + 0's to see what changed between saves. The layer structure and content + differences are observations; any timestamps inside each revision are that + revision's own claims — order is established by the byte layering, timing + is not. (Note: the final entry has no + `pdf:incremental-update-number` key *at all* — `jq` prints `null` for a + missing key and a null value alike, so "null on entry 0" is expected, not + an error.) +- `tk:version-count` / `tk:version-number` — the format-general spelling of + the same idea (also 0-indexed earliest-first; the latest version carries + none). PDFs emit `tk:version-count` alongside the pdf-specific key; other + formats that retain prior versions may adopt it, but not all parsers emit + these yet. +- `msoffice:has-track-changes`, `msoffice:has-comments` — content the + author may not have meant to ship. Parse output includes tracked-change + and comment text; a "deleted" tracked change is still in the file. +- `extended-properties:HiddenSlides` — slides present but not shown. +- `zip:*` integrity family (zip-based formats): `zip:integrity-check-result`, + `zip:duplicate-entry-names`, `zip:local-header-only-entries`, + `zip:central-directory-only-entries` — structural anomalies associated + with crafted or tampered archives (a duplicate entry name can make two + tools see two different "same" files). + +## Hidden and embedded content + +The rmeta array *is* the embedded-content inventory: every entry past index +0 is something inside the file — attachments, embedded objects, images, +archive members, prior VERSIONs — each with its own metadata, +`tk:embedded-resource-type` (`INLINE`, `ATTACHMENT`, `MACRO`, `METADATA`, +`THUMBNAIL`, `VERSION`, ...), `tk:embedded-depth`, and path. + +**Show the human the literal files.** The metadata inventory is for the +agent; the extracted bytes are for the person. Extract everything embedded +to disk so they can open the images, hand an attachment to another tool, or +open a prior PDF revision side-by-side with the final: + +```bash +java -jar tika-app.jar --config=file-forensics-config.json -Z --extract-dir=evidence/suspect-embedded suspect.file +``` + +- With the forensics config, this includes **macro source** (MACRO entries) + and **each prior PDF revision as a standalone, openable PDF** (VERSION + entries). +- **Read code from the extracted files, not from `tk:content`.** The default + content handler is Markdown, which escapes underscores and other + characters — VBA/script source read via `jq '.[N]."tk:content"'` comes + back visually corrupted (`VB\_Name`). The `-z`/`-Z` extracted files are + pristine bytes. (Alternative: capture with `-J -t` for unescaped plain + text per entry.) +- Extracted files are **renumbered** (`00000001.jpg`, ...); original names + live in each rmeta entry's `tk:resource-name`, and `-z`/`-Z` writes a + sidecar `<name>.json` metadata dump for mapping numbers back to names. +- Digest what you extracted (`sha256sum evidence/suspect-embedded/*/*`) so + each artifact is pinned the same way the container file is. +- Mechanics and gotchas (Pipes-mode delay, chatty stderr) are in + `file-to-markdown`; the server-side equivalent is `/unpack`, which returns + the embedded files as a zip over HTTP. **`/unpack` names differ from + `-z`/`-Z`:** plain sequential names (`1.jpg`, `2.pdf`, ...) and **no + sidecar JSON** — map names back via each rmeta entry's `tk:resource-name` + yourself. + +**Macros:** Office macro code is surfaced as embedded entries typed `MACRO`, +but only when macro extraction is enabled — it is **off by default**; +`file-forensics-config.json` (above) turns it on. Read the extracted VBA as +text. Presence of macros is a data point, not a verdict — +plenty of legitimate spreadsheets have them. + +## Risk indicators + +- `pdf:action-triggers`, `pdf:action-types`, `pdf:js-name` — automatic + actions and JavaScript wired into a PDF (open actions are a common + malicious-document mechanism, and also used legitimately by forms) +- `pdf:has-xfa`, `pdf:has-acro-form-fields` — active form machinery +- `tk:encrypted` — the file (or an embedded item) is encrypted; content + Tika couldn't read is content nobody scanned +- `tk:exception:*` — parse failures, per embedded item; a file that + crashes parsers is itself a signal worth recording + +## Signatures — presence, not validity + +`pdf:has-signature-fields` and the `tk:signature:*` family (`name`, `date`, +`reason`, `location`, `contact-info`, `filter`) report that signature +structures exist and what they claim. **Tika does not cryptographically +validate signatures** — a well-formed `tk:signature:name` proves only that +signature metadata is present, not that it verifies, and +`pdf:incremental-update-count` > 0 alongside a signature means the file +changed after some revision was signed. Use a signature-validation tool for +validity; use Tika to know there's something to validate. + +## Digests: pin down what you examined + +```bash +java -jar tika-app.jar --digest=sha256 -j suspect.file +# adds tk:digest:SHA-256 to the metadata +``` + +Record the digest alongside findings so they're tied to exact bytes — +malware family lookups, dedup across an evidence set, and "is this the same +file I looked at yesterday" all start here. (`md5`, `sha1`, `sha384`, +`sha512` also available; SHA3 requires configuring the BouncyCastle digester +in a JSON config.) + +## Demo files + +The `demo/` directory in this skill ships four small real files with real +findings — a PDF with revision history, a macro-bearing Word document, a docx with +embedded content, and an xlsx whose metadata records the absolute path where +it was last saved — plus a README of suggested questions. They're +for showing a human what this skill does on files where the default parse +looks unremarkable, and for smoke-testing your setup end-to-end. + +## Showing the human the evidence + +Terminal `jq` quotes are fine for single answers; for "walk me through what +you found," give the human something browsable: + +- If an interactive JSON viewer is installed, use it: `jless + suspect.meta.json` or `fx`, or `visidata` (the rmeta array is naturally + tabular — rows are entries, columns are keys). +- **Better: generate a report.** (Run the embedded-file extraction first if + the report will link to extracted items.) Write a self-contained static + HTML file next to the evidence — summary and digests up top, a claims-vs-observations + table per entry, a collapsible `<details>` section per embedded item, + relative links to the extracted files — and open it in the browser. No + server, no dependencies, everything embedded (a page that phones out is + not an evidence artifact). Regenerate it as the investigation deepens; it + doubles as the deliverable the human keeps. +- If your agent host supports publishing pages/artifacts, that can make the + report nicely viewable — **but read this first: publishing uploads the + evidence to an external service.** Document content, metadata, extracted + file names and paths leave the machine; anything shared can be forwarded, + cached, or indexed beyond your control, and "deleted" rarely means gone. + For anything sensitive — client documents, case material, unreleased + content, anything under privilege or NDA — keep the report as a local + HTML file and do not publish it. Publish only when the human has + explicitly confirmed the content is safe to leave the machine. + +## Evidence discipline + +- Work on a copy; mount read-only in Docker. Tika doesn't modify inputs, + but the discipline should not depend on that. +- Save the full rmeta JSON per file and record the exact command and Tika + version (`--version`) with it — findings should be reproducible. +- Report inconsistencies as inconsistencies ("claimed creation 2019, claimed + creator tool released 2021"), not conclusions ("forged"). +- This is content analysis, not chain-of-custody forensics: no write + blocking, no acquisition hashing, no court-grade custody trail. For + matters likely to face legal scrutiny, treat Tika as the analysis layer + inside a proper forensic process, not the process. diff --git a/.skills/file-forensics/demo/README.md b/.skills/file-forensics/demo/README.md new file mode 100644 index 0000000000..5592344f40 --- /dev/null +++ b/.skills/file-forensics/demo/README.md @@ -0,0 +1,21 @@ +# Demo files — try the skill on these + +Real files with real findings to discover (Apache Tika test corpus, +ALv2-licensed, renamed for realism). Ask your agent, for example: + +| File | Try asking | +|---|---| +| `contract.pdf` | "Has this PDF been modified since it was first written? Show me what it said before." | +| `quarterly-report.docm` | "Does this document contain macros? Show me the code." | +| `memo.docx` | "What did the author delete or comment on? Show me content this file still carries but doesn't display." | +| `budget.xlsx` | "Where was this spreadsheet last saved, and what does that path reveal about its author's machine?" | + +Each is small and safe, and each carries a genuine non-obvious finding — +revision history you can extract as openable prior PDFs, real macro source, +metadata that leaks a local username and path. (Note: tika-app's single-file +mode pre-enables several forensics switches, so some findings appear even +without the skill's config; the skill explains which, and the capture-once / +query workflow is where the investigation value lives.) + +Original fixture names (provenance): testPDF_incrementalUpdates.pdf, +testWORD_macros.docm, testWORD_embedded_pics.docx, testEXCEL_big_numbers.xlsx. diff --git a/.skills/file-forensics/demo/budget.xlsx b/.skills/file-forensics/demo/budget.xlsx new file mode 100644 index 0000000000..ce5dd8e1f5 Binary files /dev/null and b/.skills/file-forensics/demo/budget.xlsx differ diff --git a/.skills/file-forensics/demo/contract.pdf b/.skills/file-forensics/demo/contract.pdf new file mode 100644 index 0000000000..8494cc8396 Binary files /dev/null and b/.skills/file-forensics/demo/contract.pdf differ diff --git a/.skills/file-forensics/demo/memo.docx b/.skills/file-forensics/demo/memo.docx new file mode 100644 index 0000000000..1a63e6f585 Binary files /dev/null and b/.skills/file-forensics/demo/memo.docx differ diff --git a/.skills/file-forensics/demo/quarterly-report.docm b/.skills/file-forensics/demo/quarterly-report.docm new file mode 100644 index 0000000000..a9153100f4 Binary files /dev/null and b/.skills/file-forensics/demo/quarterly-report.docm differ diff --git a/.skills/file-forensics/file-forensics-config.json b/.skills/file-forensics/file-forensics-config.json new file mode 100644 index 0000000000..8df9c3ab97 --- /dev/null +++ b/.skills/file-forensics/file-forensics-config.json @@ -0,0 +1,46 @@ +{ + "server": {}, + "parsers": [ + { + "default-parser": {} + }, + { + "pdf-parser": { + "extractActions": true, + "extractFontNames": true, + "parseIncrementalUpdates": true, + "extractInlineImages": true + } + }, + { + "office-parser": { + "extractMacros": true, + "includeDeletedContent": true, + "includeMoveFromContent": true, + "includeMissingRows": true + } + }, + { + "ooxml-parser": { + "extractMacros": true, + "includeDeletedContent": true, + "includeMoveFromContent": true, + "includeMissingRows": true + } + }, + { + "jsoup-parser": { + "extractScripts": true + } + } + ], + "parse-context": { + "commons-digester-factory": { + "digests": [ + { + "algorithm": "SHA256" + } + ] + } + } +} diff --git a/.skills/file-to-markdown-docker/SKILL.md b/.skills/file-to-markdown-docker/SKILL.md new file mode 100644 index 0000000000..fd0bc8fdcb --- /dev/null +++ b/.skills/file-to-markdown-docker/SKILL.md @@ -0,0 +1,223 @@ +--- +name: file-to-markdown-docker +description: > + Run Apache Tika as a Docker container when you need guaranteed OCR (scanned + PDFs, images) or geospatial raster support with zero local install — + `apache/tika:<version>-full` bundles Tesseract, GDAL, ImageMagick, and + fonts. Also covers the minimal image, port/volume/memory conventions, the + path-identity mount gotcha, and how to confirm OCR actually ran rather than + silently returning no text. Powered by Apache Tika. Use when a local `tika-app`/`tika-server` + doesn't have Tesseract installed, or you want a disposable, self-contained + parsing environment. Companion to the `file-to-markdown` skill, which covers the + parsing calls themselves once a server is up. +--- + +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +# Running Apache Tika via Docker + +Two images on Docker Hub: `apache/tika` (REST server, port 9998) and +`apache/tika-grpc` (gRPC, port 9090). This skill covers `apache/tika`; the +`file-to-markdown` companion skill covers the parsing calls in more depth once the +container is up (same HTTP API either way), but the examples below are +enough to parse on their own. + +The Docker route needs **no local Java at all** — the container brings its +own. If the host lacks Java 17+ (Tika 4.x's requirement), this is the +easiest path, not just the OCR path. This skill describes Tika 4.x images; +on a 3.x image the default output is XHTML, not Markdown. + +## Minimal vs `-full` — the choice that matters + +``` +apache/tika:<version> # JRE + tika-server-standard, pure-Java parsers only +apache/tika:<version>-full # + Tesseract OCR, GDAL, ImageMagick, font sets +``` + +**Take `-full` if the task involves OCR (scanned PDFs, photos of documents, +image-only PDFs) or geospatial rasters — otherwise take minimal.** A scanned +PDF parsed against the minimal image doesn't error; it just silently returns +little or no text, because there's no Tesseract to run. If output looks +suspiciously short for a document that's clearly a scan, that's the signal +you're on the wrong image, not that the file has no text — see **Confirming +OCR actually ran**, below. + +Tag forms: `<version>` rolls forward on rebuild of the same release, +`<version>-<N>` is immutable (pin this in anything long-lived), `latest` +tracks newest stable. Published for `linux/amd64`, `linux/arm64`, +`linux/s390x` — `docker pull`/`docker run` picks the right one automatically. + +## Starting it + +```bash +docker run -d -p 127.0.0.1:9998:9998 apache/tika:latest-full +# or pin a real version: apache/tika:4.0.0-full +curl -T document.pdf http://localhost:9998/tika +``` + +**Bind to `127.0.0.1`, not `0.0.0.0` or a bare port mapping, unless you +specifically mean to expose it.** Docker writes its own iptables rules, so +`-p 9998:9998` (no host part) can publish the server past your host +firewall onto the network — a real, easy-to-hit surprise, not a theoretical +one. Tika parses untrusted input by design; the server itself does no +authentication, so treat network exposure as a deliberate decision, not a +default. + +Give it a few seconds to start before the first request — `curl -sf +http://localhost:9998/version` is a simple readiness check to poll in a +script. + +## Mounting files: get the path identity right + +Plain `curl -T` uploads need **no mount at all** — the document travels in +the HTTP body. Mounts matter when the container must read paths itself: a +`-c` config file, pipes fetchers reading from a directory, extra jars. The +path-identity rule below applies to **directories of documents that callers +reference by path**; a single config or jar mounted at a fixed container +path (`-v .../my.json:/my.json:ro`) is fine and normal — nothing translates +those paths back and forth. When a caller (an agent, a script) passes +filesystem paths, mount the directory at the **same absolute path inside +the container** that the caller uses outside it: + +```bash +docker run -d -p 127.0.0.1:9998:9998 \ + -v "/home/me/project:/home/me/project:ro" \ + apache/tika:<version>-full +``` + +Don't remap to something like `/data` — if you do, every path the caller +passes needs translating before Tika can see it, and every response +(embedded-file paths from `/unpack`, resource names) needs translating back. +Matching the host path exactly makes the container a transparent stand-in +for a locally-installed Tika: paths just work in both directions. `:ro` is +worth defaulting to — Tika only needs to read the input, and a read-only +mount is a real containment boundary if a parse goes wrong, on top of (not +instead of) the process isolation Tika's own forked workers already give +you. + +The container runs as a **non-root user, UID/GID `35002:35002`**. Mounted +input files must be readable, and any directory Tika writes to must be +writable, by that UID — a mount that's `0600` owned by your host user will +fail inside the container even though it works fine outside it. + +## Confirming OCR actually ran + +Don't assume the `-full` image means OCR fired on a given file — confirm it, +especially the first time you stand one up. + +**For PDFs**, `pdf:ocr-page-count` is a verified, code-confirmed signal — +non-zero means Tesseract processed that many pages: + +```bash +curl -T scanned.pdf http://localhost:9998/rmeta | jq '.[0]."pdf:ocr-page-count"' +``` + +Secondary evidence: the same entry's `tk:parsed-by-full-set` lists +`org.apache.tika.parser.ocr.TesseractOCRParser` when Tesseract ran. + +`0` or `null` means OCR didn't run on this PDF — check you're on `-full` (not +minimal), that the PDF is actually image-only (a PDF with a real text layer +correctly skips OCR — that's not a bug), and that no mounted config sets +`skipOcr: true` (see below). If the key exists but under a **camelCase name** +(`pdf:ocrPageCount`) — or you see `X-TIKA:*` keys — your image predates the +4.0.0 metadata-key renames; upgrade the image rather than adapting to the old +spellings. + +**For standalone images** (not embedded in a PDF), there's no single +verified flag confirmed here — this skill doesn't have a code-checked answer +for that case yet. The pragmatic fallback: compare extracted-text length +against a file you know is a genuine scan; suspiciously empty output on a +visibly text-bearing image is the same "wrong image" signal as above. + +## Configuration (turning OCR off, or anything else) + +Mount a `tika-config.json` and point `-c` at it — anything after the image +name is appended to the entry point, which already sets `-h 0.0.0.0` (don't +pass `-h` again): + +```bash +docker run -d -p 127.0.0.1:9998:9998 \ + -v "$(pwd)/tika-config.json:/tika-config.json" \ + apache/tika:<version>-full -c /tika-config.json +``` + +To keep `-full`'s other parsers but disable OCR specifically (e.g. you only +wanted GDAL): + +```json +{ + "parsers": [ + { "default-parser": {} }, + { "tesseract-ocr-parser": { "skipOcr": true } } + ] +} +``` + +## Memory + +Size the container's `--memory` limit; don't pass `-Xmx` — the JVM sizes its +heap from the container's own limit. Tika Pipes forks additional JVMs +*inside* the same container for isolation, and each fork's heap comes out of +that same limit, so size for the forks, not just the parent: + +```bash +docker run -d -p 127.0.0.1:9998:9998 --memory 4g apache/tika:<version>-full +``` + +## Disposable / one-shot use + +For a short-lived session, name the container so you can stop it +deterministically — backgrounding `docker run` does NOT stop the container +when your script exits, and a leaked container keeps port 9998 occupied for +your next attempt: + +```bash +docker run -d --rm --name tika-tmp -p 127.0.0.1:9998:9998 apache/tika:latest-full +until curl -sf http://localhost:9998/version >/dev/null; do sleep 1; done +curl -T document.pdf http://localhost:9998/tika +docker stop tika-tmp # --rm removes it on stop +docker ps --filter name=tika-tmp # verify: should list nothing +``` + +For many parses in a session, start it once and reuse it — the JVM warm-up +cost is worth paying only once, not per file. + +## Troubleshooting + +- **`500` with an empty body** from `/tika` or `/rmeta`: the response tells + you nothing — go straight to `docker logs <container>`. A log line like + `Unknown fetcher type: 'file-system-fetcher' ... Available types: []` plus + a pf4j `No 'plugins' root` warning means the server can't find its plugins + directory — a symptom of an outdated or hand-built image whose working + directory isn't the distribution root. Official 4.0.0+ images set the + working directory correctly; upgrade the image (workaround for a broken + one: add `--workdir /opt/tika-server`). +- **Old metadata key spellings** (`pdf:ocrPageCount`, `X-TIKA:*`): image + predates 4.0.0 — upgrade (see the OCR section). +- **Connection refused right after start**: the JVM is still booting; poll + `curl -sf http://localhost:9998/version` rather than sleeping a fixed time. +- **Permission denied reading a mounted file**: the container runs as UID + 35002 — see the mount section. + +## What's not built yet + +There is no `apache/tika-app` image and no MCP mode for the Tika Docker +images as of this writing — the container always runs `tika-server`. If a +task specifically wants a stdio-spoken tool (MCP) rather than an HTTP server, +that isn't available through Docker today; use the `file-to-markdown` skill's +tika-app path directly on the host instead. diff --git a/.skills/file-to-markdown/SKILL.md b/.skills/file-to-markdown/SKILL.md new file mode 100644 index 0000000000..52253471b8 --- /dev/null +++ b/.skills/file-to-markdown/SKILL.md @@ -0,0 +1,316 @@ +--- +name: file-to-markdown +description: > + Turn almost any file into Markdown plus metadata — PDF, Office, HTML, + email, archives, images, audio/video, 1000+ formats — powered by Apache + Tika, either via + the tika-app CLI (zero setup, one file) or a running tika-server (curl, + warm process, many calls). Leads with rmeta (structured, embedded-item-aware + output) as the default operation rather than flat concatenated text, since + you can't tell whether a file has embedded content from its extension. + Covers metadata-only triage, type and language detection, OCR, and + output-size discipline for agent context. Use whenever a task involves + reading the content of a file whose format you don't want to hand-parse. +--- + +<!-- +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> + +# Using Apache Tika from an agent + +Apache Tika turns almost any document into text you can read, and reports +per-document content as **Markdown** by default (4.x) — the format you +actually want, not raw XML or a wall of HTML tags. See below for why to +reach for the structured `rmeta` view rather than a flat blob, even when the +file looks simple. + +This skill is for reading files, one at a time, inside your normal working +loop. It is not a batch pipeline — see **Batch processing**, below, for why. + +**When NOT to reach for Tika:** if your host can already read the file +natively (many agent environments read PDFs and images directly) and you +only need its visible text, use that — it's one step, not three. Tika earns +its place for everything else: `.docx`/`.xlsx`/`.pptx`, email (`.eml`, +`.msg`) and its attachments, archives, embedded content inside anything, +metadata (authors, dates, edit history), OCR, and the long tail of ~1000 +formats nothing else opens. + +**Requirements:** Tika 4.x needs **Java 17+** (`java -version` to check). +No Java 17? In order of least pain: + +1. **Docker installed?** Use the Docker route — zero local Java (see the + `file-to-markdown-docker` companion skill, or + `docker run apache/tika:latest-full`). +2. **No Docker either? Offer to install a user-local Java — ask the user + first; never install software silently.** The least invasive option is a + Temurin JRE unpacked into a directory the user owns: ~45 MB, no admin + rights, no PATH changes, uninstall = delete the directory. + + ```bash + mkdir -p ~/tika-jre && cd ~/tika-jre + curl -L "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse" | tar xz + ~/tika-jre/*/bin/java -jar tika-app.jar --version + ``` + + Swap `linux`/`x64` in the URL for `mac`|`windows` and `aarch64` as + needed (Windows: fetch the `.zip` variant and unzip). If the user prefers + a managed install, the system package works too (`temurin-21-jre` via + apt, `brew install --cask temurin@21`, `winget install + EclipseAdoptium.Temurin.21.JRE`) — that needs admin rights and touches + system state, so it's their call, not the default. +This skill describes **Tika 4.x**; on a 3.x install the defaults differ +(3.x outputs XHTML, not Markdown, and some flags changed) — check +`java -jar tika-app.jar --version` or `curl localhost:9998/version` if +behavior doesn't match what's described here. + +## Which surface: tika-app or tika-server? + +First gate: **no Java 17 on this machine?** Then this choice is moot — use +the Docker route (`file-to-markdown-docker` skill), or offer the user-local +JRE install (Requirements, above). With Java 17+, two ways to reach Tika — +pick based on how many files you expect to touch in this session. + +- **`tika-app` (CLI)** — zero setup, no process to manage, one file per + invocation. Each call pays a JVM startup cost (roughly a second). Use this + for "read this one file" or a handful of files. +- **`tika-server` (curl)** — a warm process you either already have running + (ask, or check `curl -s localhost:9998/version`) or start yourself. No + per-call JVM cost once it's up. Use this if you're about to read many files + in the same session, or a server is already available. + +If unsure and doing more than two or three files, start a server: + +```bash +java -jar tika-server-standard-<version>.jar & +# starts on localhost:9998 +``` + +**Getting either one:** download the **zip** distribution from +https://tika.apache.org/download.html and run the jar from inside the +unzipped directory. Do NOT grab just the jar from Maven Central: 4.x jars +are thin launchers that need the `lib/` directory sitting next to them, and +fail with `NoClassDefFoundError` on their own. If a jar you've been pointed +at already works, it's inside a proper distribution — leave it where it is. + +Both surfaces are driven by the same parsing engine — output is identical +either way for the same handler/format choice. + +## The core operation: parse one file with `rmeta` + +Default to **`-J` (tika-app) / `/rmeta` (tika-server)**, not plain `/tika`. + +```bash +java -jar tika-app.jar -J document.pdf # JSON array, markdown content per entry +curl -T document.pdf http://localhost:9998/rmeta # same shape +``` + +Result is a JSON array: entry 0 is the document itself, entries 1+ are +anything embedded in it (attachments, embedded images/objects, archive +members — if any). Read entry 0's `tk:content` field for the common +single-document case (note the `tk:` prefix — a plain `.content` lookup +returns nothing). + +**Why rmeta and not plain `/tika`, even for an ordinary-looking file:** you +cannot tell whether a file has embedded content from its extension or format +— a `.pdf` can carry attachments, a `.docx` can carry embedded objects, a +`.png` can carry XMP-embedded sidecar data, and even a file you're sure is +"flat" you can't actually confirm without parsing it. Plain `/tika` doesn't +avoid this — it still recursively parses and includes any embedded text, +just concatenated into one blob with no boundaries, no per-item metadata, +and no visibility into whether an individual embedded item failed. `rmeta` +is the *same parse*, structured, at no real extra cost. Reach for plain +`/tika`/`-t` deliberately (see below), not as the default. + +`-J` combines with `-x`/`-h`/`-t`/`-m` to pick the content format used inside +each entry (default markdown); the server's equivalent is `/rmeta/<handler>` +(`/rmeta/text`, `/rmeta/html`, ...). + +To pull embedded items out as actual files (not just their extracted text): +`/unpack` on the server, or on tika-app `-z`/`--extract` (direct +attachments, depth 1) or `-Z` (recursive, all depths), with +`--extract-dir=<dir>` for the destination. This is not +format-specific to office documents — it works on anything Tika can find +embedded content in: email attachments (`.eml`, `.msg`), archive members +(`.zip`), PDF attachments, embedded objects/images in any office format +(an inline pasted picture counts), and so on. + +What lands on disk: a `<name>-embed/` directory of the embedded files +**renumbered** (`00000001.jpg`, ...) — original names are not preserved on +disk; they're in the rmeta output's `tk:resource-name` per entry, so keep +the sibling `<name>.json` (an rmeta-shaped metadata dump `-z` also writes) +if you need to map numbers back to names. + +`-z`/`-Z` route through Tika Pipes mode rather than the fast synchronous +path the other flags use — expect several seconds and a burst of +plugin/forked-JVM startup logging on stderr; that's normal, not a hang or +an error. + +Read the extracted content selectively (grep, or Read with offset/limit) +rather than dumping it into your context wholesale — see **Output +discipline** below. + +## Flat concatenated text — when you deliberately don't need structure + +If you specifically want one blob of body text and don't care about +per-document boundaries, embedded-item metadata, or per-item exception +visibility — a quick keyword search, a rough skim — plain output is cheaper: + +```bash +java -jar tika-app.jar document.pdf > document.md # markdown is the default +curl -T document.pdf http://localhost:9998/tika > document.md +# note: the HTTP response Content-Type header says text/plain even when the +# body is Markdown — the endpoint you called, not the header, tells you the format +``` + +This still includes embedded document text (see above) — it's just flattened +in. And its damage behavior is the strongest reason to prefer `rmeta`: a +*fatal* container exception gets a `422` here (partial content, no detail), +but a **recoverable** parse problem — the common case for a damaged or +truncated file — returns a plain `200` with **silently truncated content +and no signal at all**: no status change, no header, nothing in the body. +`rmeta` on the same file returns `200` too, but *shows* the damage: a +`tk:exception:warn` (or `tk:exception:container-exception`) entry with the +stack trace, and possibly fewer array entries than the intact file would +produce. Status codes cannot be relied on to detect damage — inspecting +`tk:exception:*` keys in rmeta output is the detection mechanism. + +## Metadata only (fast triage before committing to full content) + +Cheaper than a full parse when you just need to know what a file is — +author, dates, page count, content-type — before deciding whether to read it. + +```bash +java -jar tika-app.jar -j document.pdf # JSON metadata, no content +curl -T document.pdf http://localhost:9998/meta # JSON by default +``` + +This doesn't tell you whether the file has embedded content — `/meta`/`-j` +covers only the container document's own metadata. Some formats surface a +hint (Office's `msoffice:has-comments`/`has-track-changes`), but the only +reliable way to know is `rmeta`'s array length: +`java -jar tika-app.jar -J file | jq 'length'` — `1` means no embedded +items; each entry past the first is one embedded item (its +`tk:embedded-resource-type` says how it's embedded, e.g. `INLINE` for a +pasted-in image vs `ATTACHMENT`). + +## Detection: what kind of file is this, without parsing it + +```bash +java -jar tika-app.jar -d document # prints the media type +curl -T document http://localhost:9998/detect # text/plain media type + +java -jar tika-app.jar -l document.pdf # language only +curl -T document.pdf http://localhost:9998/language +``` + +`/detect` and `/language` work even on files with no extension or a +misleading one — detection is content-based. + +## OCR (scanned PDFs, images, screenshots) + +Text-layer parsing does nothing for a scanned page or a photo of text — you +need OCR, which requires Tesseract to be present. `tika-app`/local +`tika-server` only OCR if Tesseract is installed on the host; there is no +bundled fallback. + +**Guaranteed OCR, no local install:** run the `-full` tika-server Docker +image, which bundles Tesseract, GDAL, and fonts: + +```bash +docker run -d -p 127.0.0.1:9998:9998 apache/tika:<version>-full +curl -T scanned.pdf http://localhost:9998/tika # OCR runs automatically +``` + +If a parse of an image-heavy PDF comes back suspiciously short, that's the +signal you're missing OCR, not that the file has no text. For mount/path +setup, confirming OCR actually ran, and other Docker specifics, see the +`file-to-markdown-docker` skill. + +## Output discipline — don't flood your own context + +The most common mistake using Tika from inside an agent loop: piping a large +document's full Markdown/JSON straight into context. A single PDF can produce +megabytes of output. Instead: + +1. **Redirect to a file, not a variable or inline output.** `> extracted.json`, + then read what you need with offset/limit or grep — don't capture full + stdout into your working context by default. +2. **Triage with metadata or detection first** (above) when you're deciding + *whether* to read a file, not just what's in it. +3. **`rmeta`/`-J` output is compact single-line JSON — use `jq`, not grep, to + isolate one entry** (e.g. `jq '.[0]."tk:content"'`); a bare grep can find a + string but can't tell you which array entry it came from. Add + `-r`/`--pretty-print` first if you want it grep-friendly instead. A file + with many embedded items (a large `.eml`, a nested archive) can produce a + long array either way. +4. **Ask for `text`/`txt` as the per-entry handler** (`/rmeta/text`, + `-J -t`) when you only need body text, not formatting — smaller output, + same information for most downstream uses (search, keyword extraction, + summarization prompts you control the framing of). + +## Batch processing — not this skill + +If the task is "parse thousands of files," stop reaching for per-call +tika-app/curl inside your loop — that's the wrong shape (slow, and each +result would flood your context in turn). Use tika-app's Tika Pipes mode +instead, which is designed for it and writes results to an output directory +(or a configured emitter) rather than back to you: + +```bash +java -jar tika-app.jar -i /path/to/input -o /path/to/output +# one JSON-array (rmeta-shaped) output file per input document; +# add --handler m --content-only for bare .md files instead +``` + +This runs out-of-band; check the output directory or configured emitter for +results rather than expecting them in your context. Configuring fetchers, +emitters, and worker count is beyond this skill's scope — see the Tika Pipes +documentation at https://tika.apache.org/docs if you need to set this up. + +## Error handling (tika-server) + +- **`429`** — the server's worker pool is saturated, not broken. Back off and + retry (`Retry-After` header tells you how long). +- **`503`** with `TIMEOUT`/`OOM`/`UNSPECIFIED_CRASH` — that specific parse + failed (the file may be hostile or malformed); the server itself is fine. + Retrying the same file will likely fail the same way — move on rather than + loop. +- **`422`** on the raw endpoints (`/tika`, `/tika/text`, etc.) — a *fatal* + container exception; partial content is still in the body. Recoverable + parse problems do NOT get a 422 — they return `200` with silently + truncated content (see the flat-text section above); only `/rmeta` reveals + those, via `tk:exception:*`. +- **`400`** — malformed request: an unrecognized handler name on + `/rmeta/<handler>` (the message lists the valid set), or an unknown + fetcher/emitter on `/pipes`. Fix the request; retrying unchanged won't + help. **Exception:** a wrong handler under `/tika/...` returns a bare + `404`, because `/tika` has only four literal handler routes. +- Handler names differ per family: `/rmeta/<handler>` accepts `text`, `txt`, + `html`, `xml`, `body`, `markdown`, `md`, `ignore`; `/tika/<handler>` is + only `text`, `html`, `xml`, `md` (plus `json`). `/tika/markdown` is a 404 + even though `/rmeta/markdown` works. +- **`429`/`503`** (below) are documented behavior you can't easily reproduce + with clean small files — take them on faith until you hit them under load. + +## Trust note + +Tika parses untrusted files safely when it runs in a forked/isolated process +— which `tika-server` and `tika-app`'s `-f`/`--fork` mode both do. Calling +the library directly in-process on a file you don't trust has no such +protection: a hostile file can exhaust memory/CPU or crash the process. If +you're not sure a file is safe, use tika-server or `-f`, not an embedded +parser call. diff --git a/AGENTS.md b/AGENTS.md index 3603210829..a858197a5b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,6 +23,10 @@ the ground rules: build with `./mvnw` (always `clean`, `-Pfast` for quick builds), never run `git commit`/`git push` or write to GitHub, code and test conventions, pre-commit checks. +## Working on Tika + +Contributor-facing — building, testing, and releasing this codebase. + | Skill | Use when | |-------|----------| | `.skills/dev/SKILL.md` | Any development task — load at session start | @@ -32,3 +36,14 @@ conventions, pre-commit checks. | `.skills/tika-eval-encoding-regression/SKILL.md` | Charset-detector regression hunts | | `.skills/tika-eval-h2-query/SKILL.md` | Querying the tika-eval H2 database directly | | `.skills/update-site-for-release/SKILL.md` | Updating tika.apache.org for a release | + +## Using Tika + +For any agent that wants Tika as a tool — not specific to this repo, useful +whether or not you're working on Tika's own source. + +| Skill | Use when | +|-------|----------| +| `.skills/file-to-markdown/SKILL.md` | Turning a file (PDF, Office, email, archives, images, ...) into Markdown + metadata via tika-app or tika-server | +| `.skills/file-to-markdown-docker/SKILL.md` | Need guaranteed OCR/GDAL with no local install, or a disposable containerized Tika — running tika-server via Docker | +| `.skills/file-forensics/SKILL.md` | What a file claims vs. contains: provenance, tamper signals, hidden/embedded content, macros, digests — evidence, not verdicts | diff --git a/README.md b/README.md index f8f27b7a5c..06c6ab157f 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,29 @@ [](https://ci-builds.apache.org/job/Tika/job/tika-main-jdk17/lastBuild/testReport/) [](http://search.maven.org/#search|ga|1|g%3A%22org.apache.tika%22) -Apache Tika(TM) is a toolkit for detecting and extracting metadata and structured text content from various documents using existing parser libraries. +Apache Tika(TM) detects and extracts metadata and text from over a thousand +file types. As of 4.0.0 it emits **Markdown by default** — output shaped for +LLM and RAG pipelines — parses in **crash-isolated forked processes**, and +adds vision-language-model parsers (Claude, Gemini, OpenAI) for documents OCR +can't read. Tika is a project of the [Apache Software Foundation](https://www.apache.org). Apache Tika, Tika, Apache, the Apache feather logo, and the Apache Tika project logo are trademarks of The Apache Software Foundation. +## Using Tika from AI Agents + +Tika 4.x is built for agent pipelines: Markdown output by default, structured +recursive extraction (`-J` / `/rmeta`), and process isolation so a hostile +document takes down a fork, not your service. + +Ready-to-use agent skills live in [`.skills/`](.skills/): +[`file-to-markdown`](.skills/file-to-markdown/SKILL.md) (parsing via tika-app +or tika-server) and +[`file-to-markdown-docker`](.skills/file-to-markdown-docker/SKILL.md) +(containerized Tika with guaranteed OCR). They are standalone — copy them into +any agent's skill directory; nothing in them requires this repository. + ## Quick Start **Parse a file in Java:** @@ -29,7 +46,11 @@ launcher that loads the parsers from the adjacent `lib/`; on its own it fails wi `NoClassDefFoundError`. ```bash -java -jar tika-app-<version>.jar --text document.pdf +java -jar tika-app-<version>.jar document.pdf # Markdown (the 4.x default) +java -jar tika-app-<version>.jar --text document.pdf # plain text +java -jar tika-app-<version>.jar -J document.pdf # structured JSON: metadata + + # content for the document AND + # anything embedded in it ``` **Maven dependency:**
