Skip to content

Add eyeball plugin: document analysis with inline source screenshots 🤖🤖🤖#1294

Open
dvelton wants to merge 2 commits intostagedfrom
plugin/eyeball
Open

Add eyeball plugin: document analysis with inline source screenshots 🤖🤖🤖#1294
dvelton wants to merge 2 commits intostagedfrom
plugin/eyeball

Conversation

@dvelton
Copy link
Copy Markdown
Contributor

@dvelton dvelton commented Apr 4, 2026

What this adds

Eyeball is a Copilot CLI plugin that generates document analyses as Word files with inline screenshots from the source material. Every factual claim in the analysis includes a highlighted excerpt from the original document, so you can verify each assertion without switching between files or hunting for the right page.

How it works

  1. You give Copilot a document (PDF, Word file, or web URL) and ask it to analyze something
  2. Eyeball reads the source, writes the analysis, and captures a screenshot of the relevant section from the original document with the cited text highlighted in yellow
  3. The output is a Word document on your Desktop with analysis text and source screenshots interleaved

Source types supported

  • PDF files (via PyMuPDF)
  • Word documents (via Microsoft Word or LibreOffice for conversion)
  • Web pages (via Playwright + Chromium)

Plugin contents

  • plugins/eyeball/ - plugin.json and README
  • skills/eyeball/ - SKILL.md and the eyeball.py tool

Prerequisites for users

Python 3.8+ and the following pip packages: pymupdf, pillow, python-docx, playwright. The SKILL.md includes setup instructions.

Source repo

https://github.com/dvelton/eyeball

Eyeball generates Word documents where every factual claim includes a
highlighted screenshot from the source material. Supports PDFs, Word
docs, and web URLs. Designed for verifying AI-generated document
analysis against the original source.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 4, 2026 06:47
@dvelton dvelton requested a review from aaronpowell as a code owner April 4, 2026 06:47
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

🔍 Skill Validator Results

2 resource(s) checked | ✅ All checks passed

Full output
Found 1 skill(s)
[eyeball] 📊 eyeball: 1,463 BPE tokens [chars/4: 1,615] (detailed ✓), 13 sections, 10 code blocks
�[32m✅ All checks passed (1 skill(s))�[0m

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Eyeball Copilot CLI plugin + skill that generates a Word analysis document with inline, highlighted screenshots from source PDFs/Word docs/web pages to support claim verification.

Changes:

  • Introduces eyeball.py tool for document conversion (to PDF), anchor search/highlighting, screenshot generation, and DOCX assembly.
  • Adds skills/eyeball/SKILL.md to define the Copilot workflow and usage instructions.
  • Registers the plugin/skill in the docs and plugin marketplace metadata.
Show a summary per file
File Description
skills/eyeball/tools/eyeball.py Implements conversion/rendering, anchor search + screenshot highlighting, and DOCX output CLI commands.
skills/eyeball/SKILL.md Documents activation + step-by-step workflow for using Eyeball in Copilot CLI.
plugins/eyeball/README.md User-facing plugin overview, setup steps, usage examples, limitations.
plugins/eyeball/.github/plugin/plugin.json Declares plugin metadata (name/version/keywords) and includes the Eyeball skill.
docs/README.skills.md Adds Eyeball to the skills index table.
docs/README.plugins.md Adds Eyeball to the plugins index table.
.github/plugin/marketplace.json Adds Eyeball to the marketplace listing.

Copilot's findings

Comments suppressed due to low confidence (4)

skills/eyeball/tools/eyeball.py:143

  • _convert_with_word_windows swallows all exceptions and returns False, but it doesn't reliably call doc.Close() / word.Quit() on failure after Word has been launched. This can leave orphaned WINWORD.EXE processes. Use try/finally to ensure COM objects are closed/quit even when Open/SaveAs fails.
def _convert_with_word_windows(source_path, output_pdf_path):
    """Convert using Microsoft Word on Windows via win32com."""
    try:
        import win32com.client
        word = win32com.client.Dispatch("Word.Application")
        word.Visible = False
        doc = word.Documents.Open(os.path.abspath(source_path))
        doc.SaveAs(os.path.abspath(output_pdf_path), FileFormat=17)  # 17 = PDF
        doc.Close()
        word.Quit()
        return True
    except Exception:
        return False

skills/eyeball/tools/eyeball.py:176

  • render_url_to_pdf manually closes the browser at the end, but if page.goto, page.evaluate, or page.pdf throws, the browser may not be closed cleanly. Wrap browser usage in try/finally (or use Playwright context managers) so Chromium is always closed on errors/timeouts.
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle", timeout=30000)

        # Clean up navigation/footer elements for cleaner output
        page.evaluate("""
            document.querySelectorAll(
                'header, footer, nav, [data-testid="header"], [data-testid="footer"], '
                + '.site-header, .site-footer, #cookie-banner, .cookie-consent'
            ).forEach(el => el.remove());
        """)

        page.pdf(
            path=output_pdf_path,
            format="Letter",
            print_background=True,
            margin={"top": "0.5in", "bottom": "0.5in",
                    "left": "0.75in", "right": "0.75in"}
        )
        browser.close()

skills/eyeball/tools/eyeball.py:521

  • cmd_screenshot opens the PDF before parsing --anchors. If json.loads(args.anchors) (or later code) raises, pdf_doc.close() is never reached. Consider restructuring to parse/validate inputs first and/or wrap PDF usage in try/finally (or a context manager) to guarantee the file handle is closed on errors.
def cmd_screenshot(args):
    """Generate a single screenshot from a PDF."""
    pdf_doc = fitz.open(os.path.expanduser(args.source))
    anchors = json.loads(args.anchors)
    target_page = args.page
    padding = args.padding or 40

skills/eyeball/SKILL.md:125

  • The build example uses python3 eyeball.py build ... without specifying a path. Unless the instructions require running from the directory containing eyeball.py, this command will fail. Use python3 <path-to>/eyeball.py build ... (or explicitly instruct to run from <plugin_dir>/skills/eyeball/tools).
```bash
python3 eyeball.py build \
  --source "<path-or-url>" \
  --output ~/Desktop/<title>.docx \
  --title "Analysis Title" \
  --subtitle "Source description" \
  • Files reviewed: 7/7 changed files
  • Comments generated: 6

Comment on lines +39 to +45
from PIL import Image, ImageDraw
from docx import Document
from docx.shared import Inches, Pt, RGBColor
except ImportError as e:
print(f"Missing dependency: {e}", file=sys.stderr)
print("Run setup.sh or: pip3 install pymupdf pillow python-docx playwright", file=sys.stderr)
sys.exit(1)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module-level dependency imports exit the script on ImportError, which prevents setup-check from running when dependencies are missing (and also forces convert to require PyMuPDF/Pillow/python-docx even though conversion can work via Word/LibreOffice alone). Consider moving these imports into the specific commands/functions that need them, and let setup-check run without requiring any optional deps.

This issue also appears in the following locations of the same file:

  • line 130
  • line 155
  • line 516
Suggested change
from PIL import Image, ImageDraw
from docx import Document
from docx.shared import Inches, Pt, RGBColor
except ImportError as e:
print(f"Missing dependency: {e}", file=sys.stderr)
print("Run setup.sh or: pip3 install pymupdf pillow python-docx playwright", file=sys.stderr)
sys.exit(1)
except ImportError:
fitz = None
try:
from PIL import Image, ImageDraw
except ImportError:
Image = None
ImageDraw = None
try:
from docx import Document
from docx.shared import Inches, Pt, RGBColor
except ImportError:
Document = None
Inches = None
Pt = None
RGBColor = None

Copilot uses AI. Check for mistakes.
python3 eyeball.py build \
--source <path-or-url> \
--output <output.docx> \
--sections sections.json
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage example shows --sections sections.json, but cmd_build does json.loads(args.sections) (expects an inline JSON string), so passing a filename will fail. Either update the usage/docs to show a JSON string (as in SKILL.md), or add support for --sections @file.json / reading a file path.

Suggested change
--sections sections.json
--sections '[{"heading": "Section 1", "analysis": "Example analysis text"}]'

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +51
If anything is missing, run the setup script from the eyeball plugin directory:
```bash
bash <path-to>/setup.sh
```

Or install manually:
```bash
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs instruct users to run bash <path-to>/setup.sh, but this PR doesn't add a setup.sh under the plugin/skill directory. Either include the referenced script in the plugin, or remove/update the instructions to only describe the manual pip3 install ... setup path.

Suggested change
If anything is missing, run the setup script from the eyeball plugin directory:
```bash
bash <path-to>/setup.sh
```
Or install manually:
```bash
If anything is missing, install the required dependencies manually:
```bash

Copilot uses AI. Check for mistakes.
from docx.shared import Inches, Pt, RGBColor
except ImportError as e:
print(f"Missing dependency: {e}", file=sys.stderr)
print("Run setup.sh or: pip3 install pymupdf pillow python-docx playwright", file=sys.stderr)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message suggests running setup.sh, but the PR doesn't include a setup.sh file in the plugin. This is likely to confuse users on first-run failures; either add the script or adjust the message to only point to the manual pip3 install ... instructions.

Suggested change
print("Run setup.sh or: pip3 install pymupdf pillow python-docx playwright", file=sys.stderr)
print("Install dependencies with: pip3 install pymupdf pillow python-docx playwright", file=sys.stderr)

Copilot uses AI. Check for mistakes.
Comment on lines +491 to +498
has_converter = checks["Word (macOS)"] or checks["LibreOffice"]
has_web = checks["Playwright"] and checks["Chromium browser"]

print("")
print("Source support:")
print(f" PDF files: {'Ready' if all_core else 'Needs: pip3 install pymupdf pillow python-docx'}")
print(f" Word docs: {'Ready' if has_converter else 'Needs: Microsoft Word or LibreOffice'}")
print(f" Web URLs: {'Ready' if has_web else 'Needs: pip3 install playwright && python3 -m playwright install chromium'}")
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup-check treats Word conversion as available only on macOS (Word (macOS)) or via LibreOffice. On Windows, even if Word is installed, has_converter will be false and the output will incorrectly say Word docs "Needs: Microsoft Word or LibreOffice". Consider adding a Windows Word check and including it in has_converter.

Copilot uses AI. Check for mistakes.
Before writing any analysis, extract and read the full text of the source document:

```bash
python3 eyeball.py extract-text --source "<path-or-url>"
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow commands alternate between python3 <path-to>/eyeball.py ... and python3 eyeball.py ... (no path). Unless the user is instructed to cd into the tools directory, the latter won’t work. Recommend using the explicit <path-to>/eyeball.py form consistently (or add a step to change directories).

This issue also appears on line 120 of the same file.

Suggested change
python3 eyeball.py extract-text --source "<path-or-url>"
python3 <path-to>/eyeball.py extract-text --source "<path-or-url>"

Copilot uses AI. Check for mistakes.
- Make PyMuPDF/Pillow/python-docx imports soft so setup-check runs
  without dependencies installed
- Add _check_core_deps() guard at CLI command entry points
- Add Windows Word detection in setup-check
- Remove references to setup.sh (not included in plugin)
- Fix usage docstring to show inline JSON instead of filename
- Use consistent <path-to>/eyeball.py paths in SKILL.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants