{"_id":"@duskmoon/pi-academic-search","name":"@duskmoon/pi-academic-search","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@duskmoon/pi-academic-search","version":"0.1.0","description":"Academic paper search, lookup, citation analysis, and PDF download for Pi coding agent. Searches Semantic Scholar, OpenAlex, Crossref, arXiv, DBLP, and PubMed. Supports Unpaywall for open-access PDF discovery.","type":"module","engines":{"node":">=22.19.0"},"scripts":{"test":"node --experimental-strip-types --test","typecheck":"npx tsc --noEmit"},"keywords":["pi-package","pi","pi-coding-agent","extension","academic-search","semantic-scholar","openalex","crossref","arxiv","pubmed","dblp","unpaywall","pdf-download","academic-search"],"license":"MIT","author":{"name":"duskmoon314","email":"kp.campbell.he@duskmoon314.com"},"pi":{"extensions":["./src/index.ts"]},"dependencies":{"typebox":"^1.3.22"},"peerDependencies":{"@earendil-works/pi-ai":"*","@earendil-works/pi-coding-agent":"*"},"devDependencies":{"@types/node":"^26.4.0","typescript":"^7.0.2"},"gitHead":"ee15647ccce59b87f2e3893b22b6abc654e06b58","_id":"@duskmoon/pi-academic-search@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-amhx4uy+ykzwVtoXp5V5YMDtbrXj4RLOoV0cISv34KzvPJJeVb53koiPiJv2+b+ZevKeGBnN47T5gvUlvoHPJg==","shasum":"eee973deba8cdc01cc8a747a51984ff88a9d867f","tarball":"https://registry.npmjs.org/@duskmoon/pi-academic-search/-/pi-academic-search-0.1.0.tgz","fileCount":21,"unpackedSize":167357,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCvQD3t/xxzMyO9m/N3EnMBKtSvemXwJArbVhJr0hfWFwIhAMKeYiciOrLNwHeO9BROp3/eo2dfCUQCf11s09TkirOF"}]},"_npmUser":{"name":"duskmoon","email":"campbell.he@icloud.com"},"directories":{},"maintainers":[{"name":"duskmoon","email":"campbell.he@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-academic-search_0.1.0_1788079698532_0.547154269250002"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-30T08:48:18.265Z","0.1.0":"2026-08-30T08:48:18.671Z","modified":"2026-08-30T08:48:18.880Z"},"maintainers":[{"name":"duskmoon","email":"campbell.he@icloud.com"}],"description":"Academic paper search, lookup, citation analysis, and PDF download for Pi coding agent. Searches Semantic Scholar, OpenAlex, Crossref, arXiv, DBLP, and PubMed. Supports Unpaywall for open-access PDF discovery.","keywords":["pi-package","pi","pi-coding-agent","extension","academic-search","semantic-scholar","openalex","crossref","arxiv","pubmed","dblp","unpaywall","pdf-download","academic-search"],"author":{"name":"duskmoon314","email":"kp.campbell.he@duskmoon314.com"},"license":"MIT","readme":"# Pi Academic Search\n\n**Multi-source academic paper search, lookup, citation analysis, and PDF download for Pi coding agent.**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)\n\nSearch across Semantic Scholar, OpenAlex, Crossref, arXiv, DBLP, and PubMed — merged and deduplicated. Look up papers by DOI, arXiv ID, or PMID. Explore citation graphs. Download open-access PDFs.\n\n## Install\n\n```bash\npi install npm:@duskmoon/pi-academic-search\n```\n\nWorks immediately with no API keys — Semantic Scholar, OpenAlex, Crossref, arXiv, DBLP, and PubMed all support keyless access. Add API keys for better rate limits and Unpaywall for PDF downloads.\n\n### OpenAlex API Key\n\nOpenAlex offers a free API key (register at [openalex.org](https://openalex.org), then get your key at [openalex.org/settings/api](https://openalex.org/settings/api)). Adding an API key increases your daily request budget by 10x. The key works alongside the `mailto` polite pool mechanism — both can be configured simultaneously.\n\n## Quick Start\n\n```typescript\n// Search papers across default sources (semantic, openalex, arxiv, dblp, crossref)\nacademic_search({ query: \"transformer attention mechanism\" })\n\n// Search with arXiv category filtering\nacademic_search({ query: \"mixture of experts\", categories: [\"cs.CL\", \"cs.LG\"] })\n\n// Auto-route DOI query to direct lookup\nacademic_search({ query: \"10.1145/3292500.3330919\" })\n\n// Search specific sources with filters\nacademic_search({ query: \"quantum computing\", sources: [\"arxiv\", \"semantic\"], year: \"2023-2024\", limit: 5 })\n\n// Look up a paper by ID\npaper_lookup({ id: \"ARXIV:1706.03762\" })\npaper_lookup({ id: \"10.1145/3292500.3330919\" })\npaper_lookup({ id: \"PMID:12345678\" })\n\n// Get citations or references\npaper_citations({ id: \"ARXIV:1706.03762\", direction: \"citing\", limit: 20 })\n\n// Download an open-access PDF\ndownload_pdf({ arxivId: \"1706.03762\" })\ndownload_pdf({ doi: \"10.1145/3292500.3330919\", outputDir: \"./papers\" })\ndownload_pdf({ resultId: \"abc12345\" }) // from a previous academic_search\n```\n\n## Tools\n\n### academic_search\n\nSearch academic papers across multiple sources. Returns merged, deduplicated results. Supports ID-based auto-routing, arXiv category filtering, and abstract enrichment.\n\n```typescript\nacademic_search({\n  query: \"transformer attention mechanism\",\n  sources: [\"semantic\", \"openalex\", \"arxiv\"],  // optional, defaults to semantic, openalex, arxiv, dblp, crossref\n  limit: 10,                                    // per source, default 10, max 50\n  year: \"2020-2024\",                            // optional year filter\n  openAccessOnly: false,                        // optional OA filter\n  categories: [\"cs.CL\", \"cs.LG\"],              // optional, arXiv categories\n  enrich: true                                  // optional, fill missing abstracts (default true)\n})\n\n// ID auto-routing — pass a DOI or arXiv ID as query\nacademic_search({ query: \"10.1145/3292500.3330919\" })\nacademic_search({ query: \"2301.07041\" })\n```\n\n| Parameter | Description |\n|-----------|-------------|\n| `query` | Search query string, DOI, or arXiv ID (auto-routed to direct lookup) |\n| `sources` | Single source or array of sources. Default: semantic, openalex, arxiv, dblp, crossref |\n| `limit` | Max results per source (1–50, default 10) |\n| `year` | Year filter: `\"2023\"` or `\"2020-2024\"` range (applied post-dedupe) |\n| `openAccessOnly` | Only return open access papers |\n| `categories` | arXiv categories to restrict results (e.g. `[\"cs.CL\",\"cs.LG\"]`) |\n| `enrich` | Auto-fill missing abstracts from Semantic Scholar batch API (default true) |\n\nResults are stored with a unique ID (TTL 1 hour) for reference by other tools.\n\n### paper_lookup\n\nLook up a single paper by identifier. Returns a detailed Markdown card with abstract, metadata, and all known identifiers.\n\n```typescript\npaper_lookup({ id: \"10.1145/3292500.3330919\" })\npaper_lookup({ id: \"ARXIV:1706.03762\" })\npaper_lookup({ id: \"PMID:12345678\" })\npaper_lookup({ id: \"S2:abc123def\" })\npaper_lookup({ id: \"OPENALEX:W2741809807\" })\n```\n\nSupported ID formats:\n- **DOI**: bare `10.xxxx/yyyy`, `doi:10.xxxx/yyyy`, `https://doi.org/10.xxxx/yyyy`\n- **arXiv**: `ARXIV:2301.07041`, bare `2301.07041`, old-style `cs/0501001`\n- **PubMed**: `PMID:12345678`\n- **Semantic Scholar**: `S2:abc123def`\n- **OpenAlex**: `OPENALEX:W2741809807`\n\n### paper_citations\n\nGet citing papers or references for a paper. Only Semantic Scholar and OpenAlex support citation queries.\n\n```typescript\npaper_citations({ id: \"ARXIV:1706.03762\", direction: \"citing\" })\npaper_citations({ id: \"10.1145/3292500.3330919\", direction: \"references\", limit: 50 })\n```\n\n| Parameter | Description |\n|-----------|-------------|\n| `id` | Paper identifier (any supported format) |\n| `direction` | `\"citing\"` = papers citing this one; `\"references\"` = papers this one cites |\n| `limit` | Max results (default 20, max 100) |\n\n### download_pdf\n\nDownload an open-access PDF to local disk. ⚠️ **Writes files to the local filesystem.**\n\n```typescript\ndownload_pdf({ arxivId: \"1706.03762\" })\ndownload_pdf({ doi: \"10.1145/3292500.3330919\" })\ndownload_pdf({ resultId: \"abc12345\", outputDir: \"./papers\" })\n```\n\n| Parameter | Description |\n|-----------|-------------|\n| `doi` | Paper DOI |\n| `arxivId` | arXiv paper ID |\n| `resultId` | Result ID from a previous `academic_search` call |\n| `outputDir` | Output directory (default: `./papers`) |\n\nAt least one of `doi`, `arxivId`, or `resultId` is required.\n\n**Download chain priority:**\n1. arXiv direct link (if arXiv ID available) — highest priority\n2. Unpaywall OA lookup (if DOI available and Unpaywall email configured)\n3. Direct PDF URL from paper metadata (from search results or lookup)\n\n**Safety:** Validates response `content-type` or `%PDF` magic bytes before writing. 100 MB size limit (streaming check). 60-second download timeout. Existing files get a random suffix to avoid overwrites. All download URLs are validated against SSRF: only HTTPS allowed, DNS-resolved IPs are checked for private/loopback/link-local ranges, and redirects are followed manually with per-hop validation (max 5 hops).\n\n**Output directory boundary:** By default, `outputDir` must resolve within the current working directory. Set `downloadAllowOutsideCwd: true` in config to allow writing outside cwd. Paths starting with `~` are expanded to the home directory.\n\n## Commands\n\n### /academic-search\n\nInteractive configuration wizard for providers and credentials. Opens a selection menu to configure each provider's API key or email.\n\n## Configuration\n\nConfig file: `~/.pi/academic-search.json` (all fields optional).\n\n```json\n{\n  \"semanticScholarApiKey\": \"$S2_API_KEY\",\n  \"ncbiApiKey\": \"$NCBI_API_KEY\",\n  \"coreApiKey\": null,\n  \"crossrefMailto\": \"me@example.com\",\n  \"unpaywallEmail\": \"$UNPAYWALL_EMAIL\",\n  \"openAlexMailto\": null,\n  \"openAlexApiKey\": null,\n  \"defaultSources\": [\"semantic\", \"openalex\", \"arxiv\", \"crossref\", \"dblp\"],\n  \"defaultLimit\": 10,\n  \"downloadDir\": \"./papers\",\n  \"downloadAllowOutsideCwd\": false,\n  \"proxy\": null\n}\n```\n\nAll credential fields support:\n- **Literal**: `\"sk-xxx\"` — plain text value\n- **Environment variable**: `\"$VAR\"` or `\"${VAR}\"` — read from environment\n- **Command**: `\"!op read op://Private/...\"` — execute local command, use stdout\n- **Escape**: `\"$$...\"` or `\"$!...\"` — literal `$` or `!` prefix\n\n### Environment Variables\n\n| Variable | Used By | Purpose |\n|----------|---------|---------|\n| `S2_API_KEY` | Semantic Scholar | API key for higher rate limits |\n| `NCBI_API_KEY` | PubMed | API key for higher rate limits |\n| `CROSSREF_MAILTO` | Crossref | Email for Polite Pool access |\n| `UNPAYWALL_EMAIL` | Unpaywall | Email required for PDF download (free, 100k/day) |\n| `OPENALEX_MAILTO` | OpenAlex | Email for polite access |\n| `OPENALEX_API_KEY` | OpenAlex | API key for 10x daily request budget |\n\nWhen a config value is null/absent, the corresponding environment variable is tried automatically.\n\n## Providers\n\n| Provider | Search | Lookup | Citations | Key Required | Rate Limit (no key) |\n|----------|--------|--------|-----------|--------------|---------------------|\n| Semantic Scholar | ✓ | ✓ | ✓ | Optional | ~3s interval |\n| OpenAlex | ✓ | ✓ | ✓ | Optional (API key or mailto) | Default limits |\n| Crossref | ✓ | ✓ | — | Optional (mailto) | Default limits |\n| arXiv | ✓ | ✓ | — | No | 3s interval |\n| DBLP | ✓ | — | — | No | ~1s interval |\n| PubMed | ✓ | ✓ | — | Optional | ~350ms interval |\n\n## License\n\nMIT\n\n## Provider Health Status\n\nThe extension passively records the health status of each provider during normal use — no extra requests are made. When a provider consistently returns errors (e.g., 403 for invalid credentials, 429 for rate limiting), a brief health indicator is shown in the `/academic-search` status display.\n\n**Health indicators:**\n- `⚠ HTTP 403 (凭据可能无效) · 5分钟前` — authentication may be invalid\n- `⏳ 429 限速 · 2分钟前` — rate limited\n- `⚠ 503 服务端错误 · 1分钟前` — server-side issue\n- `⚠ 网络/超时 · 30秒前` — network connectivity issue\n\nHealth data is stored in `academic-search-health.json` alongside the config file (e.g., `~/.pi/academic-search-health.json`). It resets to healthy status once a successful request is made.\n\n## Quality Pack Features\n\n### 429 Retry with Backoff\n\nAll provider requests automatically retry on HTTP 429 (rate limit) errors. Retries respect the `Retry-After` header when present, otherwise use exponential backoff (1s → 2s → 4s, capped at 20s). External abort signals are honored during retry waits.\n\n### ID Auto-Routing\n\nPass a DOI or arXiv ID as the `query` parameter to `academic_search` and it will automatically resolve the paper via direct lookup, prepend the result to search results, and deduplicate normally.\n\n### arXiv Category Filtering\n\nUse the `categories` parameter to restrict arXiv results to specific categories (e.g., `[\"cs.CL\", \"cs.LG\"]`). Queries with existing arXiv field prefixes (ti:, au:, abs:, cat:, all:) or boolean operators (AND/OR/ANDNOT) are passed through verbatim.\n\n### Abstract Enrichment\n\nBy default (`enrich: true`), papers missing abstracts are enriched via the Semantic Scholar batch API (up to 20 papers per call, matched by DOI). Failures are silent — the original results are returned unchanged.\n\n### Source Priority\n\nWhen the same paper appears from multiple sources, the first source in the priority order determines scalar fields (title, year, abstract, etc.): Semantic Scholar > OpenAlex > arXiv > Crossref > DBLP > PubMed.\n\n### Open Access Filtering\n\nWhen `openAccessOnly` is true, OA filtering is applied uniformly at the aggregation layer after deduplication, based on the merged `isOpenAccess` field. DBLP results are always marked as `isOpenAccess: false` (the `ee` field indicates electronic edition, not open access).\n\n### Post-Dedupe Year Filter\n\nThe `year` filter is applied after deduplication on the merged results, ensuring consistent filtering regardless of which source contributed the data. Papers without a year field are preserved (not filtered out).\n","readmeFilename":"README.md","_rev":"1-1ac94814413504e241e805abdc31d287"}