Add eyeball plugin: document analysis with inline source screenshots 🤖🤖🤖#1294
Add eyeball plugin: document analysis with inline source screenshots 🤖🤖🤖#1294
Conversation
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>
🔍 Skill Validator Results2 resource(s) checked | ✅ All checks passed Full output |
There was a problem hiding this comment.
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.pytool for document conversion (to PDF), anchor search/highlighting, screenshot generation, and DOCX assembly. - Adds
skills/eyeball/SKILL.mdto 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_windowsswallows all exceptions and returns False, but it doesn't reliably calldoc.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 whenOpen/SaveAsfails.
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_pdfmanually closes the browser at the end, but ifpage.goto,page.evaluate, orpage.pdfthrows, 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_screenshotopens the PDF before parsing--anchors. Ifjson.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 containingeyeball.py, this command will fail. Usepython3 <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
skills/eyeball/tools/eyeball.py
Outdated
| 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) |
There was a problem hiding this comment.
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
| 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 |
skills/eyeball/tools/eyeball.py
Outdated
| python3 eyeball.py build \ | ||
| --source <path-or-url> \ | ||
| --output <output.docx> \ | ||
| --sections sections.json |
There was a problem hiding this comment.
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.
| --sections sections.json | |
| --sections '[{"heading": "Section 1", "analysis": "Example analysis text"}]' |
skills/eyeball/SKILL.md
Outdated
| If anything is missing, run the setup script from the eyeball plugin directory: | ||
| ```bash | ||
| bash <path-to>/setup.sh | ||
| ``` | ||
|
|
||
| Or install manually: | ||
| ```bash |
There was a problem hiding this comment.
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.
| 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 |
skills/eyeball/tools/eyeball.py
Outdated
| 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) |
There was a problem hiding this comment.
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.
| 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) |
skills/eyeball/tools/eyeball.py
Outdated
| 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'}") |
There was a problem hiding this comment.
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.
skills/eyeball/SKILL.md
Outdated
| Before writing any analysis, extract and read the full text of the source document: | ||
|
|
||
| ```bash | ||
| python3 eyeball.py extract-text --source "<path-or-url>" |
There was a problem hiding this comment.
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.
| python3 eyeball.py extract-text --source "<path-or-url>" | |
| python3 <path-to>/eyeball.py extract-text --source "<path-or-url>" |
- 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>
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
Source types supported
Plugin contents
plugins/eyeball/- plugin.json and READMEskills/eyeball/- SKILL.md and the eyeball.py toolPrerequisites 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