pdf

PDF files: create, read, merge, fill, OCR, edit text.

  • pdf
  • documents
  • forms
  • ocr
  • text-extraction
  • reportlab
  • pypdf
  • pdfplumber
  • pymupdf
  • marker

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/skills/productivity/pdf'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Document metadata and file attachments for PDFs (pypdf). Modes (one required):  --set-meta            set metadata keys given via --title/--author/...  --clear-meta          drop all document info metadata  --attach FILE         embed a file attachment  --list-attachments    list embedded attachment names  --extract-attachments DIR  write all attachments into DIR Metadata note: values are stored in the classic DocInfo dictionary(Title/Author/Subject/Keywords). XMP metadata, if present, is notrewritten and may disagree in sophisticated viewers."""from __future__ import annotations import argparseimport jsonimport osimport sysfrom pathlib import Path  def main() -> int:    for stream in (sys.stdout, sys.stderr):        try:            stream.reconfigure(encoding="utf-8")        except Exception:            pass    parser = argparse.ArgumentParser(description="Set/clear PDF metadata; manage attachments.")    parser.add_argument("pdf", help="Input PDF path")    mode = parser.add_mutually_exclusive_group(required=True)    mode.add_argument("--set-meta", action="store_true", help="Set metadata fields")    mode.add_argument("--clear-meta", action="store_true", help="Remove all DocInfo metadata")    mode.add_argument("--attach", metavar="FILE", help="Embed FILE as an attachment")    mode.add_argument("--list-attachments", action="store_true", help="List attachment names")    mode.add_argument("--extract-attachments", metavar="DIR", help="Extract attachments into DIR")    parser.add_argument("-o", "--output", help="Output PDF (required for write modes)")    parser.add_argument("--title")    parser.add_argument("--author")    parser.add_argument("--subject")    parser.add_argument("--keywords")    parser.add_argument("--password", help="Password if the input is encrypted")    args = parser.parse_args()     try:        from pypdf import PdfReader, PdfWriter    except ImportError:        print("Missing dependency: install with 'python3 -m pip install pypdf'", file=sys.stderr)        return 2     reader = PdfReader(args.pdf)    if reader.is_encrypted:        if args.password is None or not reader.decrypt(args.password):            print("Error: input is encrypted; pass --password", file=sys.stderr)            return 3     if args.list_attachments:        names = list(reader.attachments.keys())        json.dump({"attachment_count": len(names), "attachments": names}, sys.stdout,                  ensure_ascii=False, indent=2)        print()        return 0     if args.extract_attachments:        out_dir = Path(args.extract_attachments)        out_dir.mkdir(parents=True, exist_ok=True)        written = []        for name, contents in reader.attachments.items():            data = contents[0] if isinstance(contents, list) else contents            safe = os.path.basename(name) or "attachment.bin"            target = out_dir / safe            with open(target, "wb") as fh:                fh.write(bytes(data))            written.append(str(target))        json.dump({"extracted": written}, sys.stdout, ensure_ascii=False, indent=2)        print()        return 0     if not args.output:        print("Error: -o/--output is required for write modes", file=sys.stderr)        return 4     writer = PdfWriter()    writer.append(reader)     if args.set_meta:        meta = {}        for key, value in ((f"/{k.capitalize()}", getattr(args, k))                           for k in ("title", "author", "subject", "keywords")):            if value is not None:                meta[key] = value        if not meta:            print("Error: --set-meta needs at least one of --title/--author/--subject/--keywords",                  file=sys.stderr)            return 4        writer.add_metadata(meta)        result = {"output": args.output, "set": {k.lstrip("/"): v for k, v in meta.items()}}    elif args.clear_meta:        writer.metadata = None        result = {"output": args.output, "cleared": True}    else:  # --attach        attach_path = Path(args.attach)        with open(attach_path, "rb") as fh:            writer.add_attachment(attach_path.name, fh.read())        result = {"output": args.output, "attached": attach_path.name}     with open(args.output, "wb") as fh:        writer.write(fh)    print(json.dumps(result, ensure_ascii=False))    return 0  if __name__ == "__main__":    sys.exit(main()) 
Referenced from SKILL.md