Skip to content

Commit 3fc5516

Browse files
committed
Merge origin/main into codex/nutrient-skill-router
Resolve the PR conflict by adopting the Python plugin structure merged in #3 while preserving the broader document-processing guidance, modular references, Codex metadata, and expanded capability coverage from this branch. Add a lightweight validation workflow so packaging drift and unresolved markers are caught automatically in future PRs.
2 parents f6449b8 + 861b1e9 commit 3fc5516

24 files changed

Lines changed: 1241 additions & 89 deletions

.github/workflows/validate.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Validate
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
- "codex/**"
9+
10+
jobs:
11+
validate:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Check out repository
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.12"
21+
22+
- name: Install YAML parser
23+
run: python -m pip install --disable-pip-version-check pyyaml
24+
25+
- name: Validate repo structure
26+
run: python tools/validate-repo.py
27+
28+
- name: Validate Codex metadata
29+
run: |
30+
python - <<'PY'
31+
import pathlib
32+
import yaml
33+
34+
path = pathlib.Path("nutrient-document-processing/agents/openai.yaml")
35+
data = yaml.safe_load(path.read_text())
36+
interface = data.get("interface", {})
37+
required = ["display_name", "short_description", "default_prompt"]
38+
missing = [key for key in required if key not in interface]
39+
if missing:
40+
raise SystemExit(f"openai.yaml missing interface keys: {missing}")
41+
print("openai.yaml parsed successfully")
42+
PY
43+
44+
- name: Compile Python scripts
45+
run: |
46+
python -m py_compile nutrient-document-processing/scripts/*.py nutrient-document-processing/scripts/lib/common.py
47+
48+
- name: Smoke test script help
49+
run: |
50+
for script in nutrient-document-processing/scripts/*.py; do
51+
python "$script" --help > /dev/null
52+
done

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__/
2+
*.pyc
3+
.recording-tmp/

README.md

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,38 @@
2020
<a href="#30-second-quickstart">Quickstart</a> •
2121
<a href="#real-world-workflows">Workflows</a> •
2222
<a href="#features">Features</a> •
23-
<a href="#supported-agents">40+ Agents</a> •
24-
<a href="#alternative-integrations">MCP &amp; OpenClaw</a>
23+
<a href="#supported-agents">40+ Agents</a>
2524
</p>
2625

2726
---
2827

2928
## 30-Second Quickstart
3029

30+
**1. Get a free API key****<https://dashboard.nutrient.io/sign_up/?product=processor>**
31+
32+
**2. Install & configure:**
33+
3134
```bash
32-
# 1. Install the skill (works with 40+ agents)
35+
# Install the skill (works with 40+ agents)
3336
npx skills add PSPDFKit-labs/nutrient-agent-skill
3437

35-
# 2. Set your API key (free at https://dashboard.nutrient.io/sign_up/?product=processor)
38+
# Set your API key
3639
export NUTRIENT_API_KEY="pdf_live_..."
37-
38-
# 3. Ask your agent
39-
> "Extract the text from invoice.pdf"
4040
```
4141

42-
That's it. Your agent now has full document processing capabilities — no MCP setup required.
42+
**3. Ask your agent:**
43+
44+
> *"Extract the text from invoice.pdf"*
45+
46+
That's it. Your agent now has full document processing capabilities.
47+
48+
---
49+
50+
## Requirements
51+
52+
- Python 3.10+
53+
- `uv` installed: <https://docs.astral.sh/uv/>
54+
- Nutrient API key
4355

4456
---
4557

@@ -176,62 +188,42 @@ cp -r nutrient-agent-skill/nutrient-document-processing ~/.claude/skills/
176188

177189
---
178190

179-
## Alternative Integrations
180-
181-
### MCP Server (For agents with MCP support)
182-
183-
The **Nutrient DWS MCP Server** provides all operations as native agent tools with file I/O handling and sandboxing.
184-
185-
```bash
186-
npx @nutrient-sdk/dws-mcp-server
187-
```
188-
189-
Add to your MCP config (e.g., `claude_desktop_config.json`):
190-
191-
```json
192-
{
193-
"mcpServers": {
194-
"nutrient-dws": {
195-
"command": "npx",
196-
"args": ["-y", "@nutrient-sdk/dws-mcp-server"],
197-
"env": {
198-
"NUTRIENT_DWS_API_KEY": "YOUR_API_KEY",
199-
"SANDBOX_PATH": "/path/to/working/directory"
200-
}
201-
}
202-
}
203-
}
204-
```
205-
206-
📦 [npm](https://www.npmjs.com/package/@nutrient-sdk/dws-mcp-server) · [GitHub](https://github.com/PSPDFKit/nutrient-dws-mcp-server)
207-
208-
### OpenClaw Plugin
209-
210-
For [OpenClaw](https://openclaw.com) users:
211-
212-
```bash
213-
openclaw plugins install @nutrient-sdk/nutrient-openclaw
214-
```
215-
216-
📦 [npm](https://www.npmjs.com/package/@nutrient-sdk/nutrient-openclaw)
217-
218-
---
219-
220191
## Skill Structure
221192

222193
```
223194
nutrient-document-processing/
224-
├── SKILL.md # Main instructions (loaded by agents)
195+
├── SKILL.md # Main instructions (loaded by agents)
196+
├── agents/
197+
│ └── openai.yaml # Optional Codex App metadata
225198
├── references/
226-
│ └── REFERENCE.md # Full API reference (loaded on demand)
227-
├── LICENSE # Apache-2.0
228-
└── README.md
199+
│ ├── REFERENCE.md # Reference index
200+
│ └── *.md # Focused cookbooks by workflow type
201+
├── scripts/
202+
│ ├── *.py # Single-operation scripts
203+
│ └── lib/common.py # Shared utilities
204+
├── assets/
205+
│ ├── nutrient.svg # Skill icon
206+
│ └── templates/
207+
│ └── custom-workflow-template.py # Runtime pipeline template
208+
├── tests/
209+
│ └── testing-guide.md
210+
└── LICENSE.txt # Apache-2.0
229211
```
230212

213+
### Script Model
214+
215+
- `scripts/*.py` are single-operation scripts only.
216+
- Multi-step workflows are generated at runtime in a temporary script from `assets/templates/custom-workflow-template.py`.
217+
- Do not commit runtime pipeline scripts.
218+
- Use `references/` for HTML/URL generation, compliance outputs, and other workflows that are easier to express as direct API payloads or temporary pipelines.
219+
231220
## Documentation
232221

233222
- **[SKILL.md](nutrient-document-processing/SKILL.md)** — Agent instructions with setup and operation examples
234-
- **[REFERENCE.md](nutrient-document-processing/references/REFERENCE.md)** — Complete API reference with all endpoints, parameters, and error codes
223+
- **[Reference Index](nutrient-document-processing/references/REFERENCE.md)** — Modular cookbook for generation, conversion, extraction, security, compliance, and workflow sequencing
224+
- **[Testing Guide](nutrient-document-processing/tests/testing-guide.md)** — Manual test procedures
225+
- **[Custom Workflow Template](nutrient-document-processing/assets/templates/custom-workflow-template.py)** — Runtime pipeline starting point
226+
- **[Codex App Metadata](nutrient-document-processing/agents/openai.yaml)** — Optional manifest for Codex App packaging
235227
- **[API Playground](https://dashboard.nutrient.io/processor-api/playground/)** — Interactive API testing
236228
- **[Official API Docs](https://www.nutrient.io/guides/dws-processor/)** — Nutrient documentation
237229

@@ -241,4 +233,4 @@ Built by [Nutrient](https://www.nutrient.io/) (formerly PSPDFKit) — document S
241233

242234
## License
243235

244-
[Apache-2.0](nutrient-document-processing/LICENSE)
236+
[Apache-2.0](nutrient-document-processing/LICENSE.txt)
Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
---
22
name: nutrient-document-processing
3-
description: Use when tasks involve generating PDFs from HTML or URLs, converting Office/images/PDFs, assembling or splitting PDFs, OCRing and extracting content, redacting, watermarking, signing, filling, or producing compliance outputs like PDF/A, PDF/UA, and linearized PDFs with Nutrient DWS. Triggers include convert to PDF, OCR this scan, extract tables, merge these PDFs, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery. Prefer the Nutrient MCP server when it is already configured, otherwise call the API directly.
3+
description: >-
4+
Process documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs,
5+
convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value
6+
pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like
7+
PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables,
8+
redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
9+
license: Apache-2.0
410
metadata:
5-
short-description: Generate, convert, assemble, OCR, redact, sign, archive, and optimize documents
11+
author: nutrient-sdk
12+
version: "1.0"
13+
homepage: "https://www.nutrient.io/api/"
14+
repository: "https://github.com/PSPDFKit-labs/nutrient-agent-skill"
15+
compatibility: "Requires Python 3.10+, uv, and internet. Works with Claude Code, Codex CLI, Gemini CLI, OpenCode, Cursor, Windsurf, GitHub Copilot, Amp, or any Agent Skills-compatible product."
16+
short-description: "Generate, convert, assemble, OCR, redact, sign, archive, and optimize documents"
617
---
718

819
# Nutrient Document Processing
920

1021
Use Nutrient DWS for managed document workflows where fidelity, compliance, or multi-step processing matters more than local-tool convenience.
1122

12-
## Setup assumptions
23+
## Setup
24+
- Get a Nutrient DWS API key at <https://dashboard.nutrient.io/sign_up/?product=processor>.
1325
- Direct API calls use `Authorization: Bearer $NUTRIENT_API_KEY`.
26+
```bash
27+
export NUTRIENT_API_KEY="nutr_sk_..."
28+
```
1429
- MCP setups commonly use `@nutrient-sdk/dws-mcp-server` with `NUTRIENT_DWS_API_KEY`.
15-
- Open `references/request-basics.md` first when authentication or payload shape is the blocker.
30+
- Scripts live in `scripts/` relative to this SKILL.md. Use the directory containing this SKILL.md as the working directory:
31+
```bash
32+
cd <directory containing this SKILL.md> && uv run scripts/<script>.py --help
33+
```
34+
- Page ranges use `start:end` with 0-based indexes and end-exclusive semantics. Negative indexes count from the end.
1635

1736
## When to use
1837
- Generate PDFs from HTML templates, uploaded assets, or remote URLs.
@@ -23,38 +42,49 @@ Use Nutrient DWS for managed document workflows where fidelity, compliance, or m
2342
- Check credits before large, batch, or AI-heavy runs.
2443

2544
## Tool preference
26-
1. Prefer the Nutrient MCP server when it is already configured. It handles file I/O and reduces multipart-request boilerplate.
27-
2. Fall back to direct API calls when MCP is unavailable or the workflow is easier to express as an explicit payload.
28-
3. Use local PDF utilities only for lightweight inspection. Use Nutrient when output fidelity or compliance matters.
45+
1. Prefer `scripts/*.py` for covered single-operation workflows.
46+
2. Use `assets/templates/custom-workflow-template.py` for multi-step jobs that should still run through the Python client.
47+
3. Use the modular `references/` docs and direct API payloads for capabilities that do not yet have a dedicated helper script, especially HTML/URL generation and compliance tuning.
48+
4. Use local PDF utilities only for lightweight inspection. Use Nutrient when output fidelity or compliance matters.
2949

30-
## Request model
31-
- Most workflows use `POST https://api.nutrient.io/build`.
32-
- Use multipart requests when uploading local files. Use JSON requests when all inputs are remote URLs.
33-
- `parts` describes source files, HTML inputs, remote URLs, page ranges, and passwords.
34-
- `actions` applies ordered transformations such as OCR, redaction, watermarking, signing, flattening, or rotation.
35-
- `output` selects the final format and delivery options such as `pdf`, `text`, `docx`, `png`, `pdfa`, `pdfua`, or optimized PDF output.
36-
- Dedicated endpoints also exist for some tools such as PDF/UA auto-tagging, but `/build` is the default mental model.
50+
## Single-operation scripts
51+
- `convert.py` -> convert between `pdf`, `pdfa`, `pdfua`, `docx`, `xlsx`, `pptx`, `png`, `jpeg`, `webp`, `html`, and `markdown`
52+
- `merge.py` -> merge multiple files into one PDF
53+
- `split.py` -> split one PDF into multiple PDFs by page ranges
54+
- `add-pages.py` -> append blank pages
55+
- `delete-pages.py` -> remove specific pages
56+
- `duplicate-pages.py` -> reorder or duplicate pages into a new PDF
57+
- `rotate.py` -> rotate selected pages
58+
- `ocr.py` -> OCR scanned PDFs or images
59+
- `extract-text.py` -> extract text to JSON
60+
- `extract-table.py` -> extract tables
61+
- `extract-key-value-pairs.py` -> extract key-value pairs
62+
- `watermark-text.py` -> apply a text watermark
63+
- `redact-ai.py` -> detect and apply AI-powered redactions
64+
- `sign.py` -> digitally sign a local PDF
65+
- `password-protect.py` -> write encrypted output PDFs
66+
- `optimize.py` -> apply optimization and linearization-style options via JSON
3767

38-
Minimal direct-call template:
68+
## Multi-Step Workflow Rule
69+
Do not add new committed pipeline scripts under `scripts/`.
3970

40-
```bash
41-
curl -X POST https://api.nutrient.io/build \
42-
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
43-
-F document.pdf=@document.pdf \
44-
-F 'instructions={"parts":[{"file":"document.pdf"}]}' \
45-
-o result.pdf
46-
```
71+
When the user asks for multiple operations in one run:
72+
1. Copy `assets/templates/custom-workflow-template.py` to a temporary location such as `/tmp/ndp-workflow-<task>.py`.
73+
2. Implement the combined workflow in that temporary script.
74+
3. Run it with `uv run /tmp/ndp-workflow-<task>.py ...`.
75+
4. Return generated output files.
76+
5. Delete the temporary script unless the user explicitly asks to keep it.
4777

48-
## Workflow
49-
1. Identify the source type and the required final artifact.
50-
2. Decide whether the job is generation, conversion, extraction, security/compliance, or a chained workflow.
51-
3. Express the full pipeline in one payload when the ordering is clear and the artifact should stay in-memory on the server.
52-
4. Save outputs with stable suffixes such as `-ocr`, `-redacted`, `-pdfa`, `-pdfua`, or `-linearized`.
78+
## PDF Requirements
79+
- `split.py` requires a multi-page PDF and cannot extract ranges from a single-page document.
80+
- `delete-pages.py` must retain at least one page and cannot delete the entire document.
81+
- `sign.py` only accepts local file paths for the main PDF.
5382

5483
## Decision rules
84+
- Prefer a helper script when one already covers the requested operation cleanly.
5585
- If you control the source markup, prefer HTML generation over browser print workflows.
5686
- Use remote `file.url` inputs when the source already lives at a stable URL and you want to avoid local uploads.
57-
- Use `output.type` for conversion and finalization targets. Use `actions` for transformations.
87+
- Use `output.type` for conversion and finalization targets. Use `actions` for transformations when building direct API payloads.
5888
- OCR before text extraction, key-value extraction, or semantic redaction on scans.
5989
- Prefer preset or regex redaction when the target is explicit. Use AI redaction only for contextual or natural-language requests.
6090
- Use the PDF manipulation reference for merge, split, rotate, flatten, and page-range workflows instead of inferring those payloads from conversion examples.
@@ -68,6 +98,7 @@ curl -X POST https://api.nutrient.io/build \
6898
- Do not flatten forms or annotations until the user confirms the artifact no longer needs to stay editable.
6999
- Do not sign, archive, or linearize intermediate working files. Keep those as final-delivery steps.
70100
- Do not promise PDF/A or PDF/UA compliance without a validation step when the requirement is contractual.
101+
- Do not commit temporary workflow scripts under `scripts/`.
71102

72103
## Reference map
73104
Read only what you need:
@@ -80,9 +111,9 @@ Read only what you need:
80111
- `references/compliance-and-optimization.md` -> PDF/A, PDF/UA, optimization, and linearization
81112
- `references/workflow-recipes.md` -> end-to-end sequencing patterns for common business document workflows
82113

83-
## References
84-
- [Reference index](references/REFERENCE.md)
85-
- [API docs](https://www.nutrient.io/api/documentation/)
86-
- [Processor API overview](https://www.nutrient.io/api/processor-api/)
87-
- [API playground](https://dashboard.nutrient.io/processor-api/playground/)
88-
- [MCP server](https://github.com/PSPDFKit/nutrient-dws-mcp-server)
114+
## Rules
115+
- Fail fast when required arguments are missing.
116+
- Write outputs to explicit paths and print created files.
117+
- Do not log secrets.
118+
- All client methods are async and should run via `asyncio.run(main())`.
119+
- If import fails, install dependency with `uv add nutrient-dws`.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
# /// script
3+
# requires-python = ">=3.10"
4+
# dependencies = ["nutrient-dws"]
5+
# ///
6+
7+
import argparse
8+
import asyncio
9+
import sys
10+
from pathlib import Path
11+
12+
from nutrient_dws.builder.constant import BuildActions
13+
14+
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts"))
15+
from lib.common import create_client, write_workflow_output, handle_error
16+
17+
18+
async def main() -> None:
19+
parser = argparse.ArgumentParser(description="Template for multi-step custom workflows.")
20+
parser.add_argument("--input", required=True, help="Path or URL to the input document.")
21+
parser.add_argument("--out", required=True, help="Output file path.")
22+
args = parser.parse_args()
23+
24+
client = create_client()
25+
26+
# Customize this action list for the requested pipeline.
27+
actions = [
28+
BuildActions.ocr("english"),
29+
BuildActions.watermark_text("DRAFT", {"opacity": 0.25, "rotation": 45}),
30+
]
31+
32+
result = await (
33+
client.workflow()
34+
.add_file_part(args.input, actions=actions)
35+
.output_pdf()
36+
.execute()
37+
)
38+
write_workflow_output(result, args.out)
39+
40+
41+
if __name__ == "__main__":
42+
try:
43+
asyncio.run(main())
44+
except Exception as e:
45+
handle_error(e)

0 commit comments

Comments
 (0)