# Lightkone documentation Lightkone is a reference manager for research papers that runs in your browser. Your papers, PDFs, highlights and notes live as plain files in storage you choose, and Claude can help you read, search and cite them with your own key. ## Start here - [Getting started](GETTING-STARTED.md): the first ten minutes. - [Set up with AI](SETUP-WITH-AI.md): let Claude or another assistant set up your storage, check it in the app, and give assistants the real docs. - [Moving from Paperpile](PAPERPILE.md): bring your library, folders, labels and PDFs over. - [Where your library lives](STORAGE.md): this browser, Cloudflare R2, AWS S3, MinIO, or a private GitHub repository, set up step by step. ## Using Lightkone - [Reading and highlighting](READING.md): the PDF reader, highlights, notes, Explain, and export to Obsidian. - [Finding papers](FINDING.md): search and its filters, full text, equations, chats with Claude (Ask, comparing papers, organizing chats), New on arXiv and the citation map. - [Writing with your library](WRITING.md): citations in any journal style, LaTeX and Overleaf, citations for a whole draft, a .bib for a manuscript. - [Sharing and devices](SHARING.md): several devices, shared libraries, the phone app. - [Claude and other AI apps (MCP)](MCP.md): use your library from Claude, Claude Code or any MCP app. - [The command line](CLI.md): the `lightkone` command and Python package, on the same files as the app. - [Your data](PRIVACY.md): what is processed where, and what leaves your browser. - [Questions and fixes](FAQ.md): common questions, and what to do when something goes wrong. ## Running your own - [Hosting](HOSTING.md): deploy Lightkone for yourself or a team. - [Releasing](RELEASING.md): staging, version tags, and checking a site runs a release's exact code. - [Plugins](PLUGINS.md): add storage, lookups, importers and actions. - [Architecture](ARCHITECTURE.md): how it fits together. Inside the app, the round **?** button at the top (or the **?** key) opens Help, with a short guide and a way to replay the welcome tour. For AI assistants, the whole documentation is one text file at [`/llms-full.txt`](../llms-full.txt), with a map at [`/llms.txt`](../llms.txt). --- # Getting started ## 1. Open Lightkone Open your Lightkone site in a browser. Its front page says what Lightkone is; **Open Lightkone** takes you to the app, which lives at `/app/` (for example `https://lightkone.lab.kausalflow.com/app/`). Bookmark that address to go straight to your library next time. There is no account to create. The first time, a short tour explains the basics. A new library starts with two example papers, *Attention Is All You Need* and *Transformer models: an introduction and catalog*, tagged **Example**, so there is something to read and highlight right away. **Remove them** (above the list) whenever you like. They don't come back. On a phone, use the browser menu's **Install app** (Chrome) or **Add to Home Screen** (Safari). Installed, Lightkone opens like an app, works offline with your library and recent PDFs, and appears in the share menu so you can send it papers. ## 2. Choose where your library lives At first the library is kept in this browser only. That is fine for a look around. For a library you mean to keep, open **Settings** and choose a bucket (Cloudflare R2, AWS S3, MinIO) or a private GitHub repository, so it is safe and on every device. [Where your library lives](STORAGE.md) walks through each one; **Check these settings** tests yours before you save, and [Set up with AI](SETUP-WITH-AI.md) lets an assistant do most of it. Do this before adding much: Lightkone can't yet move a whole library from the browser to other storage. ## 3. Add papers Press **Add paper** (or **+** on a phone) and paste any of these, one per line: - an arXiv id or link: `2106.09685`, `https://arxiv.org/abs/1706.03762` - a DOI or doi.org link: `10.1103/PhysRevLett.116.061102` - a publisher's page for the paper - any other web page: a blog post, a report, documentation - BibTeX entries Details come from arXiv, Crossref, Semantic Scholar and OpenAlex. Free PDFs are downloaded for you: from arXiv always, and from open-access copies once you give a contact email under **Settings → PDFs**. Have the PDF already, from a journal you subscribe to say? Use **Add files**, or drop the files anywhere on Lightkone. Each one is identified from the DOI or arXiv id printed on its first page (or by Claude reading that page, with a key), added, and filed with its PDF. A PDF for a paper already in your library is attached to it. A web page that isn't a paper is added with its own title, authors and date, and saved: its raw HTML as it came, and its main text as Markdown, with figures, links and math kept. **Read** shows the Markdown as a clean page, and **Saved page** shows the page as it was saved. A web page already in your library, from a .bib file say, has **Save page** to do the same. See [Saved web pages](READING.md#saved-web-pages). To bring a whole library, use **Import a .bib file** in the same panel. Coming from Paperpile, follow [Moving from Paperpile](PAPERPILE.md) for your PDFs and folders as well. ## 4. Read Open a paper and press **Read**. Select text to **Highlight** it, add a note, or (with a Claude key) **Explain** it. See [Reading and highlighting](READING.md). ## 5. Add a Claude key (optional) Ask, Compare papers, Explain, Summarize, Cite this's check, and finding papers from a plain description use Claude with your own key. Get one at [console.anthropic.com](https://console.anthropic.com) and paste it under **Settings → Claude → Anthropic API key**. It is kept in this browser only and sent only to Anthropic. Everything else works without it. ## Finding your way - **Ctrl K** (⌘K on a Mac), or the ⌘ button at the top, opens a command list: type to jump to any tool, folder, tag or paper. - **Ctrl J** (⌘J) starts a chat with Claude about the paper in front of you. Every chat is kept in **Chats** (the chat button at the top), gathered by paper. - The side bar holds your folders and tags, and the tools grouped as *Discover*, *Write* and *Organize*. - **?** opens Help. --- # Set up with AI Most of setting up Lightkone is setting up storage: a bucket or a repository, a key for it, and (for a bucket) a CORS rule that lets the site in. An AI assistant can do much of that for you, and Lightkone gives it what it needs. This page shows the ways, from least to most automatic. The examples use `https://lightkone.lab.kausalflow.com`. If you run your own copy, use its address instead. ## Ask any assistant, with the real docs Assistants guess when they don't know a tool. Point them at the docs instead: - **One page:** every docs page has a **Copy page for AI** button at the bottom. It copies the page as Markdown, with its address, ready to paste into Claude or any other assistant. - **All of it:** the whole documentation is one text file at [`/llms-full.txt`](https://lightkone.lab.kausalflow.com/llms-full.txt), and a short map of it at [`/llms.txt`](https://lightkone.lab.kausalflow.com/llms.txt) (the [llms.txt](https://llmstxt.org) convention). Give an assistant either link, or attach the file, and ask your question. ## Check your storage in the app In **Settings → Where your library lives**, fill in the bucket or repository, then press **Check these settings**. Lightkone looks at the settings for common slips (the bucket name inside the endpoint, a public `r2.dev` address, a wrong region), then lists the library, saves a small test file, reads it back and removes it. If a step fails, it says what is most likely wrong and how to fix it. For a CORS problem it shows the exact rule your bucket needs, with this site's address filled in. **Copy for an AI assistant** copies the whole report, with your settings but **without your keys**, so you can paste it into Claude and ask for step-by-step help. Nothing is saved until you press **Save**. ## Let Claude Code set up Cloudflare R2 If you have [Claude Code](https://claude.com/claude-code) (or another coding assistant that can run commands), it can create the bucket and its CORS rule for you with Cloudflare's `wrangler` tool. Only one step stays with you: R2 access keys can only be made in the Cloudflare dashboard. Open a terminal in an empty folder, start Claude Code, and paste this: ```text Set up Cloudflare R2 storage for Lightkone, a reference manager that keeps its library in a bucket I own. Lightkone's docs: https://lightkone.lab.kausalflow.com/llms-full.txt (read the "Cloudflare R2" section of docs/STORAGE.md first). 1. Check that Node.js is installed, then run `npx wrangler login` and wait for me to sign in in the browser. 2. Create a private bucket called `papers` (ask me if I want another name): `npx wrangler r2 bucket create papers`. Do not make it public and do not add a custom domain. 3. Write cors.json in wrangler's format and apply it with `npx wrangler r2 bucket cors set papers --file cors.json`, allowing origin https://lightkone.lab.kausalflow.com, methods GET, PUT, DELETE and HEAD, all headers, exposing ETag, max age 3600. Show me `npx wrangler r2 bucket cors list papers` afterwards. 4. Find my account id (`npx wrangler whoami`) and tell me the endpoint: https://.r2.cloudflarestorage.com 5. Tell me how to make the access key myself: R2 overview → Manage API tokens → Create API token, permission Object Read & Write, applied to the papers bucket only. Don't ask me to paste the secret into this chat. 6. Finally list what to type in Lightkone: Settings → S3 / R2 / MinIO bucket → Endpoint, Bucket, Region `auto`, Access key id, Secret access key, then Check these settings. ``` Then make the key as it describes, enter the settings in Lightkone, and press **Check these settings**. Claude on claude.ai can do the same with the Cloudflare connector, which can create R2 buckets; ask it to follow [the R2 steps](STORAGE.md#cloudflare-r2) and to tell you the dashboard steps it can't do. ## A private GitHub repository instead A repository needs no CORS rule, so the AI part is short. Ask your assistant (or Claude Code, with the GitHub CLI) to create a **private** repository, then make a fine-grained token yourself at **GitHub → Settings → Developer settings → Fine-grained tokens**, with **Contents: Read and write** on that one repository. See [Private GitHub repository](STORAGE.md#private-github-repository). ## On the command line With Python, `pipx install lightkone` then `lightkone setup` does the same as the in-app check and the CORS step together: it asks for the bucket, checks it, adds the CORS rule for the web app (given a key allowed to), and writes a settings file to import in the web app. A coding assistant can run it for you too. See [The command line](CLI.md#starting-fresh-lightkone-setup). If the web app already works, bring its settings to the command line instead: **Settings → Backup → Export settings**, then `lightkone login --from-backup `. ## Keeping keys safe - Paste keys into Lightkone's Settings, not into a chat. **Copy for an AI assistant** leaves them out on purpose. - Use a key scoped to the one bucket or repository Lightkone uses. - If a key ever ends up somewhere it shouldn't, delete it (R2: Manage API tokens; GitHub: Fine-grained tokens) and make a new one. --- # Moving from Paperpile This page takes a Paperpile library, with its folders, labels and PDFs, into Lightkone. Plan for about an hour for a library of a few thousand papers, most of it waiting for PDFs to copy. ## What comes over | In Paperpile | In Lightkone | | --- | --- | | Papers and their details (title, authors, journal, DOI, arXiv id, abstract, the rest of the BibTeX fields) | Papers, with every field kept. Unknown fields go out again unchanged in exported BibTeX. | | Citation keys | Kept as they are. Lightkone never changes a key once given, so your LaTeX keeps compiling. | | Labels | Tags. Paperpile writes labels into the BibTeX `keywords` field, and Lightkone reads that field as tags. | | Folders | Folders, with the same nesting, read from where Paperpile filed each PDF in Google Drive. | | PDFs in Google Drive | PDFs in your Lightkone storage, in the same layout and with the same file names. | | Starred papers | Not in BibTeX, so they don't come over. Star them again in Lightkone. | | Highlights and notes made in Paperpile's viewer | Not in BibTeX either. Highlights that Paperpile saved into the PDF file itself still show in any PDF viewer, but Lightkone's highlights start fresh. | A paper that sits in several of your .bib files (say, in both a "Thesis" and a "Machine learning" export) is added once. Lightkone matches papers by DOI, arXiv id and title, and merges their tags and folders. ## Before you start: pick where the library will live Decide this first, because the library is easiest to fill once in its final home. Lightkone does not yet move a whole library from one storage to another. You can export a .bib and import it elsewhere, but notes and highlights stay behind. - **This browser only** is fine for a first look, but it can't take a folder of PDFs in one go and lives on one device. Don't migrate a real library into it. - **Cloudflare R2** is what most people should pick: free up to 10 GB, fast and cheap beyond that, and it works on every device. - **AWS S3 or MinIO** are the same as R2 from Lightkone's side. Use them if your institution already runs them. - **A private GitHub repository** keeps the full history of every change and is the easiest way to keep Paperpile and Lightkone in step while you switch (see [Keeping Paperpile running while you switch](#keeping-paperpile-running-while-you-switch)). GitHub limits file and repository size, though, so a large PDF collection is better in a bucket. [Where your library lives](STORAGE.md) explains each one and how to set it up. Set your storage up and save it in **Settings** before step 3. ## Step 1: get your BibTeX out of Paperpile Pick one of these two ways. **Once, by hand.** In Paperpile, select all papers (open *All Papers*, then press Ctrl+A or ⌘A), then **Export → BibTeX**, and save the .bib file. A large library may take a moment to export. **Kept up to date, automatically.** Click your profile picture at the top right → **Workflows and Integrations** → add a **BibTeX Export** workflow. Choose what to export (your whole library, a folder or a label), then where to: Google Drive, GitHub, or a link. Choose **GitHub** if you will use GitHub storage (see the section on keeping Paperpile running). You can add several workflows, for example one per top-level folder. Lightkone merges them. Check **Settings → BibTeX** in Paperpile, because it decides what the export contains. Keep abstracts in: Lightkone uses them for search, Ask and New on arXiv. The `file` field must be included, because it is how each paper finds its PDF and its folders. A line like this in the .bib means it is all there: ```bibtex file = {All Papers/MLDS/MachineLearning/Wager et al. 2013 - Dropout training as adaptive regularization.pdf}, ``` ## Step 2: understand where Paperpile keeps your PDFs Paperpile stores PDFs in your Google Drive, in **My Drive → Paperpile → All Papers**. Inside it: - papers you never put in a folder are filed by first author, in a folder named after the surname in capitals: `All Papers/HINTON/Hinton et al. 2012 - Improving neural networks ....pdf`; - papers in your own folders sit in those folders: `All Papers/MLDS/MachineLearning/NeuralNetworks/....pdf`. Lightkone keeps PDFs in exactly the same layout, under a folder called `papers/` in your storage: `papers/All Papers/HINTON/...`. So the move is one copy of the *All Papers* folder, with nothing renamed, and each paper's `file` field then points at its PDF. Lightkone makes folders from the second kind of path (`MLDS/MachineLearning/NeuralNetworks`) and skips the author folders, which are not folders you made. The sibling folders *Starred Papers* and *Trashed Papers* in Drive hold only shortcuts and old files. Don't copy them. If Paperpile's Drive sync is off (**Settings → Data and files** in Paperpile), turn it on and wait for it to finish first. Otherwise the folder in Drive is incomplete. ## Step 3: copy the PDFs into your storage The steps depend on your storage. For buckets, the tool is [rclone](https://rclone.org/install/), which copies between Google Drive and almost anything else, can be stopped and resumed, and only copies what is new when run again. Install it on your computer first. ### Cloudflare R2 1. Set up the bucket, token and CORS rule as in [Cloudflare R2](STORAGE.md#cloudflare-r2), and save it in Lightkone's Settings. 2. Connect rclone to Google Drive (read-only) and to R2. The first command opens a browser to sign in to Google. ```sh rclone config create gdrive drive scope drive.readonly rclone config create r2 s3 provider Cloudflare \ access_key_id secret_access_key \ endpoint https://.r2.cloudflarestorage.com ``` 3. Check that rclone sees the folder, then copy it: ```sh rclone lsd "gdrive:Paperpile" rclone copy "gdrive:Paperpile/All Papers" "r2:/papers/All Papers" --progress --transfers 8 ``` If you set **Folder in the bucket** in Lightkone's settings (say `lab`), put it in front: `r2:/lab/papers/All Papers`. 4. Check the result: `rclone size "r2:/papers"` should be close to `rclone size "gdrive:Paperpile/All Papers"`. ### AWS S3 The same as R2, with an AWS remote. Use an IAM user whose policy allows `s3:ListBucket`, `s3:GetObject`, `s3:PutObject` and `s3:DeleteObject` on this bucket only. ```sh rclone config create s3 s3 provider AWS \ access_key_id secret_access_key region rclone copy "gdrive:Paperpile/All Papers" "s3:/papers/All Papers" --progress --transfers 8 ``` If you already use the AWS CLI, `aws s3 sync "" "s3:///papers/All Papers"` does the same from a local copy. ### MinIO or another S3-compatible store ```sh rclone config create minio s3 provider Minio \ access_key_id secret_access_key endpoint https://minio.example.org rclone copy "gdrive:Paperpile/All Papers" "minio:/papers/All Papers" --progress --transfers 8 ``` ### A private GitHub repository GitHub has no direct copy from Drive, so the PDFs go through your computer: 1. Download *All Papers*. Either use `rclone copy "gdrive:Paperpile/All Papers" "./All Papers"`, or right-click the folder at drive.google.com and choose **Download**. Drive sends large folders as several zip files; unzip them all into one *All Papers* folder. 2. Clone the repository you set up for Lightkone, and move the folder in: ```sh git clone https://github.com//.git mkdir -p /papers mv "All Papers" /papers/ cd git add papers git commit -m "PDFs from Paperpile" git push ``` Mind GitHub's limits. A single file over 100 MB is refused, so find any with `find "papers/All Papers" -size +100M` and leave them out. GitHub also asks that repositories stay under about 5 GB, so check yours with `du -sh "papers"` before pushing. If it is larger, a bucket is the better home for the PDFs. ### This browser only A browser library can't take a folder of PDFs. Lightkone downloads free copies itself (arXiv, and open-access papers through Unpaywall once you give a contact email under **Settings → PDFs**), and **Attach PDF** on a paper adds one file at a time. For a real library, choose one of the storages above. ## Step 4: import the BibTeX 1. Open Lightkone, with your storage chosen and saved in Settings. 2. **Add paper → Import a .bib file**, and pick the file from step 1. Repeat for each .bib if you have several. 3. Reload the page. Lightkone then looks through `papers/` and links each paper to its PDF. The status line at the bottom of the side bar says how many PDFs were linked and how many papers were put in folders. Linking matches the `file` path first, then the file name alone, so a PDF still links when it sits one folder off. A big import doesn't fetch free PDFs on its own (only imports of up to 20 papers do). Use **Get free PDF** on any paper still missing one. ## Step 5: check and tidy - Search `no:pdf` to list the papers whose PDF didn't link, and use **Attach PDF** or **Get free PDF** on them. Usually these are papers that never had a PDF in Paperpile, or whose file was renamed in Drive. - Open a few folders in the side bar and compare them with Paperpile. - Run **Tidy up** once. It finds the same paper entered twice and arXiv preprints that have since been published, and fills in the journal, DOI and pages, keeping your citation keys. - When the list says some PDFs haven't been read for search yet, press **Read them now**, so full-text search and Ask can see inside your papers. Papers you open in the reader are read on their own. ## Keeping Paperpile running while you switch If you use GitHub storage, Lightkone can pick up what you still add in Paperpile: 1. In Paperpile, add a **BibTeX Export** workflow to GitHub, writing into a folder of your Lightkone repository, for example `paperpile/`. 2. In Lightkone's **Settings**, under *Private GitHub repository*, set **Folder of .bib files to import** to `paperpile`. Each time Lightkone opens, it reads every .bib in that folder and adds the papers it doesn't have yet. Papers already there are left as you changed them in Lightkone. PDFs Paperpile files after the first copy can be copied again the same way: rclone, or git, only adds what is new. With a bucket, re-import the newest export from time to time, and re-run the same `rclone copy`. Both only add what is new. ## After the move - Overleaf and LaTeX: see [Writing with your library](WRITING.md) for keeping a .bib in a GitHub repository in step with a folder, the way Paperpile's Overleaf export did. - Other devices: open the same Lightkone site there and use **Settings → Backup → Export settings** here and **Import settings** there (with a passphrase, the file is encrypted), so you don't type keys twice. - Lightkone doesn't touch your Google Drive or Paperpile. Keep them as long as you like. --- # Where your library lives Lightkone has no server of its own for your papers. The library is a few plain files in storage you choose, and the app reads and writes them straight from your browser. Choose it in **Settings → Where your library lives**. ## What is stored Whatever the storage, the files are the same: | Path | What it is | | --- | --- | | `lightkone/library.json` | Every paper, with its folders, tags, notes and stars. | | `lightkone/library.bib` | The whole library as BibTeX, rewritten on every save, for LaTeX. | | `lightkone/annotations/.json` | Highlights and their notes, one file per paper. | | `lightkone/text/`, `citations.json`, `equations.json`, `new-on-arxiv.json` | Work done once and kept (PDF text for search, the citation map, equations) so it isn't done again. Safe to delete; it is rebuilt. | | `lightkone/branches/` | The last result of each branch (your whole library, a folder, or a set of roots), so opening it again shows it at once. Safe to delete; it is fetched again. | | `lightkone/branches.json` | Branches you kept in the citation map: their root papers and the works already shown, so the next visit can mark what is fresh. | | `papers/All Papers/...` | The PDFs, filed by first author like Paperpile (`papers/All Papers/HINTON/Hinton et al. 2012 - ....pdf`). | These are open formats (JSON, BibTeX, PDF), so you can back them up, read them with other tools, or leave with them. ### The citation files Other tools may read and write these too, so their shapes stay stable. `lightkone/citations.json` holds each paper's references from OpenAlex: ```json { "format": "lightkone-citations", "version": 1, "works": { "": { "oa": "W2741809807", "year": 2017, "cited": 120000, "refs": ["W1522301498", "..."], "at": "2026-11-10T02:00:00Z" } } } ``` `oa` is `null` when OpenAlex didn't know the paper at `at`; it is asked again after a month. `lightkone/branches.json` holds kept branches. A branch with a `scope` takes its roots from the library (all of it, or one folder) each time, and stores none: ```json { "format": "lightkone-branches", "version": 1, "items": [{ "id": "x…", "name": "Attention Is All You Need", "scope": "library | folder:, or absent", "roots": [{ "oa": "W2626778328", "title": "…", "paperId": "" }], "seen": ["W…"], "made": "2026-11-10T02:00:00Z", "looked": "2026-11-10T02:00:00Z" }] } ``` ## Choosing | | This browser only | Cloudflare R2 | AWS S3 / MinIO | Private GitHub repository | | --- | --- | --- | --- | --- | | Setup | None | About 10 minutes | About 15 minutes | About 5 minutes | | Cost | Free | Free up to 10 GB, then about $0.015 per GB a month | Your AWS or MinIO costs | Free | | Several devices | No | Yes | Yes | Yes | | History of changes | No | No (turn on bucket versioning if you want it) | With bucket versioning | Every save is a commit | | Large PDF collections | Limited by the browser | Yes | Yes | Up to about 5 GB; no file over 100 MB | | Claude and other AI apps (MCP) | No | Yes | Yes | Yes | | Shared with a group | No | Yes, by handing out keys | Yes, by handing out keys | Yes, through the repository's collaborators | If unsure, pick **Cloudflare R2**. Credentials stay in the browser that uses them. They are never written into the library or sent to Lightkone's site. Give each key or token access to one bucket or one repository only. ## This browser only No setup. The library lives in this browser's storage (IndexedDB) on this device. It is the default, and it is good for trying Lightkone out. Keep in mind: - It doesn't sync. Another browser or device sees a different, empty library. - Clearing the site's data in the browser deletes the library. Lightkone asks the browser to keep its data (Settings says whether it agreed), but don't keep your only copy here. - PDFs come in one at a time: downloaded free copies, or **Attach PDF**. Lightkone can't yet move a whole library out of this browser into a bucket or repository. **Export .bib** (the download button at the top) takes the papers, tags and citation keys, but not notes or highlights. So choose your real storage before you put much work in. ## Cloudflare R2 R2 is Cloudflare's object storage: S3-compatible, with no fees for downloads. 1. **Create the bucket.** At [dash.cloudflare.com](https://dash.cloudflare.com), open **R2 Object Storage** (the first time, R2 asks you to add a payment method, even for the free tier) → **Create bucket**. Name it, for example `papers`. Leave the location on Automatic. 2. **Make a key for it.** On the R2 overview page, open **Manage API tokens** → **Create API token**: - Permissions: **Object Read & Write**. - Specify bucket(s): **Apply to specific buckets only**, and pick `papers`. - Create it, then copy the **Access Key ID** and the **Secret Access Key**. The secret is shown only once. - Note the endpoint under *Use jurisdiction-specific endpoints for S3 clients*: `https://.r2.cloudflarestorage.com`. 3. **Let the browser in (CORS).** Open the bucket → **Settings** → **CORS Policy** → **Add CORS policy**, and paste this, with your Lightkone site's address: ```json [ { "AllowedOrigins": ["https://lightkone.lab.kausalflow.com"], "AllowedMethods": ["GET", "PUT", "DELETE", "HEAD"], "AllowedHeaders": ["*"], "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3600 } ] ``` Add `"http://localhost:5173"` to *AllowedOrigins* if you also run Lightkone locally. 4. **Tell Lightkone.** In **Settings**, choose **S3 / R2 / MinIO bucket**: - Endpoint: `https://.r2.cloudflarestorage.com` - Bucket: `papers` - Region: `auto` - Folder in the bucket: leave empty, or a name such as `lab` to keep several libraries in one bucket - Access key id and Secret access key from step 2 Then **Save**. The side bar's status line shows the bucket once it opens. Don't make the bucket public, and don't add a custom domain to it. Lightkone signs each request with your key, so the bucket stays private. ## AWS S3 1. In the S3 console, **Create bucket**. Keep *Block all public access* on. 2. In IAM, create a user for Lightkone with this policy (replace `papers`), then create an access key for it (**Security credentials → Create access key**, use case *Application running outside AWS*): ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::papers" }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::papers/*" } ] } ``` 3. Open the bucket → **Permissions** → **Cross-origin resource sharing (CORS)** → **Edit**, and paste the same JSON as for R2 above. 4. In Lightkone's **Settings**, choose **S3 / R2 / MinIO bucket**. The endpoint is the regional one, `https://s3..amazonaws.com` (for example `https://s3.eu-central-1.amazonaws.com`), the region is that region, and the key is the one from step 2. ## MinIO and other S3-compatible stores Anything that speaks the S3 API and allows CORS works the same way (MinIO, Ceph, Wasabi, Backblaze B2's S3 API, and others). - Endpoint: the server's address, for example `https://minio.example.org`. - Region: whatever the server is configured with, often `us-east-1`. - Create a user or service account limited to the one bucket. - CORS: MinIO allows browser requests from any origin by default. If yours is locked down, set `MINIO_API_CORS_ALLOW_ORIGIN` to your Lightkone site's address. Other stores use the same CORS JSON as above. The store must be reachable over HTTPS from the browsers that use it. ## Private GitHub repository 1. **Create a private repository**, for example `papers`, on GitHub. It may be empty. 2. **Make a token** at **GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token**: - Repository access: **Only select repositories**, and pick that one. - Permissions → Repository permissions → **Contents: Read and write**. (Metadata: Read-only is added by itself.) - Pick an expiry you are comfortable with, and note when it runs out. Lightkone stops saving when the token expires. 3. In Lightkone's **Settings**, choose **Private GitHub repository**: - Repository: `owner/name` - Token: the one from step 2 - Branch: leave empty for the default branch - Folder of .bib files to import: optional. Lightkone reads every .bib in that folder each time it opens and adds the papers it lacks. Point Paperpile's or Zotero's BibTeX export there to keep them in step (see [Moving from Paperpile](PAPERPILE.md#keeping-paperpile-running-while-you-switch)). - GitHub Enterprise API URL: only for GitHub Enterprise Server, for example `https://github.example.com/api/v3`. Each save is a commit, so the repository holds the full history of your library, and you can see or undo any change on GitHub. Mind GitHub's limits: no file over 100 MB, and repositories are best kept under about 5 GB, so very large PDF collections are better in a bucket. ## Using several devices With a bucket or a repository, open the same Lightkone site on any device and enter the same storage settings. To avoid typing keys again, use **Settings → Backup → Export settings** on one device and **Import settings** on the other. With a passphrase, the file is encrypted. Before each save, Lightkone reads the library again and merges what was saved elsewhere (by another device, or by Claude through MCP) instead of overwriting it. It also picks up changes when you come back to its tab. If two devices change the same field of the same paper, the later save wins. The installed app (Install app, from the browser menu) also keeps an offline copy of the library and the last 25 PDFs you opened. To take more on a trip, choose them under **Offline papers** (see [Reading offline](READING.md#reading-offline)). ## Troubleshooting First press **Check these settings** under the storage settings: it tries the storage and says what is wrong. [Set up with AI](SETUP-WITH-AI.md) has more. - **"Could not open … Failed to fetch"** with a bucket almost always means CORS: the bucket's rule doesn't list the site's exact address (scheme and host, no trailing slash). - **403 or "Access Denied"**: the key can't reach that bucket, or the bucket name or endpoint is wrong. For R2, check the token is scoped to this bucket with *Object Read & Write*. - **GitHub 404**: the token can't see the repository. Fine-grained tokens see only the repositories picked when they were made. - **GitHub 401**: the token has expired or was revoked. Make a new one and replace it in Settings. --- # Reading and highlighting ## The reader Open a paper and press **Read**. The PDF opens full screen, on a phone too. Pinch or use **Zoom in / Zoom out**, and the moon button turns the pages dark for reading at night. **Esc** or the close button returns to the list, where Lightkone remembers the paper in **Recently read**. No PDF yet? **Get free PDF** looks for an open copy (arXiv, or Unpaywall with a contact email set under **Settings → PDFs**). **Attach PDF** adds a file of your own, and **Replace PDF** swaps it. ## Highlights and notes Select text on a page, then tap: - **Highlight** to mark it; - **Highlight + note** to mark it and write a note; - **Explain** (with a Claude key) to have Claude explain the passage, or an equation, in the context of the whole paper. It sees the PDF and a picture of what you selected, takes follow-up questions, and can keep its answer as the highlight's note. - **Ask** (with a Claude key) to put the passage in the chat beside the page, and ask your own question about it. Tap a highlight to edit its note or delete it. Highlights are saved in `lightkone/annotations/.json` next to the library, as plain JSON, and never inside the PDF, so the file stays as published. ## The side panel: notes and chat The panel button at the top right of the reader opens a side panel beside the pages, with two tabs: - **Notes** lists your highlights and their notes. Tap one to go to it; **Export as Markdown** or **Copy** takes them all. - **Chat** is one running conversation with Claude about the paper you are reading. It picks up where you left it, on any device: Claude sees the whole conversation so far with each question. **Summarize** (in the paper's details) and **Explain** on a passage add to the same running chat instead of starting new ones, and **Ask** on a selected passage asks in it. **+** starts a separate chat when you want a clean start, which then becomes the running one; the list at the top switches between the paper's chats, and the pencil names the chat shown (or the new one, before you ask). When a chat grows long (20 messages, or about 15,000 tokens of text sent again with each question), a note above the question box offers a new chat; **Keep going** hides it for that chat. Claude reads the whole PDF with each question. Every chat is kept with your other chats, under the paper, in `lightkone/history/` (in a shared or read-only library, in this browser only). The chat button opens it in Chats, to rename, pin or bring in other papers. ### Sources highlighted on the paper Claude backs each answer with quotes from the PDF: the passages that answer your question and those that help you follow the answer, such as a definition, an assumption, a step or an equation, several when several parts of the paper matter. Each quoted passage is highlighted in blue right on the page, with a numbered marker in the margin beside it, and the answer shows the same numbers with the page, like **2 · p. 3**. Tap a number in the answer, or a marker on the page, to go to that passage. The **Sources** bar over the page steps through them: **‹** and **›** go to the previous and next passage, and **Back** returns to where you were reading before the first jump. **×** hides the highlights (tapping a number in the answer shows them again). A passage whose words can't be found on the page (rare, for some scanned or unusual PDFs) is counted as "not marked", and its number opens its page instead. The highlights are those of the latest answer in the chat shown, and come back when you reopen the chat; they are not saved as your own highlights. A saved answer keeps its quotes, so it shows the same highlights without asking Claude again. Numbers in Ask and in Chats that point to a page of a paper open the paper there with the quoted passage highlighted the same way. On a computer, the three panel buttons next to the close button snap the panel to the right, the left or below the page, and the fourth floats it over the page as a window: drag its tab bar to move it and its bottom-right corner to resize it. Drag its inner edge to make it wider or narrower (taller or shorter below the page), or focus the edge and use the arrow keys; Lightkone remembers the place and the size for each place, and a double-click on the edge goes back to the default size. On a phone the panel is a sheet over the lower half of the screen, so the page stays in view above it; the arrow makes it taller, and it moves up above the keyboard while you type. Lightkone remembers which tab you used last. The paper's own **Notes** box (in its details) is for notes on the whole paper. It is searched along with everything else. ## Summaries and questions With a Claude key, a paper's details offer **Summarize** and **Chat about it**, which opens a chat about the paper in Chats. **Ctrl J** (⌘J) does the same while you read. When the PDF is stored, Claude reads the whole paper, not only the abstract. To ask across your whole library, use **Ask** at the top (see [Finding papers](FINDING.md#chats-with-claude)). ## Export to Obsidian **Export to Obsidian** (side bar, *Write*) writes one Markdown note per paper, named `@.md`, with the paper's details, your highlights (each linking back to its page in Lightkone) and your notes. Choose your vault's folder to write the notes straight into it (Chrome and Edge), or download a .zip. Run it again later and only the part above the marker line is rewritten: what you wrote below it in Obsidian is kept. ## Reading offline Lightkone keeps the last 25 PDFs you opened on the device. To choose more, for a flight or a trip, open **Offline papers** (in the side bar under Organize, or Ctrl/⌘K): - Pick a folder, tag, **Starred** or a search first. The panel then offers to keep every paper in that view, and downloads their PDFs, highlights and notes. - On a single paper, press **Keep offline** in its details. - Kept papers show an **Offline** label in the list. They stay until you remove them, one by one or with **Remove all**. - They take space on the device, within a limit you set (500 MB to 10 GB, 2 GB at first). Keeping stops at the limit and says so. For the app itself to open with no network, install it (**Install app** or **Add to Home Screen**) and open it once before you go. With no network you can read, highlight and take notes. Highlights are kept on the device and saved to your storage when you are back online, even if the app was closed meanwhile. Other changes (notes, stars, folders) are saved when you are back online as long as Lightkone stays open; the top bar says *Offline, saves later* until then. This applies to a bucket or a GitHub repository. With **This browser only**, everything is on the device already. ## Saved web pages A web page added to the library (a blog post, a report, documentation) is saved twice: its raw HTML exactly as it was fetched, and its main text as Markdown, found with Mozilla's Readability, the engine behind Firefox's reader view. **Read** opens the Markdown as a quiet column of text, with headings, figures, links, tables and math. **Saved page** shows the HTML as it was saved, in a sandbox where none of the page's scripts run. The moon gives a dark page, and the toolbar copies or downloads the Markdown, ready for Obsidian. The Markdown is also the page's text for search, Ask and the MCP server, as a PDF's text is. Pages that build their content with scripts after they load may save little text; their raw HTML is kept all the same. Figures stay on the site they came from, so they show only with a network. Some sites turn away anything that isn't a person's browser, often with a "checking your browser" page. Lightkone then reads the page through Jina Reader (r.jina.ai), which opens it in a real browser; only the page's address goes there, and **Settings → DOI, arXiv and BibTeX** switches it off. When that fails too, the page is still added, as a link with a title made from its address, and Add papers says why no copy was kept. To keep a copy, open the page in your browser and use the **+ Lightkone** bookmark (set it up once from Add papers; on a phone, tap the address bar, type Lightkone and tap the bookmark): the page as you see it goes to Lightkone and is saved. Or save the page in the browser (in Chrome on a phone, menu, then Download, an .mhtml file; on a computer, Save page as) and use **Add files** in Add papers, drop the file on Lightkone, or share it to the installed app. The saved page gets the same Markdown copy and reader as a fetched one, and a page already in the library as a link gets the copy. ## Reading at night With the Dullaby plugin set up (**Settings → Dullaby night reading**), **Read at night** opens a paper in Dullaby's calm, dim reading mode. The link that opens Dullaby also carries your library, as identifiers only: arXiv ids (or DOIs), newest first, marked when starred or never opened. No titles, authors, notes or PDFs. It travels in the part of the address after `#`, which browsers never send to a server, and Dullaby keeps it on that device. Dullaby then looks for new papers near yours and skips the ones you already have. **Read at night in Dullaby** under Discover opens it with the library and no paper, to bring it up to date. Papers you **Keep** in Dullaby come back here: its list of kept papers has **Add to Lightkone**, which opens this app's Add panel with the paper. --- # Finding papers ## Search The search box at the top (or **/**) matches words in titles, authors, abstracts, venues, tags, notes, citation keys, DOIs and arXiv ids. Every word must match, and title and author matches come first. Filters can be mixed with words: | Filter | Finds | | --- | --- | | `author:hinton` | Papers with that author | | `year:2019`, `year:2015-2020` | Papers from that year or range | | `tag:"machine learning"` | Papers with that tag (quotes for spaces) | | `in:"Physics/QFT"` | Papers in that folder and the folders inside it | | `has:pdf`, `has:abstract`, `has:arxiv`, `has:notes` | Papers that have one | | `no:pdf`, `no:abstract`, `no:arxiv`, `no:notes` | Papers that lack one (`no:pdf`: no PDF in your storage yet) | | `is:starred`, `is:recent`, `is:trash` | Starred, recently read, or in the trash | ## Inside the PDFs Once a paper's PDF has been read for search, its words are found too, with the page they are on. Papers you open are read on their own. For the rest, the list says how many haven't been read yet and offers **Read them now**. The text is kept in `lightkone/text/` next to the library, so this happens once per paper. ## Search by equation **Search by equation** (side bar, *Discover*) finds the papers in your library that use a formula. Type it in LaTeX, for example `E = mc^2` or `\nabla \cdot B = 0`. Lightkone reads the displayed equations from the LaTeX source that arXiv keeps for each paper, so it covers papers with an arXiv id, and it finds an equation however it was spaced, braced or named. Reading the equations of a large library takes a while the first time (arXiv asks to be read gently). It can be stopped and picked up later. ## Chats with Claude Everything you ask Claude happens in **Chats** and is kept there. That covers questions to your library, chats about one paper, explanations from the reader, comparison tables, agree-or-disagree weighings and review drafts. Open it with the chat button at the top or **Ask**. **Ctrl J** (⌘J on a Mac) starts a chat about the paper you are reading or have selected, from anywhere, the reader included. A paper's **Chat about it** does the same. A new chat has four kinds, as tabs: **Ask**, **Table**, **Agree or disagree** and **Review draft**. Each needs a Claude key. ### Ask About one paper, Claude reads the whole PDF (or the abstract, without one) with each question. **Choose other papers** asks more of the library instead. Then Lightkone picks the passages that bear on the question from the papers' full text and abstracts, and Claude answers citing them as [1], [2]: tap one to open that paper at that page. **Name a paper with [[.** In any chat, type `[[` and a few letters of a title, author, year or citation key, then pick the paper (arrows and Enter, or tap). It goes in as `[[key]]`, as in Obsidian, and shows as the paper's short name. A paper named this way is always read for that question: its passages that bear on the question, or its title and abstract when none do. In a chat about one paper, this brings other papers in beside it, and the chat is then listed under that set of papers. **Which papers.** Above the question, a chat starts with one paper: the one open, or the one you opened last. Add more one at a time: type a few letters of a title, author, year or key in **Add a paper** and pick it. Tap the × on a paper to take it out. There is no "whole library" and no "select all": Claude reads every paper you add with your own API key, so you choose what it costs. **Whole PDFs for a few papers.** When the papers you add are five or fewer and all have their PDF in Lightkone, **Read the whole PDFs** (on by default) sends the PDFs themselves to Claude instead of passages, so the answer can use figures, tables and equations as well as the text. Claude's citations say which page each point comes from; they show as [1], [2] and open the paper at that page with the quoted passage highlighted. The first question pays for the PDFs; follow-ups within a few minutes reuse them from Anthropic's cache. A paper named with `[[` later in the chat is added as its PDF while there is room for five, then as its title and abstract. Untick the box to ask from passages, which costs less. When the PDFs are too big for one request (about 22 MB together, or more than 600 pages), Ask says so and hands the question back to ask from passages. Instead of your papers, **Published papers beyond your library** has OpenAlex search all published work for the words of your question, and Claude answers from the abstracts of what it finds, saying that it has only abstracts. Each paper it cites has an **Add** button, or says it is already in your library. ### Compare papers The other three tabs work on papers chosen the same way, one at a time, starting from one. A table takes up to 50 and a review draft up to 60. Once there is a result, the choice folds into one line above it, so the result is in view; tap the line to change it. - **Table**: name the columns, for example *Method, Data, Main result, Error bars, Assumptions*. Claude reads each paper and fills its row, three papers at a time, with the page each value is on: tap **p. 3** to open it there, and hover a value for the sentence it is read from. On a phone, or in a narrow panel, each paper is a card with its values one under another. A paper whose PDF has not been read for search is read from its abstract, and its row says so. Values the paper does not state say *not stated*. Download the table as **CSV**, or copy it as a **LaTeX table** citing each paper by its key. Up to 50 papers at a time. - **Agree or disagree**: state a claim. Lightkone finds the passages in the chosen papers that bear on it, and Claude sorts them into what **supports**, **qualifies** or **contradicts** it, each point linked to its page, with a short summary of where and why the papers differ. Papers with nothing on the claim are listed apart. Copy the result as Markdown. - **Review draft**: Claude drafts a literature review of the chosen papers from their abstracts and your notes, grouped by theme, in **LaTeX** with `\cite{key}` or **Markdown** with `[@key]`, using your citation keys so it compiles against your .bib. Say what the review is for to focus it. Lightkone flags any key that is not among the chosen papers, and downloads a .bib of the papers the draft cites. Read it as a first draft and check every claim against the papers. Up to 60 papers. ### Finding and organizing chats The list gathers chats **by papers**: each paper, or each set of papers asked about together, is a group, and so are the whole library and beyond it. The same set in any order is one group. The **+** on a group starts a new chat about those papers. The list can gather **by date** or **by topic** instead. It can be narrowed to one kind, or one topic, and searched by words in a chat's name, question, answer or topics. Open a chat to read it again with its citations linked, and carry on where it stopped. Above it: - the pencil **renames** it (or double-click the title); - the pin keeps it at the top of the list, under **Pinned**; - **+ Topic** labels it, for example *RG flow* or *thesis ch. 3*, so chats on one subject gather together whatever papers they were about; - copy it as **Markdown** for your notes, with its citations named, or delete it on every device; - **Table of these papers**, **Where they agree** and **Ask about these** take its papers on to a new chat of another kind. A paper's details list its **chats about this paper**. A Summarize result there has **Follow up in Chats**. An Ask answer that cites several papers offers to compare them in a table. On a wide screen, the chat button's panel shows the list and the open chat side by side. It can be snapped to the right or the bottom edge with the buttons in its top corner. It then stays open beside the list and the reader, so a long chat or a table can be worked on while you read. Drag its inner edge to resize it; the size and the place are remembered. The window button puts it back over the app. On a phone it opens as a sheet. Chats live in your own storage, at `lightkone/history/`, one file per chat, so they follow you across devices. In a shared library, or one you can only read, your chats stay in this browser instead, so the group never sees your questions. ### Claude's answers are kept and reused Every answer Claude gives (a summary, an explanation, an answer in Ask or a paper chat, a row of a comparison table, a weighing, a review draft, the checks in Cite this and Cite my draft, the sorting in How others cite this) is kept in your storage, in `lightkone/answers/`, under a fingerprint of exactly what was sent: the model, the instructions, the question, and the passages or PDF. Ask for the same thing again, on any device, and the saved answer shows at once with a note saying when it was given; Claude is not asked, so it costs nothing. Anything that changes the request (a new PDF, other passages found, another model, other columns) asks Claude afresh. **Summarize again**, **Explain again**, **Ask again**, **Fill again**, **Weigh again** or **Write again** under a saved answer asks Claude anew and keeps the new answer in its place. In a shared or read-only library, answers are kept in this browser only, like your chats. ## New on arXiv **New on arXiv** ranks each day's preprints, in the categories your library is in (or the ones you choose), by how close they are to your papers. Keywords you give lift preprints that mention them. Add the good ones with one tap. ## The citation map **Citation map** grows your library through its citation network, and shows which of your papers cite which. Like Chats, it opens as a window and can be snapped to the right or bottom edge to stay beside your papers and the reader, or fill the whole window; the buttons by the close button move it, and it comes back where you left it. ### Branches **Branches** (the first tab) grows your library, the way Kirsche connected references. It starts from **your whole library**: the works that cite two or more of your papers, and the works two or more of your papers cite, ranked by how many of your papers each ties to and how many references it shares with them. Pick a **folder** instead to grow one topic, or **papers I pick** to start from a few *roots*: from a paper's details or a paper on the map (**Branch out from this**), from a work on the *Missing* tab (**Branch out**), or by typing a paper's name. For each work it reads - the works that cite the roots (the most cited first), - the works the roots cite, and - how many references it shares with the roots and how many of your own papers it cites or is cited by, and says why it is there ("cites 4 of your papers · shares 14 references"). Your library's references come from what the citation map already looked up, so growing the whole library asks OpenAlex only for what cites your papers (50 papers a request) and for the works found. The picture places roots, papers you have (filled) and papers you don't (hollow) by year and citations, linked where one cites another. Filter by words or years, order by ties, citations or date, hide the ones you have, **Add** one to your library, or **+ Root** to start a branch from it. Each branch's last result is kept in your storage (`lightkone/branches/`), so opening it again, on any device, shows it at once. It is read afresh when it is a week old, or when you press **Refresh**. **Keep this branch** saves it, with every work shown so far. Opening it again reads the network afresh (a library or folder branch includes papers added since) and marks what is **fresh**, so a kept branch doubles as a watch on your field. ### Map The *Map* tab draws your papers, linked where one cites another. Tap a paper to see what it cites and what cites it. Two layouts: - **By folder**: one band per folder (or tag), across by year, higher the more a paper is cited. - **Year × citations**: every paper on one plane, across by year and up by citations on a log scale; bigger dots have more links inside your library. The download button saves the map, or a branch's picture, as an SVG, styled as you see it, for slides, a thesis or a web page. ### Missing from your library The *Missing* tab lists the works several of your papers cite that your library doesn't have yet, most cited first. A paper's details also offer **Find related** for its neighbours by similarity. ## How others cite this **How others cite this**, in a paper's details, shows the sentences in later papers that cite it, from Semantic Scholar's citation contexts. It reads the newest thousand citing papers, puts those that come with their sentences first, and among them the ones Semantic Scholar marks as influential and the most cited. Filters pick out those Semantic Scholar tags as citing it for a result, a method or background, and those already in your library; any other can be added in one tap. With a Claude key, **Sort with Claude** reads sixty at a time and marks each as *supporting* (confirms it, reproduces it, builds on it), *contrasting* (disputes it, finds a different value, points out a limitation) or *mentioning*, with a few words on what the citing paper does with it. Contrasting ones come first. Claude judges from the citing sentence alone, so open the paper before leaning on a verdict. The sorting is kept in your storage (`lightkone/citedby/`), so it shows again the next time, on any device, and only citations that arrived since are left to sort. Semantic Scholar limits anonymous use; a free key under **Settings → Related papers** makes it steadier. ## Organizing - **Folders** sit in the side bar and can nest (`Physics/QFT`). A paper can be in several. Put a paper in a folder from its details. - **Tags** are listed under the folders. Big imported libraries show the most used first. - **Star** the papers you come back to. - **Trash**: deleted papers wait there until you restore them or empty it. - **Tidy up** finds the same paper entered twice (same DOI, arXiv id or title) and merges each group into one, keeping every tag, folder and note. It also finds arXiv preprints that have since been published and fills in the journal, DOI, volume and pages, keeping citation keys. - **Paper health** marks a paper that has been retracted, corrected (an erratum or corrigendum), given an expression of concern, or withdrawn on arXiv. The notices come from Crossref, which carries both publishers' notices and the Retraction Watch database, and from arXiv. For a preprint with a published version, the published version is checked too. A badge shows in the list, and the paper's details say what the notice is, when it appeared and who reported it, with a link to it. Lightkone checks the papers not checked for a month a little after the library opens (switch it off in Settings → Paper health), and **Tidy up** lists every paper with a notice and can check the whole library at once. --- # Writing with your library ## Citation keys Every paper has a BibTeX citation key, such as `Hinton2012-ab`. Imported keys are kept, new ones are made in Paperpile's style, and a key never changes once given, so your LaTeX keeps compiling. Tap the key in a paper's details to copy it. ## Copying a citation **Cite**, in a paper's details, copies it formatted as a short post line, APA, Markdown, a LaTeX `\cite{…}`, or a BibTeX entry. Below those come journal styles from the Citation Style Language, the same 10,000+ styles Zotero and Mendeley use. The American Physical Society style is there to start. **Another journal's style…** searches all of them by name, for example *Physical Review D*, *JCAP* or *Nature*; the ones you use most recently stay in the menu. A style is fetched from the CSL project the first time and kept in your browser. When pasted into a document, the citation keeps its italics and bold. To copy a whole reference list, open a folder or search, then choose **Copy papers in view as a reference list** in the command palette. It uses the journal style you used last. ## The library as BibTeX `lightkone/library.bib` in your storage is rewritten on every save, with the whole library. The download button at the top exports the papers in the current view as a .bib, so open a folder or search first to export part of the library. ## A folder's .bib in GitHub, for Overleaf Overleaf can pull a .bib from a GitHub repository. Lightkone keeps one up to date: 1. Make a GitHub token with **Contents: read and write** on the repository (see [Private GitHub repository](STORAGE.md#private-github-repository)), and add it under **Settings → BibTeX sync to GitHub**. If your library itself lives on GitHub and that token can reach the repository, it is used when this one is empty. 2. Open a folder (or *All papers*) and press **Sync .bib**. Choose the repository and the file path, for example `refs/thesis.bib`. From then on, whenever papers in that folder change, Lightkone rewrites the file with a commit. In Overleaf, link the file from GitHub. ## Watch my manuscript **Watch my manuscript** (side bar, *Write*) keeps a manuscript's .bib in step with what the manuscript actually cites. Point it at the GitHub repository and folder holding your .tex files. Lightkone reads every `\cite{…}`, writes a .bib with exactly those papers, and lists any keys your library doesn't have so you can add them. Entries already in the .bib for keys your library lacks are kept as they are. It checks again each time Lightkone opens. ## Cite my draft **Cite my draft** (side bar, *Write*) goes through a whole manuscript and finds the sentences that make a claim but cite nothing. 1. Open your .tex, or paste a section, and press **Find uncited claims**. Lightkone reads the document's text only: the preamble, the abstract, headings, floats, comments and the bibliography are skipped, and an equation stays part of the sentence around it. 2. It lists the sentences that read like claims from elsewhere ("has been shown", "previous studies", a measured number) and passes over those about your own work ("In this paper we", "as shown in Fig. 2"), each with its line in the source. **Show every uncited sentence** lists the rest too. With a Claude key, **Ask Claude which need a citation** reads every uncited sentence with the one before it and keeps those that need one, each with a short search to find the paper. 3. **Suggest papers** on a sentence (or **Find papers for all**) looks in your library first, in the full texts of your PDFs, then on OpenAlex for published work you don't have. With a Claude key, each suggestion is checked against its passage or abstract and marked as backing the sentence, related or not, and the ones that back it are ticked. 4. **Cite** puts `\cite{…}` into the sentence, just before its full stop. A ticked paper that is not in your library is added first, so it has a key. **Undo** takes it out again. 5. **Download** gives the .tex back with only the citations added; nothing else in the source changes. Pick `\cite`, `\citep`, `\citet`, `\autocite` or `\parencite`. **Download .bib** gives the entries for every key the new .tex cites. The draft, Claude's reading of it, the papers suggested and the citations chosen are kept in this browser and in your storage (`lightkone/cite-draft.json`; in a shared or read-only library, this browser only), so the panel can be closed and opened again, on this device or another, without asking Claude again. ## Writing helpers **Writing helpers** (side bar, *Write*) holds Cite my draft and two smaller tools: - **Cite this**: paste a sentence and get the papers in your library that support it, best first. With a Claude key, each suggestion is checked against the paper and marked as supporting, related or not. - **.bib for a .tex**: paste your LaTeX, or open the .tex file, and get a .bib with exactly the papers it cites, in the order it cites them. --- # Sharing and devices ## Your library on several devices With a bucket or a GitHub repository (see [Where your library lives](STORAGE.md)), every device that uses the same storage settings sees the same library. Move the settings with **Settings → Backup**: **Export settings** on one device (with a passphrase, the file is encrypted) and **Import settings** on the other. Changes merge rather than overwrite: before each save, Lightkone reads what other devices (or Claude through MCP) saved and keeps both, paper by paper and field by field. ## On a phone Install Lightkone from the browser menu (**Install app** in Chrome, **Add to Home Screen** in Safari). Then: - it opens offline, with the library and the last 25 PDFs you opened; - on Android it appears in the share menu: share a paper's page, or an arXiv or DOI link, to Lightkone and it is added. Share a downloaded PDF (from Files or Chrome's Downloads) and the paper is added with its PDF. A page saved with Chrome's Download (an .mhtml file) is added as a saved web page. On a computer, drop PDF files or saved pages on Lightkone to add them, or drag the **+ Lightkone** button from the Add panel to the bookmarks bar. Clicked on a paper's page, it adds that paper; on any other page, it saves the page as you see it, which works for sites that turn Lightkone's page reader away. The same bookmark works in Chrome on a phone: Add papers has the steps and a button to copy its address. ## Shared libraries A shared library lives where a group can reach it: a private GitHub repository with collaborators, or a bucket whose keys you hand out. It is opened next to your own library. Who may read and who may write is decided there, so Lightkone needs no accounts. 1. Open **Shared libraries** (side bar, *Organize*). 2. **Join or start another**: give it a name, choose where it lives, and give your own token or key for it. An empty repository or bucket starts a new shared library; one that already holds a Lightkone library is joined. 3. Press the invite button to copy a link. The link carries where the library lives, never your token or key. Whoever opens it adds their own. Set **Your name** in the same panel, so papers you add to a shared library show who added them. With a read-only token, the library opens read only. In a paper's details, **Copy to…** copies a paper, with its PDF, between your library and a shared one. --- # Lightkone for Claude and other AI apps (MCP) Lightkone runs a small [MCP](https://modelcontextprotocol.io) server next to the site, at `/mcp/`. Connected to it, Claude (on claude.ai, the apps or Claude Code) can work with your library: | Tool | What it does | | --- | --- | | `search_papers` | Search titles, authors, abstracts, tags and notes, with the app's filters (`tag:`, `author:`, `year:`, `has:pdf`). | | `get_paper` | A paper's record, your notes, your highlights with their notes, and its BibTeX. | | `read_paper` | The paper's full text, page by page. | | `list_tags` | Tags and folders, and how many papers are in each. | | `search_full_text` | Find words inside the papers' text, with the page and a snippet. Narrow it with a search, like `tag:qft`. | | `get_citations` | The library papers a paper cites, and the ones that cite it, from the citation map. | | `find_missing_papers` | Works several of your papers cite that the library does not have. | | `list_branches` | Your kept Branches and their strongest candidates. | | `export_bibtex` | BibTeX for chosen papers or for everything a search finds. | | `add_paper` | Add a paper by DOI or arXiv id, with tags. | | `tag_papers` | Add or remove tags on several papers. | | `add_note` | Append to a paper's notes. | Apps that show MCP prompts (Claude Code lists them after `/`, claude.ai under the connector's menu) also get four ready-made asks: | Prompt | What it asks | | --- | --- | | `find_in_library` | What do I have on a topic, with my notes. | | `cite_draft` | Citations from my library for a paragraph, with `\cite{}` keys and BibTeX. | | `reading_gaps` | What my library keeps citing but lacks, and the best new candidates in my Branches. | | `explain_paper` | Walk me through a paper at research level, with my notes and how it connects. | The full text is extracted from the PDF the first time you open a paper in Lightkone's reader and saved as `lightkone/text/.txt` next to it. `search_full_text` reads at most 40 texts per call, newest first; give it a search such as `tag:qft` or `year:2018-2024` to search the right ones. The citation tools and Branches read what the citation map saved (`lightkone/citations.json`, `lightkone/branches/`), so open the map once in Lightkone first. ## Your own address Anyone using the site can connect their own library, as long as it lives in an S3/R2 bucket or a GitHub repository: 1. In Lightkone, open **Settings**, set up your storage and save. 2. Under **Claude and other AI apps**, press **Make my AI app address**. The site tries your storage once and gives you an address `https:///mcp/u1.…`. 3. Add it as a connector (below). The address carries your storage settings, encrypted with a key that only the server has. The site keeps no accounts and no copy of anyone's keys: each request decrypts the address and talks to your storage directly. Anyone who has the address can read and change your library, so keep it private. To cut an address off, replace the access key or token in your storage settings and make a new address. A library kept only in the browser can't be reached this way. ## On your own computer, with the CLI Claude Code and Claude desktop can also start Lightkone themselves, with the [command line](CLI.md#in-claude-and-other-ai-apps): `pip install lightkone`, set the library up once with `lightkone login`, then ```bash claude mcp add lightkone -- lightkone mcp ``` This needs no address and no secret on the site, works with a library in a plain folder too, and keeps your keys on your computer. ## For whoever hosts the site Personal addresses need one secret on the Pages project. In Cloudflare, open **Workers & Pages → lightkone → Settings → Variables and Secrets → Add**: type **Secret**, name `MCP_SECRET`, value a long random string (for example from `openssl rand -hex 32`), then redeploy. If `MCP_SECRET` is not set, the site uses `MCP_TOKEN` instead. Changing the secret turns off every address made with it, so set it once and leave it. ### The host's own library, without the app's settings The host can also serve one library straight from an R2 bucket bound to the project: - **Variables and Secrets → Add**: Secret `MCP_TOKEN`, a long random token. - **Bindings → Add → R2 bucket**: variable name `LIBRARY`, the library's bucket. That library's address is `https:///mcp/`. Personal addresses work without either of these. ## Connecting - **claude.ai and the Claude apps:** Settings → Connectors → **Add custom connector**, paste the address. - **Claude Code:** `claude mcp add --transport http lightkone
` ## A skill for Claude Claude works better with the library when it knows the habits: search before answering, cite by key, check the text before claiming a paper says something, and ask before changing anything. Those are written down as a skill, served at `/skills/lightkone/SKILL.md` on the site (`public/skills/lightkone/` in the repository). - **Claude Code:** save it as `~/.claude/skills/lightkone/SKILL.md`, for example `mkdir -p ~/.claude/skills/lightkone && curl -o ~/.claude/skills/lightkone/SKILL.md https://lightkone.lab.kausalflow.com/skills/lightkone/SKILL.md`. - **claude.ai and the Claude apps:** put `SKILL.md` in a folder named `lightkone`, zip the folder, and upload it under Settings → Capabilities → Skills. ## Notes - Changes made through MCP appear in the app when you come back to its tab. Every save reads the library again first and merges what was saved elsewhere, paper by paper and field by field, so the app and an AI app don't overwrite each other. Only when both changed the same field of the same paper does the later save's value stay. - GitHub-backed libraries commit each change through MCP, like the app does. --- # The command line The `lightkone` command and Python package read and write the same files as the web app, wherever your library lives, so you can switch between the browser, the terminal and a notebook freely. Its code is in the `cli/` folder of this repository. ## Set up ```bash pipx install lightkone # or pip install lightkone ``` ### Starting fresh: `lightkone setup` If you haven't set up storage in the web app yet, let the command line do it: ```bash lightkone setup ``` It asks for the bucket (or repository), then: 1. looks over what you typed for common slips (the bucket name inside the endpoint, a public `r2.dev` address, a region other than `auto` on R2). A bare R2 account id is turned into the endpoint for you; 2. checks the storage by listing it and saving, reading back and removing a small test file, and says what is wrong if a step fails; 3. for a bucket, checks that the web app may reach it (CORS) and, if not, offers to add the rule. The terminal can do this, the browser can't. It needs a key allowed to change the bucket's settings (on R2, an *Admin Read & Write* token); with an *Object Read & Write* key it prints the rule to paste in the dashboard, or a `wrangler` command, instead. Other CORS rules on the bucket are kept; 4. saves the library for the command line, and writes `lightkone-settings-main.json` for the web app: open Lightkone → **Settings → Backup → Import settings**, pick the file, **Save**. It holds your keys (readable by you only, or encrypted with `--passphrase`); delete it once imported. Options: `--provider s3|github|folder` and `--set field=value` to skip the questions, `--origin http://localhost:5173` to also let a local copy in, `--url` for your own Lightkone site, `--cors/--no-cors`, `--open` to open the app at the end. Running it again on a working setup only checks. ### From the web app's settings If the web app is already set up, the quickest way is a settings backup from it: **Settings → Backup → Export settings**. Then ```bash lightkone login --from-backup lightkone-settings-2026-11-10.json ``` Only where the library lives (and an OpenAlex key, if you set one) is kept, in `~/.config/lightkone/config.json`, readable by you only. An encrypted backup asks for its passphrase (install `lightkone[crypto]` for that). Or set it up by hand: ```bash lightkone login --provider s3 # endpoint, bucket, region, folder, access key, secret lightkone login --provider github # owner/name, a fine-grained token, branch lightkone login lab --provider folder --set path=~/papers ``` Several libraries can be kept under names: `lightkone libraries`, `lightkone use lab`, or `-L lab` on any command. `--dir PATH` (or `LIGHTKONE_DIR`) uses a folder directly, for example a clone of the library's GitHub repository. A library kept only in a browser can't be reached from the command line; move it to a bucket or repository first. ## Find and read ```bash lightkone ls # newest first lightkone ls gravitational lensing # best match first lightkone ls author:einstein year:1915-1920 -o bib lightkone ls in:Physics/QFT is:starred has:pdf lightkone show Hinton2012-in lightkone text Hinton2012-in | grep -i dropout # once the reader has extracted it lightkone highlights Hinton2012-in -o markdown lightkone pdf Hinton2012-in --open ``` The search syntax is the app's: words anywhere, `tag:"a b"`, `author:`, `year:2019` or `year:2015-2020`, `in:Folder/Sub`, `is:starred|recent|trash`, `has:pdf|abstract|arxiv|notes` and `no:` for the opposite. ## Open in the browser ```bash lightkone web # the Lightkone app lightkone open Hinton2012-in # the paper's details lightkone open Hinton2012-in -p 3 # its reader at page 3 ``` `--url` (or `LIGHTKONE_URL`) points at another Lightkone, such as your own deployment or staging. The browser uses the library set up in its own Lightkone Settings. ## Add and organise ```bash lightkone add 2106.09685 10.1103/PhysRevLett.116.061102 -t lensing -f Physics/GR lightkone import paperpile.bib --dry-run lightkone tag Hinton2012-in -a dropout -r todo lightkone tag -q 'author:einstein year:1915' -a classics lightkone folder -q tag:lensing -a Physics/GR lightkone note Hinton2012-in "Compare with DropConnect." lightkone star Hinton2012-in lightkone trash Hinton2012-in # --restore to take it back lightkone attach Hinton2012-in ~/Downloads/1207.0580.pdf ``` Each change is saved straight away. Before writing, the library is read again and anything saved elsewhere since (in the app, by Claude through MCP, or by another run) is merged in field by field, the way the app does it. ## Export ```bash lightkone export -o library.bib lightkone export in:Thesis -o thesis.bib lightkone export tag:lensing -f csv -o lensing.csv ``` ## The citation map ```bash lightkone map # citation-map.html for the whole library lightkone map in:Thesis --open lightkone map tag:lensing -o lensing.json --linked-only lightkone missing in:Thesis --min 3 # what your thesis papers keep citing that you lack lightkone missing --add -t to-read ``` References come from OpenAlex and are kept in `lightkone/citations.json`, shared with the web app's Citation map, so each paper is looked up once. OpenAlex's free budget is per network; for a budget of your own pass `--openalex-key` or set `OPENALEX_API_KEY`. The page draws papers across by year and up by how often they are cited, in bands by folder; tap a paper to see its links. The JSON has `nodes` and `edges` for networkx, Gephi or D3. ## In Claude and other AI apps `lightkone mcp` serves the library to Claude Code, Claude desktop or any [MCP](https://modelcontextprotocol.io) app over standard input and output. The app starts it itself, so there is nothing to keep running, and it works with any storage the CLI can reach, a plain folder included. ```bash claude mcp add lightkone -- lightkone mcp # Claude Code, default library claude mcp add thesis -- lightkone -L thesis mcp # another library claude mcp add lab -- lightkone --dir ~/lab-library mcp # a folder ``` For Claude desktop, add it to `claude_desktop_config.json` (Settings → Developer → Edit Config): ```json { "mcpServers": { "lightkone": { "command": "lightkone", "args": ["mcp"] } } } ``` It has the same tools and prompts as the site's address (see [MCP](MCP.md)), with no limit on how many texts a full-text search reads. ## In Python ```python from lightkone import Library, S3Storage lib = Library(S3Storage(endpoint="https://.r2.cloudflarestorage.com", bucket="papers", access_key_id="...", secret_access_key="...")).load() recent = lib.search("year:2024-2026 has:pdf", sort="year") df = lib.to_dataframe(recent, columns=["key", "title", "year", "tags"]) ``` Papers are plain dicts in the shape of Lightkone's `Paper` record, so fields this version does not know survive a save. ## Raw data, drawn your way The built-in page is only one way to look at the map. Everything behind it is yours to load and plot however you like: ```python from lightkone import FolderStorage, Library lib = Library(FolderStorage("~/papers")).load() lib.papers # every record, as dicts lib.to_dataframe() # papers as a pandas DataFrame (pip install "lightkone[data]") lib.citations() # raw lightkone/citations.json: OpenAlex id, year, cited count, references g = lib.citation_graph("in:Thesis", fetch=True) # fetch=True looks up what's new on OpenAlex nodes, edges = g.to_dataframes() # pandas: one row per paper, one per link (source cites target) G = g.to_networkx() # networkx DiGraph (pip install "lightkone[graph]") g.to_json() # plain nodes/edges g.missing # [(OpenAlex id, [ids of your papers citing it]), ...] ``` From the command line, `lightkone map` writes the same data as `.json`, `.graphml` or `.gexf` (Gephi, Cytoscape, networkx) or `.csv` (nodes, plus `-edges.csv`). `lightkone ls -o json` and `lightkone export -f csv` give the paper records. --- # Your data Lightkone has no accounts, no database of its own, no analytics and no ads. The site serves the app. Your library lives in storage you choose, and the app talks to it from your browser. | What | Where it goes | | --- | --- | | Your papers, notes, highlights and PDFs | Only your own storage. The site never receives them, except when you use the AI app address (MCP): then the site's server reads your library from your storage for each request, with your storage keys sealed inside your private address. | | Keys and tokens (Claude, GitHub, bucket, OpenAlex) | Only this browser's settings. Each is sent only to the service it belongs to. Invite links never carry them. | | Ask, Explain, Summarize, Compare papers and Cite this's check | When you use them, your question and the passages picked from your papers (for a comparison table, each chosen paper's text; for Ask about five papers or fewer with Read the whole PDFs on, the PDFs themselves) go from your browser straight to Anthropic, with your own Claude key. | | Your chats with Claude, and Claude's answers | Your own storage (`lightkone/history/` for chats; `lightkone/answers/`, `lightkone/citedby/` and `lightkone/cite-draft.json` for answers kept so they aren't asked for twice). In a shared or read-only library, this browser only. | | Asking beyond your library | The words of your question go to OpenAlex's search. | | Cite my draft | Your draft is read in your browser, and kept with your chats (above). A few words from each sentence you look up go to OpenAlex's search. With your Claude key, the uncited sentences (each with the one before it) and the passages found for them go straight to Anthropic. | | How others cite this | The paper's DOI or arXiv id goes to Semantic Scholar. With your Claude key, the citing sentences and the paper's details go straight to Anthropic when you sort them. | | Paper health | Your papers' DOIs go to Crossref, and their arXiv ids to arXiv through the site, about once a month. Nothing else about them is sent. | | Journal citation styles | The style's name goes to the Citation Style Language project on GitHub, and the list of styles is fetched from Zotero. Your papers are formatted in your browser. | | Adding and looking up papers | The DOI, arXiv id or title goes to Crossref, OpenAlex, Semantic Scholar or arXiv. For open-access PDFs, the DOI and your contact email go to Unpaywall through the site. A web page you add is fetched through the site, which passes only its address to the page's own server. If that server turns the site away, the address goes to Jina Reader (r.jina.ai) instead, unless you switch that off in Settings → DOI, arXiv and BibTeX. | | Related papers and the citation map | The DOIs or arXiv ids of your papers go to OpenAlex or Semantic Scholar. | | New on arXiv | Only the categories you follow go to arXiv, through the site. The ranking happens in your browser. | | Search by equation | Each arXiv id goes to the site, which fetches that paper's public LaTeX source from arXiv and returns its equations. | The site's few server functions exist only where browsers aren't allowed to call a service directly (arXiv listings and sources, open-access PDFs, published versions, and the MCP address). They keep nothing; public answers from arXiv are cached for a while. The host (Cloudflare) keeps its usual request logs. The same table is in the app, under **Help → Your data**. --- # Questions and fixes ## Questions **Is there an account?** No. Lightkone has no accounts. Your settings are in your browser and your library is in your storage. **What happens if I clear my browser data?** Your settings and keys go with it (keep a backup from **Settings → Backup**). A library in a bucket or on GitHub is untouched. A library kept in this browser only is lost. **Can I move my library from this browser to a bucket later?** Not in one step yet. **Export .bib** and import it into the new storage to carry papers, tags and keys, but notes and highlights stay behind. **Do I need a Claude key?** Only for Ask, Compare papers, Explain, Summarize, Cite this's check, and adding a paper from a plain description. Everything else works without one. **Will the example papers come back?** No. Once removed, they stay gone. Only a brand-new library in this browser gets them, never a bucket, repository or shared library. **Does Lightkone change my Paperpile library or Google Drive?** No. It only reads what you export or copy. **Which version am I using?** The last line of **Settings** and of **Help** names it, for example `0.1.0 · 2026-10-21 · ec1bee0`: the release, the day of the change it was built from, and that change's short id. The live site says the same at `/version.json`. When the site has a newer one than the open app, that line says so and offers a reload. ## When something goes wrong **"Could not open …: Failed to fetch"** with a bucket: the bucket's CORS rule doesn't allow the site. Add the exact address (for example `https://lightkone.lab.kausalflow.com`) to *AllowedOrigins*. See [Where your library lives](STORAGE.md#cloudflare-r2). **403, Access Denied, or 404 from GitHub**: the key or token can't reach that bucket or repository. Check the bucket name and endpoint, or that the fine-grained token was given this repository with *Contents: Read and write*. **It stopped saving after weeks of working**: a GitHub token has expired. Make a new one and replace it in Settings. **PDFs didn't link after importing from Paperpile**: check they were copied to `papers/All Papers/` in the same storage (with your bucket folder in front, if you set one), then reload. Search `no:pdf` for the ones still missing. See [Moving from Paperpile](PAPERPILE.md#step-5-check-and-tidy). **"No free PDF found"**: the paper has no legal open-access copy, or no contact email is set under **Settings → PDFs** (needed for non-arXiv papers). Use **Attach PDF**. **"Not saved" at the top**: the last save didn't reach your storage, and a message says why. Your changes are still in the open page, and the next change saves everything again, so keep the tab open until it says *Saved*. If it keeps failing, check the storage details in Settings. **"Offline, saves later" at the top**: there is no network. Changes are saved when it comes back, as long as Lightkone stays open; highlights are kept on the device either way. See [Reading offline](READING.md#reading-offline). **Ask says to add a key**: paste your Anthropic API key under **Settings → Claude**. --- # Hosting Lightkone is a static site (`npm run build` writes `dist/`), so any static host works. The repository deploys to Cloudflare Pages: free for private repositories, and in the same account as an R2 bucket for storage. ## Cloudflare Pages from GitHub Actions `.github/workflows/deploy.yml` publishes every push to `main` to a staging site (`https://staging..pages.dev`), and each version tag to the live site, checking both in a browser; see [RELEASING.md](RELEASING.md). It needs these in the repository (Settings → Secrets and variables → Actions): | Name | Kind | Value | | --- | --- | --- | | `CLOUDFLARE_ACCOUNT_ID` | secret | Shown on the Cloudflare dashboard's account home, right sidebar. | | `CLOUDFLARE_API_TOKEN` | secret | My Profile → API Tokens → Create Token → Custom token, permission **Account · Cloudflare Pages · Edit**, limited to your account. | | `PAGES_DOMAIN` | variable (optional) | A custom domain, e.g. `lightkone.lab.kausalflow.com`. | | `PAGES_PROJECT` | variable (optional) | Pages project name; defaults to `lightkone`. | The first run creates the Pages project; after the first release the app is at `https://.pages.dev`. For a custom domain, set `PAGES_DOMAIN`, then point DNS at Pages: add a `CNAME` from the domain to `.pages.dev` (proxied, if the zone is on Cloudflare). The deploy token only manages Pages, so it cannot add the record. If storage is an S3/R2 bucket, add the site's address to the bucket's CORS rule (see [STORAGE.md](STORAGE.md)). ## The PDF service `functions/api/pdf.ts` is a Cloudflare Pages Function, deployed with the site at `/api/pdf`. Given a DOI and a contact email it asks [Unpaywall](https://unpaywall.org) for a legal open-access copy and passes the PDF through, because publishers do not let browsers download from another site. It fetches only URLs Unpaywall returns for that DOI. arXiv PDFs do not need it. On other hosts, either run the same function elsewhere and set its URL under **Settings → PDFs**, or go without DOI downloads. ## The published-version service `functions/api/published.ts` (at `/api/published?arxiv=`) tells **Tidy up** which arXiv preprints have been published: first from the DOI and journal reference authors added on arXiv, then from Semantic Scholar. Semantic Scholar limits anonymous requests; for steadier results, request a free key at https://www.semanticscholar.org/product/api and add it to the Pages project as the secret `S2_API_KEY`. The same service tells **Paper health** which preprints have been withdrawn on arXiv. ## The page service `functions/api/page.ts` (at `/api/page?url=`) fetches a web page for saving it into the library, because browsers don't let one site read another's pages. It answers JSON, never HTML, so nothing it fetches can run as part of the site, and it fetches only public http(s) addresses on the usual ports, checking each redirect, up to 5 MB, waiting at most 15 s. When the site fails or refuses, it answers 424 with the reason (never 502 or 504, which Cloudflare replaces with its own error page); the other services do the same. A page behind a browser check is read through Jina Reader (r.jina.ai) when the app asks (it does unless switched off in Settings). Without a key Jina shares one small limit among everyone calling from Cloudflare's addresses, so it mostly answers 429: add a `JINA_API_KEY` secret to the Pages project, in both the Production and the Preview environment (the staging site is a Preview deployment), then deploy again so the Functions see it. ## Limits on the services The services under `/api/` (PDF, web pages, published versions, arXiv, equations, MCP address) are for the site's own app. `functions/api/_middleware.ts` refuses calls that a browser says come from another website, and limits each address to a number of calls a minute per service (30 for PDFs, 120 for equations; see `LIMITS`), answering `429` with `Retry-After` beyond that. Normal use stays far below these. Those counts are kept per Cloudflare server. For a hard limit across the whole network, also add a rate limiting rule in the dashboard (the free plan includes one): your domain's zone → **Security → WAF → Rate limiting rules → Create rule**, with *URI Path starts with `/api/`*, counting by IP, for example **50 requests per 10 seconds**, action **Block** for 10 seconds. ## Other hosts Copy `dist/` to any static host (GitHub Pages, Netlify, S3 + CloudFront, an internal nginx). Paths are relative, so it also works under a sub-path. --- # Releasing The live site (https://lightkone.lab.kausalflow.com) is what people use, so nothing reaches it untested. Changes go to a staging site first, and reach the live site only with a release: a version tag. GitHub Actions does the work (`.github/workflows/ci.yml` and `deploy.yml`). ## How a change travels 1. **Every pull request** runs `npm run check` (types and tests) and the build. 2. **Every merge to `main`** deploys to the staging site, https://staging.lightkone.pages.dev. The app there shows a **Staging** tag beside its name. After the deploy, the workflow: - opens the site in a browser (`scripts/smoke.mjs`): the landing page, docs and app load, the app opens with its example papers and Chats, with no errors, `/api/` answers, and `version.json` names the commit just built; - checks that the site sends exactly the files this commit builds (`npm run verify`). 3. **A version tag** (`v0.2.0`, matching `package.json`) deploys the same way to the live site, runs the same checks there (on both https://lightkone.pages.dev and the custom domain), and makes the GitHub release. The release gets notes listing the pull requests merged since the last one, and `fingerprints.txt`: the SHA-256 of every built file. The live site keeps the last release until the next tag, however many merges come in between. ## Making a release 1. Try the merged changes on staging. 2. Open a pull request titled **Lightkone 0.2.0** that sets `version` in `package.json` (and `package-lock.json`, with `npm version 0.2.0 --no-git-tag-version`). Merge it. 3. Tag that commit on `main` and push the tag: ```sh git checkout main && git pull git tag v0.2.0 && git push origin v0.2.0 ``` Or on GitHub: **Releases → Draft a new release**, tag `v0.2.0` on `main`, **Publish**. The workflow adds the notes and fingerprints if the release has none. 4. Watch the **Deploy** run under **Actions**. When it is green, the live site is on the new version; Help and Settings show it (`Lightkone 0.2.0 · date · commit`), and open apps offer **Reload to update**. A tag that does not match `package.json` stops before deploying. ### Version numbers While Lightkone is 0.x: the middle number for new features (0.2.0 → 0.3.0), the last for fixes only (0.2.0 → 0.2.1). Say in the release notes when a release changes how the library is stored, and what an older copy of the app does with it. ## Putting an earlier release back **Actions → Deploy → Run workflow**, with the tag to put back (for example `v0.2.0`) as *release*. It builds that tag and deploys it to the live site, with the same checks. Cloudflare's dashboard can also roll back: Workers & Pages → lightkone → Deployments → a production deployment → **Rollback**. ## Check it yourself The build is reproducible: the same commit builds byte for byte the same files. Anyone can check that a site sends exactly the code of a release, and so that the code holding their API keys is the code in this repository: ```sh git checkout v0.2.0 && npm ci && npm run build npm run verify -- https://lightkone.pages.dev/ ``` `npm run verify -- --list` prints the fingerprints, to compare with `fingerprints.txt` on the release. A custom domain on a Cloudflare zone can change pages on the way (email obfuscation, Rocket Loader), so check the `pages.dev` address, or turn those off for the domain. ## Staging is a separate site Staging has its own address, so the browser keeps its library settings, keys and offline papers apart from the live site's: - Copy your settings over with **Settings → Export settings** on the live site and **Import settings** on staging. - An S3 or R2 bucket must allow the staging address in its CORS rule (see [STORAGE.md](STORAGE.md)), as it does the live one. - The services under `/api/` that need secrets (`MCP_SECRET` for the AI app address, `S2_API_KEY`) read them from the Pages project's **Preview** environment on staging: Workers & Pages → lightkone → Settings → Variables and Secrets, and add them for Preview as well as Production. Staging and preview deployments are not indexed by search engines. ## Checking a site by hand ```sh npm run smoke -- https://staging.lightkone.pages.dev/ # the browser check npm run verify -- https://staging.lightkone.pages.dev/ # same files as this build ``` The browser check needs Chromium: `npx playwright install chromium`. ## Releasing the command line The command line in `cli/` is published to PyPI as [`lightkone`](https://pypi.org/project/lightkone/) on its own schedule, with tags of the form `cli-v0.1.0`. It does not touch the site. 1. Change `__version__` in `cli/lightkone/__init__.py` in a pull request and merge it. The same 0.x rule applies: the middle number for new features, the last for fixes. 2. Tag that commit on `main`: `git tag cli-v0.1.0 && git push origin cli-v0.1.0`, or on GitHub, **Releases → Draft a new release** with the tag `cli-v0.1.0`. 3. The **Publish CLI** workflow tests and builds the package, then uploads it. A tag that does not match `__version__` stops before uploading. ### One-time setup on PyPI The workflow uses PyPI's trusted publishing, so no token is stored anywhere. Before the first release, a maintainer signs in to PyPI and adds a *pending publisher* (Account → Publishing → Add a new pending publisher): - PyPI project name: `lightkone` - Owner: `kausalflow`, repository: `lightkone` - Workflow name: `publish-cli.yml` - Environment name: `pypi` The first upload creates the project and binds it to this workflow. On GitHub, the `pypi` environment (Settings → Environments) can require an approval before each upload. --- # Writing a plugin A plugin is an ES module whose default export is a `LightkonePlugin` (or a function returning one). Add its URL under Settings → Plugins. The types live in [`src/core/types.ts`](../src/core/types.ts). ```js // my-plugin.js export default { id: "inspire", name: "INSPIRE-HEP", description: "Adds papers by INSPIRE record id.", lookups: [ { id: "inspire", label: "INSPIRE record", matches: (s) => /inspirehep\.net\/literature\/\d+/.test(s), async resolve(s) { const id = s.match(/literature\/(\d+)/)[1]; const r = await fetch(`https://inspirehep.net/api/literature/${id}`); const m = (await r.json()).metadata; return { title: m.titles[0].title, authors: m.authors.map((a) => a.full_name), year: Number(m.preprint_date?.slice(0, 4)) || undefined, arxiv: m.arxiv_eprints?.[0]?.value, doi: m.dois?.[0]?.value, fields: {}, }; }, }, ], actions: [ { id: "inspire-citations", label: "Citations on INSPIRE", available: (p) => Boolean(p.arxiv), async run(p, ctx) { ctx.open(`https://inspirehep.net/search?p=refersto:arxiv:${p.arxiv}`); return { kind: "none" }; }, }, ], }; ``` ## What a plugin can provide | Field | Purpose | | --- | --- | | `storage` | Places a library can live. `create(config)` returns a `StorageAdapter` with `read`, `write`, `list` and optionally `remove` and `seed` (papers the backend already holds in another format). | | `lookups` | Turn an identifier or link into a paper. The first lookup whose `matches` returns true is used. | | `importers` | Turn pasted text or a file into papers. | | `actions` | Buttons on a paper. `run` returns text to show, a list of papers the user can add, or nothing. | | `settings` | Fields shown in Settings, read back as `ctx.settings["."]`. | Plugins run in the page with full access, so only load code you trust, and serve plugin files with CORS headers that allow the Lightkone origin. --- # Architecture Lightkone is a static web app (Preact + TypeScript, built with Vite). There is no Lightkone server: the browser talks to the storage backend and to public metadata APIs directly. That keeps hosting free for a person or a small team, and every part that would need a server later sits behind a plugin interface. ``` src/core/ types, BibTeX, library model, search, plugin registry, settings src/plugins/ built-in plugins (storage, lookups, related papers, Claude, Dullaby) src/reader/ the PDF reader (pdf.js) with highlights and notes src/app.tsx the interface public/sw.js service worker, so the installed app opens offline ``` Remote storage is wrapped by `src/core/offline.ts`, which keeps a copy of what was last read (the library, notes and the 25 most recent PDFs) in the browser's Cache Storage and answers from it when there is no network. PDFs chosen under Offline papers are kept there too, outside the 25, within a size limit. Highlights written with no network wait there and are written on reconnect. The web app manifest makes Lightkone installable and a share target: shares are posted to `app/`, and the service worker turns them into `?title=&text=&url=`, keeping any PDF files (or saved pages) in a cache for the app to take (`?shared-files`); the bookmarklet sends `?add=` (`src/core/capture.ts`). ## Data The library is these files in the chosen storage: | Path | What | | --- | --- | | `lightkone/library.json` | Source of truth: every paper with its tags, notes and extra BibTeX fields | | `lightkone/library.bib` | Rewritten on every save, for LaTeX, Overleaf or any other tool | | `papers/All Papers// et al. - .pdf` | PDFs, in the same layout Paperpile uses in Google Drive. Each paper's `file` field (also in the `.bib`) is the path under `papers/`, so Paperpile's `All Papers` folder can be copied in as it is. Libraries that kept PDFs under `Paperpile/` have them moved to `papers/` when they open | | `lightkone/annotations/<id>.json` | Highlights and notes on a paper's PDF | | `lightkone/pages/<id>.html`, `lightkone/pages/<id>.md` | A saved web page: its raw HTML, and its main text as Markdown (`src/core/webpage.ts`) | | `lightkone/text/<id>.txt` | A paper's text for search, Ask and MCP: from its PDF, or a saved page's Markdown | Because these are plain files, a library can always be read without Lightkone, moved between backends by copying a folder, and versioned (GitHub storage commits every save). Saves are debounced and write the whole library. That is fine into the low tens of thousands of papers; a server mode (below) would switch to per-paper writes. ## Plugins Everything the app can do beyond its core goes through `LightkonePlugin` (`src/core/types.ts`): storage providers, lookups (identifier → paper), importers (text → papers) and paper actions (buttons on a paper). Built-in plugins use the same interface, and outside plugins load from a URL as ES modules. See [PLUGINS.md](PLUGINS.md). ## AI The Claude plugin calls the Anthropic API from the browser with the user's own key, which is stored in that browser only. It sends the paper's metadata and, when the PDF is stored in Lightkone, the PDF itself. When adding a paper from a link without a DOI or from a description, it asks Claude to find the paper with the web search and fetch tools, then takes the record from Crossref or arXiv when Claude found an identifier. ## Security model Settings, storage credentials and API keys stay in the browser's localStorage and are never written into the library. Lightkone asks the browser to keep that storage persistent (`navigator.storage.persist()`). **Settings → Backup** exports them to a JSON file, encrypted with AES-GCM under a PBKDF2 key when a passphrase is given, and imports such a file on another device. Use credentials scoped to one bucket or one repository. Plugins loaded from a URL run with full page access, so only load code you trust. ## Next steps - **Server mode for teams.** A small optional API (Cloudflare Worker, or Docker for self-hosting) that holds the storage credentials, adds sign-in through the company's identity provider (OIDC/SAML), shared libraries and per-paper writes. The browser storage plugins stay for single users. - **MCP server.** Expose search, add and cite as MCP tools so Claude and other agents can work with the library directly. - **Google Drive and OneDrive storage**, as plugins over the same file layout. - **Full-text search** over stored PDFs, and semantic search with embeddings. - **Browser button** to save the paper on the current page.