
Seed Pdf Import
- 3 installs
- 56 repo stars
- Updated August 4, 2026
- seed-hypermedia/seed
Read a PDF with vision, structure its content, and publish it as a Seed Hypermedia document via the seed-cli, extracting images to IPFS.
About
Converts PDF files into structured Seed Hypermedia documents using LLM vision for OCR and layout recognition, then publishes them with the seed-cli. A developer uses it to ingest papers or reports into the Seed platform as markdown or JSON blocks.
- LLM-powered PDF reading as an alternative to the CLI's built-in import
- Extracts images to IPFS and publishes atomically with the document
Seed Pdf Import by the numbers
- 3 all-time installs (skills.sh)
- Ranked #1,267 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/seed-hypermedia/seed --skill seed-pdf-importAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 56 |
| Last updated | August 4, 2026 |
| Repository | seed-hypermedia/seed ↗ |
What it does
Read a PDF with vision, structure its content, and publish it as a Seed Hypermedia document via the seed-cli, extracting images to IPFS.
Files
PDF to Seed Document Import
Convert PDFs into Seed Hypermedia documents by reading the PDF, structuring the content, and publishing via the Seed CLI. Images are extracted, converted to IPFS, and published atomically alongside the document.
For CLI setup, key management, and environment configuration, see the seed-hypermedia-write skill.
Prerequisites
1. Seed CLI (@seed-hypermedia/cli on npm, binary: seed-cli) -- See the seed-hypermedia-write skill for detection, installation, updates, key management, and environment configuration. 2. For image extraction (optional) -- poppler-utils for pdfimages, or Python pypdfium2. See references/pdf-extraction.md.
Workflow
Step 1: Read the PDF
Preferred: Read the PDF directly using vision capabilities. Analyze each page to understand:
- Document structure (headings, sections, hierarchy)
- Text content with formatting (bold, italic, links, code)
- Images and figures (location, captions)
- Tables, lists, code blocks, math formulas
- Reading order and logical nesting
- Metadata: title, authors, publication date, abstract/summary
Fallback: For very large PDFs or when vision is unavailable, use extraction tools documented in references/pdf-extraction.md.
Step 2: Extract Images
If the PDF contains images/figures that should be preserved:
# Extract embedded images
pdfimages -j document.pdf /tmp/pdf-images/img
# Produces /tmp/pdf-images/img-000.jpg, img-001.jpg, etc.
# Or render specific pages as images for figures
pdftoppm -png -r 300 -f 3 -l 3 document.pdf /tmp/pdf-images/pageOr use Python:
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument("document.pdf")
# Render page 3 (0-indexed) at 2x resolution
bitmap = pdf[2].render(scale=2.0)
bitmap.to_pil().save("/tmp/pdf-images/figure1.png")Step 3: Produce Content
You have two output format options:
Option A: Markdown with Frontmatter (Preferred)
Write a markdown file with YAML frontmatter containing all extracted metadata. This is the simplest approach:
---
name: 'Paper Title'
summary: 'The paper abstract or a brief summary'
displayAuthor: 'Jane Doe, John Smith'
displayPublishTime: '2024-06-15'
---
# Introduction
Paper content as markdown...
## Methods

More content...Then publish:
seed-cli document create -f extracted.md --key <keyname>Option B: JSON Blocks (Precise Control)
For precise control over block structure, annotations, and non-standard block types, produce a JSON array of HMBlockNode objects. Use file:///absolute/path for image links -- the CLI converts them to IPFS automatically.
See references/seed-document-format.md for the complete block format reference and a comprehensive example covering all block types.
Key rules:
- Every block needs a unique
id: 8 random characters from[A-Za-z0-9_-] - Headings contain their content as
children - Lists are a container Paragraph with
childrenType("Ordered"/"Unordered") and child Paragraphs - Annotations use byte-offset
starts/endsarrays within thetextfield - Images use
"link": "file:///path/to/image.png"for local files - Math blocks use LaTeX in the
textfield - Code blocks use
attributes.languagefor syntax highlighting
Then publish:
seed-cli document create -f blocks.json --name "Paper Title" --display-author "Jane Doe" --key <keyname>Or pipe JSON via stdin:
cat blocks.json | seed-cli document create --name "Paper Title" --key <keyname>Step 4: Publish and Verify
Publish using the Seed CLI (see seed-hypermedia-write skill for full reference):
# Create the document
seed-cli document create -f content.md --key <keyname>
# Or with explicit metadata overrides
seed-cli document create -f content.md \
--name "Paper Title" \
--display-author "Jane Doe, John Smith" \
--display-publish-time "2024-06-15" \
--key <keyname>
# Preview extraction without publishing
seed-cli document create -f content.md --dry-run
# Append to an existing document
seed-cli document update <hm-id> -f additional-content.md --key <keyname>Verify the result:
seed-cli document get <hm-id> --mdBuilt-in PDF Extraction
The CLI also has built-in PDF extraction (pdfjs-dist + optional GROBID) which can be used directly:
# Built-in extraction
seed-cli document create -f paper.pdf --key <keyname>
# With GROBID for better academic paper extraction
seed-cli document create -f paper.pdf --grobid-url http://localhost:8070 --key <keyname>
# Preview extraction result
seed-cli document create -f paper.pdf --dry-runThe LLM-powered approach (this skill) produces higher quality results for complex layouts, figures, and multi-column papers, but the built-in extraction is faster for simple documents.
Output Format Summary
The JSON output should be valid JSON matching HMBlockNode[]:
[
{
"block": {
"id": "<8-char-id>",
"type": "Heading|Paragraph|Code|Math|Image|Video|File|Embed|WebEmbed|Button",
"text": "...",
"annotations": [...],
"attributes": {...},
"link": "..." // for Image, Video, File, Embed, WebEmbed, Button
},
"children": [...] // nested HMBlockNode[]
}
]For the full schema, block type details, annotation format, and a comprehensive worked example, see references/seed-document-format.md.
PDF Content Extraction Reference
Tools for extracting text, tables, and images from PDFs. Use as a fallback when LLM vision alone is insufficient (e.g., very large PDFs, dense tables, or when images need to be extracted as files).
Python Libraries
pypdf -- Basic Text Extraction
from pypdf import PdfReader
reader = PdfReader("document.pdf")
print(f"Pages: {len(reader.pages)}")
text = ""
for page in reader.pages:
text += page.extract_text()pdfplumber -- Text and Tables
import pdfplumber
with pdfplumber.open("document.pdf") as pdf:
for page in pdf.pages:
# Text with layout
text = page.extract_text()
print(text)
# Tables as lists of rows
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)Advanced table extraction with custom settings:
with pdfplumber.open("document.pdf") as pdf:
page = pdf.pages[0]
tables = page.extract_tables({
"vertical_strategy": "lines",
"horizontal_strategy": "lines",
"snap_tolerance": 3,
})pypdfium2 -- Page Rendering
Render PDF pages as images for visual analysis or figure extraction:
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument("document.pdf")
for i, page in enumerate(pdf):
bitmap = page.render(scale=2.0)
img = bitmap.to_pil()
img.save(f"/tmp/pdf-images/page_{i+1}.png", "PNG")pytesseract -- OCR for Scanned PDFs
import pytesseract
from pdf2image import convert_from_path
images = convert_from_path('scanned.pdf')
text = ""
for i, image in enumerate(images):
text += f"Page {i+1}:\n"
text += pytesseract.image_to_string(image)
text += "\n\n"Command-Line Tools
pdftotext (poppler-utils)
pdftotext input.pdf output.txt # Basic extraction
pdftotext -layout input.pdf output.txt # Preserve layout
pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5pdfimages (poppler-utils) -- Extract Embedded Images
# Extract all images as JPEG files
pdfimages -j input.pdf /tmp/pdf-images/img
# Produces: /tmp/pdf-images/img-000.jpg, img-001.jpg, etc.
# Extract images preserving original format
pdfimages -all input.pdf /tmp/pdf-images/img
# List image info without extracting
pdfimages -list input.pdfpdftoppm (poppler-utils) -- Page-to-Image
# Render pages as PNG at 300 DPI
pdftoppm -png -r 300 input.pdf /tmp/pdf-images/page
# Produces: /tmp/pdf-images/page-1.png, page-2.png, etc.Quick Reference
| Task | Best Tool | Notes |
|---|---|---|
| Extract text | pdfplumber or pdftotext | pdfplumber better for structured text |
| Extract tables | pdfplumber | page.extract_tables() |
| Extract embedded images | pdfimages | Fastest, preserves original quality |
| Render pages as images | pypdfium2 or pdftoppm | For visual analysis or figure capture |
| OCR scanned PDFs | pytesseract + pdf2image | Convert to image first, then OCR |
| Quick text dump | pdftotext -layout | CLI, preserves spatial layout |
Installation
# Python libraries
pip install pypdf pdfplumber pypdfium2
# For OCR
pip install pytesseract pdf2image
# CLI tools (poppler-utils)
# Fedora/RHEL
sudo dnf install poppler-utils
# Debian/Ubuntu
sudo apt-get install poppler-utils
# macOS
brew install popplerSeed Document Block Format
Reference for generating Seed Hypermedia documents as JSON block trees. Use -f blocks.json with the CLI's document create or document update commands, or pipe JSON via stdin.
Block Node Structure
A document is an array of top-level HMBlockNode objects. Each node has a block and optional children:
[
{
"block": { "id": "...", "type": "...", ... },
"children": [ /* nested HMBlockNode[] */ ]
}
]Block IDs
Every block requires a unique id: 8 random characters from [A-Za-z0-9_-] (64-char alphabet). Generate fresh IDs for each block.
Block Types
Paragraph
{
"id": "aBcD1234",
"type": "Paragraph",
"text": "Hello world",
"annotations": [],
"attributes": {}
}Heading
Same as Paragraph but type: "Heading". Content nested under a heading becomes its children. Set attributes.childrenType to "Group" (default, may omit) so children render as nested content.
{
"id": "hd1_abcd",
"type": "Heading",
"text": "Introduction",
"annotations": [],
"attributes": {"childrenType": "Group"}
}Code
{
"id": "cd1_abcd",
"type": "Code",
"text": "console.log('hello')",
"attributes": {"language": "javascript"}
}Math
text contains LaTeX source. Rendered client-side via KaTeX.
{
"id": "mt1_abcd",
"type": "Math",
"text": "E = mc^2",
"attributes": {}
}Image
link is either ipfs://CID (already uploaded) or file:///absolute/path (CLI resolves to IPFS automatically). text is optional caption.
{
"id": "im1_abcd",
"type": "Image",
"text": "Figure 1: Architecture diagram",
"link": "file:///tmp/figure1.png",
"annotations": [],
"attributes": {"width": 800}
}Video
{
"id": "vd1_abcd",
"type": "Video",
"link": "file:///tmp/intro.mp4",
"attributes": {"width": 640, "name": "intro.mp4"}
}File
{
"id": "fl1_abcd",
"type": "File",
"link": "file:///tmp/data.csv",
"attributes": {"name": "data.csv", "size": 1024}
}Embed (Seed document reference)
{
"id": "em1_abcd",
"type": "Embed",
"link": "hm://z6Mk.../some-document",
"attributes": {"view": "Card"}
}view: "Content" (inline), "Card" (preview card), "Comments" (comment thread).
WebEmbed
{
"id": "we1_abcd",
"type": "WebEmbed",
"link": "https://www.youtube.com/watch?v=..."
}Button
{
"id": "bt1_abcd",
"type": "Button",
"text": "Learn more",
"link": "https://example.com",
"attributes": {"alignment": "center"}
}alignment: "flex-start" | "center" | "flex-end".
Children Types (Lists, Blockquotes)
The attributes.childrenType field on a parent block controls how its children render:
| Value | Rendering |
|---|---|
"Group" or omitted | Default nested content |
"Ordered" | Numbered list (1. 2. 3.) |
"Unordered" | Bullet list |
"Blockquote" | Blockquoted content |
Lists are a container Paragraph (with empty text and childrenType) whose children are the list items:
{
"block": {
"id": "ls1_abcd",
"type": "Paragraph",
"text": "",
"annotations": [],
"attributes": {"childrenType": "Unordered"}
},
"children": [
{"block": {"id": "li1_abcd", "type": "Paragraph", "text": "First item", "annotations": []}, "children": []},
{"block": {"id": "li2_abcd", "type": "Paragraph", "text": "Second item", "annotations": []}, "children": []}
]
}Annotations (Inline Formatting)
Annotations mark spans within a block's text using byte-offset arrays starts and ends. Multiple spans of the same annotation type are encoded in the same annotation object.
| Type | Fields | Description |
|---|---|---|
Bold | starts, ends | Bold text |
Italic | starts, ends | _Italic_ text |
Underline | starts, ends | Underlined text |
Strike | starts, ends | ~~Strikethrough~~ text |
Code | starts, ends | Inline code |
Link | starts, ends, link | Hyperlink |
Embed | starts, ends, link | Inline embed reference |
Example: "Hello **bold** world" where "bold" (positions 6-10) is bold:
{
"text": "Hello bold world",
"annotations": [{"type": "Bold", "starts": [6], "ends": [10]}]
}Multiple spans: "A **B** C **D**" where B (2-3) and D (6-7) are bold:
{
"text": "A B C D",
"annotations": [{"type": "Bold", "starts": [2, 6], "ends": [3, 7]}]
}Link annotation:
{
"text": "Click here for details",
"annotations": [{"type": "Link", "starts": [6], "ends": [10], "link": "https://example.com"}]
}Image Handling
Local files (file://)
Set "link": "file:///absolute/path/to/image.png". The CLI reads the file, chunks it with IPFS UnixFS, replaces the link with ipfs://CID, and publishes image blocks atomically alongside the document.
Already uploaded (ipfs://)
Set "link": "ipfs://bafkrei..." for images that are already stored on the target server.
Comprehensive Example
A document with headings, nested content, formatted text, lists, code, math, an image, and an embed:
[
{
"block": {
"id": "hd_Intro1",
"type": "Heading",
"text": "Project Overview",
"annotations": [],
"attributes": {"childrenType": "Group"}
},
"children": [
{
"block": {
"id": "p_desc01",
"type": "Paragraph",
"text": "This project implements a distributed protocol with strong consistency guarantees.",
"annotations": [
{"type": "Bold", "starts": [29, 60], "ends": [50, 79]},
{"type": "Italic", "starts": [29], "ends": [50]},
{"type": "Link", "starts": [60], "ends": [79], "link": "https://en.wikipedia.org/wiki/Consistency_model"}
]
},
"children": []
},
{
"block": {
"id": "im_arch01",
"type": "Image",
"text": "Figure 1: System architecture",
"link": "file:///tmp/pdf-images/architecture.png",
"annotations": [],
"attributes": {"width": 800}
},
"children": []
},
{
"block": {
"id": "hd_Goals1",
"type": "Heading",
"text": "Goals",
"annotations": [],
"attributes": {"childrenType": "Group"}
},
"children": [
{
"block": {
"id": "ol_cont1",
"type": "Paragraph",
"text": "",
"annotations": [],
"attributes": {"childrenType": "Ordered"}
},
"children": [
{
"block": {
"id": "li_goal1",
"type": "Paragraph",
"text": "Achieve sub-second latency for all read operations",
"annotations": [{"type": "Bold", "starts": [8], "ends": [18]}]
},
"children": []
},
{
"block": {
"id": "li_goal2",
"type": "Paragraph",
"text": "Support 10,000+ concurrent writers",
"annotations": [{"type": "Code", "starts": [8], "ends": [14]}]
},
"children": []
},
{
"block": {
"id": "li_goal3",
"type": "Paragraph",
"text": "Maintain CRDT-based conflict resolution",
"annotations": [
{"type": "Strike", "starts": [0], "ends": [8]},
{"type": "Underline", "starts": [15], "ends": [34]}
]
},
"children": []
}
]
}
]
},
{
"block": {
"id": "hd_Tech01",
"type": "Heading",
"text": "Technical Details",
"annotations": [],
"attributes": {"childrenType": "Group"}
},
"children": [
{
"block": {
"id": "p_tech01",
"type": "Paragraph",
"text": "The core algorithm uses a Merkle DAG for content-addressed storage.",
"annotations": [
{"type": "Code", "starts": [26], "ends": [36]},
{"type": "Italic", "starts": [41], "ends": [60]}
]
},
"children": []
},
{
"block": {
"id": "cd_algo1",
"type": "Code",
"text": "func Store(data []byte) CID {\n hash := sha256.Sum256(data)\n cid := NewCIDv1(hash)\n blockstore.Put(cid, data)\n return cid\n}",
"attributes": {"language": "go"}
},
"children": []
},
{
"block": {
"id": "mt_form1",
"type": "Math",
"text": "H(x) = \\sum_{i=0}^{n} h(x_i) \\mod 2^{256}",
"attributes": {}
},
"children": []
},
{
"block": {
"id": "bq_cont1",
"type": "Paragraph",
"text": "",
"annotations": [],
"attributes": {"childrenType": "Blockquote"}
},
"children": [
{
"block": {
"id": "bq_text1",
"type": "Paragraph",
"text": "Content addressing is the foundation of trustless distributed systems.",
"annotations": [{"type": "Italic", "starts": [0], "ends": [19]}]
},
"children": []
}
]
},
{
"block": {
"id": "ul_feat1",
"type": "Paragraph",
"text": "",
"annotations": [],
"attributes": {"childrenType": "Unordered"}
},
"children": [
{
"block": {
"id": "li_ft01",
"type": "Paragraph",
"text": "Content-addressed blocks with CIDv1",
"annotations": [{"type": "Bold", "starts": [0], "ends": [18]}]
},
"children": []
},
{
"block": {
"id": "li_ft02",
"type": "Paragraph",
"text": "Bitswap protocol for P2P block exchange",
"annotations": [
{"type": "Link", "starts": [0], "ends": [16], "link": "https://docs.ipfs.tech/concepts/bitswap/"}
]
},
"children": []
}
]
}
]
},
{
"block": {
"id": "hd_Refs01",
"type": "Heading",
"text": "References",
"annotations": [],
"attributes": {"childrenType": "Group"}
},
"children": [
{
"block": {
"id": "em_ref01",
"type": "Embed",
"link": "hm://z6Mkon33EULrw7gnZHrcqX89W11NtEatDk6rnq2Qm7ysJwm4/protocol-spec",
"attributes": {"view": "Card"}
},
"children": []
},
{
"block": {
"id": "we_ref01",
"type": "WebEmbed",
"link": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
},
"children": []
}
]
}
]
}
]