Metadata-Version: 2.4
Name: incite-app
Version: 0.1.1
Summary: Local-first citation recommendation system
Project-URL: Homepage, https://github.com/galenphall/incite
Project-URL: Repository, https://github.com/galenphall/incite
Project-URL: Issues, https://github.com/galenphall/incite/issues
Project-URL: Documentation, https://github.com/galenphall/incite#readme
Author: Galen Hall
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: academic,citation,embeddings,recommendation,zotero
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: bibtexparser>=2.0.0b1
Requires-Dist: faiss-cpu>=1.7.0
Requires-Dist: nltk>=3.8.0
Requires-Dist: numpy>=1.24.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rank-bm25>=0.2.2
Requires-Dist: rapidfuzz>=3.0.0
Requires-Dist: requests>=2.28.0
Requires-Dist: tqdm>=4.65.0
Provides-Extra: all
Requires-Dist: anthropic>=0.39.0; extra == 'all'
Requires-Dist: fastapi>=0.100.0; extra == 'all'
Requires-Dist: mypy>=1.0.0; extra == 'all'
Requires-Dist: onnxruntime>=1.16.0; extra == 'all'
Requires-Dist: optimum[onnxruntime]>=1.14.0; extra == 'all'
Requires-Dist: plotly>=5.18.0; extra == 'all'
Requires-Dist: pymupdf>=1.23.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
Requires-Dist: pytest-timeout>=2.0.0; extra == 'all'
Requires-Dist: pytest>=7.0.0; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: pyzotero; extra == 'all'
Requires-Dist: ruff>=0.1.0; extra == 'all'
Requires-Dist: rumps>=0.4.0; extra == 'all'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
Requires-Dist: spacy>=3.6.0; extra == 'all'
Requires-Dist: streamlit>=1.28.0; extra == 'all'
Requires-Dist: tokenizers>=0.15.0; extra == 'all'
Requires-Dist: umap-learn>=0.5.0; extra == 'all'
Requires-Dist: uvicorn>=0.23.0; extra == 'all'
Requires-Dist: voyageai>=0.3.0; extra == 'all'
Provides-Extra: api
Requires-Dist: fastapi>=0.100.0; extra == 'api'
Requires-Dist: python-multipart>=0.0.9; extra == 'api'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'api'
Requires-Dist: uvicorn>=0.23.0; extra == 'api'
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: lite
Requires-Dist: fastapi>=0.100.0; extra == 'lite'
Requires-Dist: onnxruntime>=1.16.0; extra == 'lite'
Requires-Dist: optimum[onnxruntime]>=1.14.0; extra == 'lite'
Requires-Dist: python-multipart>=0.0.9; extra == 'lite'
Requires-Dist: tokenizers>=0.15.0; extra == 'lite'
Requires-Dist: uvicorn>=0.23.0; extra == 'lite'
Provides-Extra: llm
Requires-Dist: anthropic>=0.39.0; extra == 'llm'
Provides-Extra: ml
Requires-Dist: sentence-transformers>=2.2.0; extra == 'ml'
Provides-Extra: nlp
Requires-Dist: spacy>=3.6.0; extra == 'nlp'
Provides-Extra: onnx
Requires-Dist: onnxruntime>=1.16.0; extra == 'onnx'
Requires-Dist: optimum[onnxruntime]>=1.14.0; extra == 'onnx'
Requires-Dist: tokenizers>=0.15.0; extra == 'onnx'
Provides-Extra: pdf
Requires-Dist: pymupdf>=1.23.0; extra == 'pdf'
Provides-Extra: serve
Requires-Dist: fastapi>=0.100.0; extra == 'serve'
Requires-Dist: python-multipart>=0.0.9; extra == 'serve'
Requires-Dist: sentence-transformers>=2.2.0; extra == 'serve'
Requires-Dist: uvicorn>=0.23.0; extra == 'serve'
Provides-Extra: tray
Requires-Dist: rumps>=0.4.0; extra == 'tray'
Provides-Extra: viz
Requires-Dist: plotly>=5.18.0; extra == 'viz'
Requires-Dist: umap-learn>=0.5.0; extra == 'viz'
Provides-Extra: voyage
Requires-Dist: voyageai>=0.3.0; extra == 'voyage'
Provides-Extra: webapp
Requires-Dist: plotly>=5.18.0; extra == 'webapp'
Requires-Dist: streamlit>=1.28.0; extra == 'webapp'
Requires-Dist: umap-learn>=0.5.0; extra == 'webapp'
Provides-Extra: zotero
Requires-Dist: pyzotero; extra == 'zotero'
Description-Content-Type: text/markdown

# inCite

**Write text. Get relevant papers from your library.**

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python](https://img.shields.io/badge/python-3.10%2B-brightgreen.svg)](https://www.python.org/downloads/)
[![PyPI](https://img.shields.io/pypi/v/incite-app.svg)](https://pypi.org/project/incite-app/)

## Overview

inCite is a local-first citation recommendation system. It indexes your Zotero library or a folder of PDFs and suggests relevant papers as you write. Everything runs on your machine -- no cloud account, no API keys, no data leaving your laptop.

- **Local-first**: Your papers and writing stay on your machine
- **Works with what you have**: Zotero library, a folder of PDFs, or a JSONL corpus
- **Editor plugins**: Obsidian, VS Code, Google Docs, and Microsoft Word
- **Fine-tuned models**: Citation-specific sentence transformers trained on 64K academic citation contexts

## Quick Start

```bash
pip install incite-app
incite setup
```

The setup wizard auto-detects your Zotero library (or accepts a folder of PDFs), builds a search index, and verifies everything works.

## Usage

### Command Line

```bash
# Get recommendations for a passage
incite recommend "The relationship between CO2 emissions and global temperature..." -k 10

# Start the API server (for editor plugins)
incite serve --embedder minilm-ft

# Start the menu bar app (macOS, manages the server for you)
pip install incite-app[tray]
incite tray
```

### Python API

```python
from incite.agent import InCiteAgent

# From Zotero library
agent = InCiteAgent.from_zotero(embedder_type="minilm-ft")

# From a folder of PDFs
agent = InCiteAgent.from_folder("~/Papers")

# Get recommendations
response = agent.recommend("climate change and agricultural productivity", k=10)
for rec in response.recommendations:
    print(f"  {rec.rank}. [{rec.score:.2f}] {rec.title} ({rec.year})")
```

### REST API

```bash
incite serve --embedder minilm-ft
# API docs at http://localhost:8230/docs

curl -X POST http://localhost:8230/recommend \
  -H "Content-Type: application/json" \
  -d '{"query": "climate change impacts on crop yields", "k": 5}'
```

## Editor Plugins

inCite integrates with your writing environment via editor plugins that connect to the local API server.

| Editor | Status | Install |
|--------|--------|---------|
| **Obsidian** | Stable | Build from `editor-plugins/obsidian-incite/` |
| **VS Code** | Stable | Build from `editor-plugins/vscode-incite/` |
| **Google Docs** | Stable | Apps Script add-on via `clasp push` |
| **Microsoft Word** | Beta | Office.js add-in, sideload `manifest.xml` |

All plugins share the `@incite/shared` TypeScript package for API communication and context extraction.

## Paper Sources

- **Zotero** (recommended): Auto-detects your local Zotero library and reads directly from the SQLite database
- **PDF folder**: Point at any directory of PDFs -- metadata is extracted automatically
- **JSONL corpus**: Load a pre-built corpus file with title, abstract, authors, and other metadata

## How It Works

1. **Embed papers**: Each paper is embedded as `title. authors. year. journal. abstract` using a sentence transformer
2. **Embed your writing**: Your text is embedded with the same model
3. **Search**: FAISS finds the nearest papers by cosine similarity
4. **Fuse** (optional): BM25 keyword matching is combined with neural results via Reciprocal Rank Fusion for improved recall
5. **Evidence**: The best matching paragraph from each paper's full text is attached as supporting evidence

## Embedder Models

| Model | Key | Dims | Notes |
|-------|-----|------|-------|
| MiniLM fine-tuned v4 | `minilm-ft` | 384 | Default. Citation-specific, auto-downloads from HuggingFace |
| MiniLM | `minilm` | 384 | Fast, good baseline |
| SPECTER2 | `specter` | 768 | Scientific domain |
| Nomic v1.5 | `nomic` | 768 | Long context (8K tokens) |
| Granite | `granite` | 384 | IBM Granite, 8K context |

> For even better results (MRR 0.550 vs 0.428), try the cloud service at [inciteref.com](https://inciteref.com) which uses our best fine-tuned model.

## Development

```bash
git clone https://github.com/galenphall/incite.git
pip install -e ".[dev]"
pytest
ruff check src/incite && ruff format src/incite
```

## Optional Dependencies

inCite's core is Apache 2.0 licensed. Some optional features depend on copyleft-licensed libraries and are packaged as extras to keep the default installation permissive.

```bash
pip install incite-app[pdf]       # PyMuPDF for PDF text extraction (AGPL)
pip install incite-app[zotero]    # pyzotero for Zotero integration (GPL)
pip install incite-app[api]       # FastAPI server
pip install incite-app[webapp]    # Streamlit UI
pip install incite-app[tray]      # macOS menu bar app
pip install incite-app[all]       # Everything
```

> **Note**: The `pdf` and `zotero` extras pull in AGPL and GPL dependencies respectively. If license compatibility matters for your use case, install only the extras you need.

## Cloud Service

[inciteref.com](https://inciteref.com) offers a hosted version of inCite with additional features:

- **Better model**: Granite-FT fine-tuned embedder (MRR 0.550 vs 0.428 for the default local model)
- **Cloud PDF processing**: Full-text extraction without running GROBID locally
- **Reference manager**: Collections, tags, notes, and citation export (BibTeX/RIS)
- **Multi-device sync**: Access your library from anywhere

The local CLI and cloud service are complementary -- use whichever fits your workflow.

## Contributing

Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

[Apache License 2.0](LICENSE)

## Citation

```bibtex
@software{incite2025,
  author       = {Hall, Galen},
  title        = {inCite: Local-First Citation Recommendation},
  year         = {2025},
  url          = {https://github.com/galenphall/incite},
  license      = {Apache-2.0}
}
```
