RishabhKodes/blinks

An AI-powered knowledge graph that saves, classifies, and connects your web resources into an interactive graph you can chat with.

6

stars

47

commits

TypeScript

primary language

Sep 5, 2026

updated

README

blinks

A local-first, AI-powered knowledge graph for your saved web resources. Paste a URL, and blinks fetches the content, classifies it with an LLM, discovers connections between your resources, and renders everything as an interactive graph you can explore and chat with.

blinks knowledge graph overview Resource detail panel with connections

Bring Your Own Key -- blinks does not ship any API keys. You supply your own OpenAI, Anthropic, or Ollama setup. All LLM calls run against your credentials, on your account. Nothing is proxied through a third party.


Features

  • Save and classify -- paste a URL and blinks fetches the page content, generates a title, summary, key concepts, and topic tags via LLM
  • Knowledge graph -- resources and topics render as an interactive force-directed graph with drag, zoom, and click-to-inspect
  • Semantic connections -- a second LLM pass finds typed relationships between resources (same subject, builds on, contrasts, applies, source reference, duplicate)
  • Chat -- ask questions about your saved resources; the LLM answers grounded in your knowledge base, with the option to file answers back as notes
  • Search -- full-text search across all topics, resources, titles, summaries, and authors
  • Dark and light mode -- toggle between themes; preference is saved in your browser
  • PWA -- installable on mobile and desktop with share-target support (share URLs directly from other apps into blinks)
  • Authentication -- optional login wall via environment variables to protect access on shared networks
  • Three LLM providers -- OpenAI (cloud), Anthropic/Claude (cloud), or Ollama (local, fully offline)
  • Supported sources -- auto-detects Twitter/X, YouTube, GitHub, ArXiv, Medium, Reddit, Substack, Spotify, Hacker News, Stack Overflow, Wikipedia, and any generic webpage

How to use the app

To add a resource, click + Add (or press Ctrl+N), paste the resource's URL, and submit it. Blinks will fetch and process the resource, then add it to your knowledge graph.


Stack

LayerTechnology
FrameworkNext.js 16 (App Router)
UIReact 19, TypeScript, Tailwind CSS 4
DatabaseCloudflare D1 (SQLite) via Drizzle ORM
Graphreact-force-graph-2d + dagre layout
LLMOpenAI, Anthropic, or Ollama (raw fetch, no SDK)
Content extractiondefuddle (reader mode) + open-graph-scraper
DeploymentOpenNext on Cloudflare Workers (optional)

Quickstart

1. Prerequisites

2. Clone and install

git clone https://github.com/RishabhKodes/blinks.git
cd blinks
npm install

3. Configure environment

cp .env.example .env

Open .env and set your provider. Pick one of the three options below:

Option A -- OpenAI (default)

LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...

Option B -- Anthropic (Claude)

LLM_PROVIDER=claude
ANTHROPIC_API_KEY=sk-ant-...

Option C -- Ollama (local, no key needed)

LLM_PROVIDER=ollama
OLLAMA_MODEL=llama3.2

Make sure ollama serve is running before you start the app. See the Ollama section below for full setup.

4. Initialize the database

npm run db:migrate:local

This applies the SQL migrations from drizzle/ to your local D1 SQLite database stored under .wrangler/state/.

5. Start the app

npm run dev

Open http://localhost:3000.

6. Verify everything works

  1. Go to /settings and confirm your provider and key status show green.
  2. Click + Add (or press Ctrl+N) and paste any URL.
  3. Confirm the resource appears as a node in the graph.
  4. Open Chat (Cmd+J / Ctrl+J) and ask a question to verify LLM streaming works.

LLM Providers

blinks supports three providers. Set LLM_PROVIDER in your .env to switch between them. You must restart the dev server after changing this value.

OpenAI

The default provider. Requires OPENAI_API_KEY.

LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...

OpenAI uses separate models for different tasks. You can override each one individually or use the defaults:

VariableDefaultUsed for
OPENAI_MODELgpt-5.5Chat and general LLM calls
OPENAI_CLASSIFICATION_MODELgpt-5-miniResource classification (title, summary, topics)
OPENAI_CONNECTION_MODELgpt-5.4-miniJudging connections between resources
OPENAI_JSON_FALLBACK_MODELgpt-5.4-miniRetry model when classification JSON parsing fails

Anthropic (Claude)

Requires ANTHROPIC_API_KEY.

LLM_PROVIDER=claude
ANTHROPIC_API_KEY=sk-ant-...

Claude also supports per-task model overrides:

VariableDefaultUsed for
CLAUDE_MODELclaude-sonnet-4-20250514All tasks (chat, classification, connections)
CLAUDE_CLASSIFICATION_MODELSame as CLAUDE_MODELResource classification override
CLAUDE_CONNECTION_MODELSame as CLAUDE_MODELConnection discovery override
CLAUDE_JSON_FALLBACK_MODELclaude-sonnet-4-20250514Retry model for classification

Ollama (Local Models)

Ollama lets you run LLMs locally with no cloud API key and no data leaving your machine. blinks talks to Ollama via its OpenAI-compatible API.

Setup:

  1. Install Ollama from ollama.com.

  2. Pull a model:

    ollama pull llama3.2
    
  3. Start the Ollama server (if not already running):

    ollama serve
    
  4. Set your .env:

    LLM_PROVIDER=ollama
    OLLAMA_MODEL=llama3.2
    # OLLAMA_BASE_URL=http://localhost:11434   # default, change only if needed
    
  5. Start blinks: npm run dev

  6. Go to /settings and confirm the Ollama status badge shows Connected.

Recommended models:

blinks requires models that follow JSON instructions reliably. Good options:

ModelSizeNotes
llama3.23BFast, good JSON output, low RAM usage
mistral7BStrong instruction following
qwen2.57BGood multilingual + JSON support
llama3.370BBest quality, needs 48GB+ RAM

Ollama uses a single model for all tasks (classification, connections, chat). Set it via OLLAMA_MODEL.

blinks has built-in JSON repair for malformed LLM output, so even smaller models work reasonably well. If classification consistently fails, try a larger model.


Authentication

blinks includes an optional login wall. When enabled, all pages (except the login page itself) require a username and password.

To enable:

Set both variables in your .env:

AUTH_USERNAME=admin
AUTH_PASSWORD=your-password-here

To disable: remove or leave both variables unset.

How it works:

  • On login, the credentials are hashed (SHA-256) and stored as a session cookie (blinks-session).
  • Sessions last 30 days.
  • The cookie is HTTP-only and uses the secure flag in production.
  • If either AUTH_USERNAME or AUTH_PASSWORD is missing, auth is completely bypassed and the app is open to anyone who can reach it.

This is useful when you access blinks over your local network or expose it via a tunnel.


Keyboard Shortcuts

ShortcutAction
Ctrl+NOpen the Add Resource modal
Cmd+K / Ctrl+KToggle Search palette
Cmd+J / Ctrl+JToggle Chat panel
EscapeClose any open modal, panel, or palette
EnterSend a chat message
Arrow Up / Arrow DownNavigate search results

Dark and Light Mode

blinks supports both dark and light themes. Toggle using the theme button in the top toolbar. Your preference is saved in localStorage and persists across sessions.

The default theme is dark. An inline script prevents flash-of-unstyled-content on page load by applying the saved theme class before first paint.


Mobile Access

The blinks UI is fully responsive -- the graph, panels, modals, and chat all adapt to small screens.

Browser access (works immediately)

Next.js 16 binds to all network interfaces by default, so npm run dev is already reachable from any device on the same WiFi.

  1. Find your local IP:

    # macOS
    ipconfig getifaddr en0
    
    # Linux
    hostname -I | awk '{print $1}'
    
    # Windows
    ipconfig | findstr IPv4
    
  2. On your phone, open http://<your-ip>:3000

The full app works in the mobile browser -- browse the graph, add URLs, chat, search, everything.

Tip: If you are on a shared network, enable authentication to protect access.

Install as app (requires HTTPS)

For the full PWA experience (standalone app window, "blinks" in the system share sheet), you need HTTPS. Over plain HTTP the app works fine in the browser, but the service worker and install prompt are unavailable.

Quick HTTPS with Cloudflare Tunnel (free, no account for quick tunnels):

# Install: brew install cloudflared (macOS) or see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel --url http://localhost:3000

This gives you a temporary https://....trycloudflare.com URL.

Then install on your device:

  • iOS: Open the HTTPS URL in Safari, tap Share, then Add to Home Screen
  • Android: Open in Chrome, tap the menu, then Install App or Add to Home Screen

Once installed, blinks opens as a standalone fullscreen app. The system share sheet includes "blinks" as a target -- share any URL from your browser or other apps and it auto-saves to your knowledge graph.


How It Works

Ingestion pipeline

When you add a URL:

  1. Content extraction -- blinks fetches the page, runs it through defuddle for reader-mode text extraction, and pulls OpenGraph metadata (title, description, image) via open-graph-scraper. Special handling for Twitter/X (via FxTwitter API, no auth needed), YouTube, GitHub, and other platforms.
  2. LLM classification -- the extracted text (truncated to 4000 characters) is sent to your LLM provider, which returns: a cleaned title, summary, key concepts, "why it matters" analysis, resource type, and topic tags.
  3. Topic creation -- new topics are created as needed, and the resource is linked to its topics. Related topics are connected via topicLinks.
  4. Connection discovery -- blinks runs a second LLM pass using a TF-IDF pre-filter (ranks all existing resources by text similarity, takes the top 18 candidates) and then asks the LLM to judge which are genuinely related. Connections require a minimum 0.72 confidence score and are typed (same_subject, builds_on, contrasts, applies, source_reference, duplicate). Each resource gets at most 4 connections.

Graph

The main page renders an interactive force-directed graph using react-force-graph-2d. Nodes represent resources, sized and colored by topic. Click a node to open its detail panel. Drag to reposition nodes -- positions are saved server-side.

Chat

Chat loads your entire knowledge base (all topics, resources, and links) as context, then streams LLM responses. You can file chat answers back into your knowledge base as either:

  • Enhance topic -- appends the answer to a topic's description under a "Q&A Insights" section
  • New resource -- creates a new note-type resource linked to a topic

Press Cmd+K to open the search palette. It searches across topic names, topic descriptions, resource titles, resource summaries, authors, and sources using SQL LIKE matching.

Graph rebuild

If you change your LLM provider or want to re-evaluate all connections with a different model, go to /settings and click Rebuild. This re-runs the connection discovery pipeline on every resource pair. Existing resource data, topics, and saved node positions are preserved -- only the LLM-generated connections are replaced.

Wiki compilation (API only)

blinks can synthesize all resources under a topic into a coherent article. This is available via the API only (no UI):

# Compile a single topic
curl -X POST http://localhost:3000/api/compile \
  -H 'Content-Type: application/json' \
  -d '{"topicId": "<topic-id>"}'

# Compile all topics
curl -X POST http://localhost:3000/api/compile \
  -H 'Content-Type: application/json' \
  -d '{"topicId": "all"}'

# Check compilation status
curl http://localhost:3000/api/compile

Environment Variables Reference

All variables are set in .env (or .env.local). Restart the dev server after any change.

LLM Provider

VariableRequiredDefaultDescription
LLM_PROVIDERNoopenaiWhich provider to use: openai, claude, or ollama

OpenAI

VariableRequiredDefaultDescription
OPENAI_API_KEYWhen provider is openai--Your OpenAI API key
OPENAI_MODELNogpt-5.5Model for chat and general use
OPENAI_CLASSIFICATION_MODELNogpt-5-miniModel for resource classification
OPENAI_CONNECTION_MODELNogpt-5.4-miniModel for connection discovery
OPENAI_JSON_FALLBACK_MODELNogpt-5.4-miniFallback model when classification JSON parsing fails

Anthropic (Claude)

VariableRequiredDefaultDescription
ANTHROPIC_API_KEYWhen provider is claude--Your Anthropic API key
CLAUDE_MODELNoclaude-sonnet-4-20250514Model for all Claude tasks
CLAUDE_CLASSIFICATION_MODELNoSame as CLAUDE_MODELOverride model for classification
CLAUDE_CONNECTION_MODELNoSame as CLAUDE_MODELOverride model for connections
CLAUDE_JSON_FALLBACK_MODELNoclaude-sonnet-4-20250514Fallback model for classification retries

Ollama

VariableRequiredDefaultDescription
OLLAMA_MODELNollama3.2Ollama model name (used for all tasks)
OLLAMA_BASE_URLNohttp://localhost:11434Ollama server address

Authentication

VariableRequiredDefaultDescription
AUTH_USERNAMENo--Set together with AUTH_PASSWORD to enable login wall
AUTH_PASSWORDNo--Set together with AUTH_USERNAME to enable login wall

Database

blinks uses Cloudflare D1 (SQLite) locally via Wrangler. The database files are stored under .wrangler/state/ (gitignored).

Initialize or reset

# Apply migrations (first run or after pulling new migrations)
npm run db:migrate:local

# Full reset (deletes all data, starts fresh)
rm -rf .wrangler/state/v3/d1
npm run db:migrate:local

Schema

The database has 8 tables:

TablePurpose
topicsTopic names and descriptions
resourcesSaved URLs with title, summary, type, author, source, thumbnail
resourceTopicsMany-to-many link between resources and topics
topicLinksConnections between related topics
resourceLinksLLM-judged connections between resources (with type, reason, confidence)
graphPositionsSaved x/y positions for graph nodes
wikiCompilationsStatus tracking for wiki compilation per topic
lintResultsKnowledge base quality audit findings (internal)

Migrations

Migrations live in the drizzle/ directory and are generated by Drizzle Kit:

# Generate a new migration after changing src/lib/db/schema.ts
npx drizzle-kit generate

# Apply migrations locally
npm run db:migrate:local

Scripts

ScriptCommandDescription
npm run devnext devStart development server
npm run buildnext build --webpackProduction build
npm startnext startStart production server
npm run linteslintRun ESLint
npm testnode --test ...Run unit tests (connection ranking)
npm run db:init:localwrangler d1 execute ...Initialize local D1 from raw SQL
npm run db:migrate:localwrangler d1 migrations apply ...Apply Drizzle migrations locally
npm run build:workernpx @opennextjs/cloudflare buildBuild for Cloudflare Workers
npm run previewbuild:worker + wrangler devLocal Cloudflare Workers preview
npm run deploybuild:worker + wrangler deployBuild and deploy to Cloudflare

Deploy to Cloudflare (Optional)

For a permanent deployment with HTTPS (also enables full PWA on mobile):

  1. Create a D1 database on Cloudflare:

    wrangler d1 create blinks-db
    
  2. Update wrangler.jsonc with the database_id from the output above.

  3. Apply migrations to the remote database:

    wrangler d1 migrations apply blinks-db --remote
    
  4. Set your secrets:

    # For OpenAI
    wrangler secret put OPENAI_API_KEY
    
    # For Claude
    wrangler secret put ANTHROPIC_API_KEY
    
    # For auth (optional)
    wrangler secret put AUTH_USERNAME
    wrangler secret put AUTH_PASSWORD
    
  5. Deploy:

    npm run deploy
    

The deployment uses OpenNext to compile the Next.js app into a Cloudflare Worker. The wrangler.jsonc file configures the Worker name, D1 binding, and static assets.


API Reference

All endpoints are under /api/. Responses are JSON unless noted.

EndpointMethodDescription
/api/ingestPOSTIngest a URL: {url, notes?}. Returns classified resource with topics. 409 if duplicate.
/api/resourcesGETList all resources with topic IDs
/api/resourcesPOSTCreate a resource manually (no LLM classification)
/api/resources/[id]PATCHArchive or unarchive: {action: "archive" | "unarchive"}
/api/resources/[id]DELETEPermanently delete a resource
/api/resources/archivedGETList archived resources
/api/topicsGETList all topics
/api/topicsPOSTCreate a topic
/api/topics/[id]GETGet a topic with its resources
/api/graphGETGet all graph nodes and resource-to-resource links
/api/graphPOSTSave node positions: {positions: {id, x, y}[]}
/api/graph/rebuildGETGet connection stats (resource count, connection count)
/api/graph/rebuildPOSTRe-run connection discovery on all resources
/api/chatPOSTStreaming chat: {messages: ChatMessage[]}
/api/filePOSTFile chat output: {content, action, topicId, title?}
/api/searchGETSearch: ?q=query
/api/compilePOSTWiki compilation: {topicId} or {topicId: "all"}
/api/compileGETCompilation status: ?topicId=... or all
/api/settingsGETCurrent config (no secrets exposed)
/api/auth/loginPOSTAuthenticate: {username, password}

Project Structure

blinks/
  src/
    app/
      api/            # API routes (ingest, chat, graph, resources, etc.)
      login/          # Login page (shown when auth is enabled)
      settings/       # Settings page
      share/          # PWA share target handler
      page.tsx        # Main graph page
      layout.tsx      # Root layout with PWA meta tags
      manifest.ts     # PWA manifest
      globals.css     # Theme variables (light/dark)
    components/
      Graph.tsx       # Force-directed graph visualization
      SidePanel.tsx   # Resource detail panel
      ChatPanel.tsx   # Chat interface
      SearchPalette.tsx  # Search overlay
      AddResourceModal.tsx  # URL input modal
      ArchivedPanel.tsx    # Archived resources
      AppProvider.tsx      # Theme + global state
    lib/
      db/             # Drizzle schema and database connection
      llm/            # LLM abstraction (OpenAI, Claude, Ollama)
      content/        # URL content fetcher and parser
      graph/          # Connection ranking (TF-IDF)
      vault/          # Markdown vault export (stubs)
    middleware.ts     # Optional auth middleware
  drizzle/            # SQL migration files
  public/             # Static assets (icons, service worker)
  wrangler.jsonc      # Cloudflare Workers config
  .env.example        # Environment variable template

Troubleshooting

"OPENAI_API_KEY is required" or "ANTHROPIC_API_KEY is required" Check your .env file, make sure the key is set for your chosen LLM_PROVIDER, then restart npm run dev.

"no such table ..." Run npm run db:migrate:local to apply migrations.

Ollama: classification fails or returns empty results

  • Make sure ollama serve is running.
  • Verify the model is pulled: ollama list should show your model.
  • Try a larger model (mistral or qwen2.5) if JSON output is consistently malformed.
  • Check OLLAMA_BASE_URL if Ollama runs on a non-default port or host.
  • Check /settings -- the Ollama status badge should show Connected.

Ollama: "fetch failed" or connection refused

  • Ollama defaults to http://localhost:11434. If you changed the port, set OLLAMA_BASE_URL in .env.
  • On Linux, check if Ollama is bound to 127.0.0.1 vs 0.0.0.0.

Mobile: can't reach the app from phone

  • Confirm your phone and computer are on the same WiFi network.
  • Check your firewall allows incoming connections on port 3000.
  • Use http://<ip>:3000, not https.

Mobile: "Install App" option not available PWA install requires HTTPS. Use cloudflared tunnel or deploy to Cloudflare (see sections above).

Wrangler / D1 commands fail Upgrade to Node 22+ (node --version).

Graph looks empty after adding resources Check the browser console for errors. The most common cause is an LLM API key issue -- go to /settings to verify.

Chat returns "LLM request failed" This is a generic error to avoid leaking API details. Check the server terminal for the actual error. Common causes: expired API key, rate limit, or network issue.


Roadmap

  • Provider selection from the Settings UI (no restart required)
  • Support for more providers (Gemini, Groq, Mistral API)

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md.

License

MIT -- see LICENSE.

Contributors

RishabhKodes

47 commits

RishabhKodes/blinks

An AI-powered knowledge graph that saves, classifies, and connects your web resources into an interactive graph you can chat with.

6

stars

47

commits

TypeScript

primary language

Sep 5, 2026

updated

README

blinks

A local-first, AI-powered knowledge graph for your saved web resources. Paste a URL, and blinks fetches the content, classifies it with an LLM, discovers connections between your resources, and renders everything as an interactive graph you can explore and chat with.

blinks knowledge graph overview Resource detail panel with connections

Bring Your Own Key -- blinks does not ship any API keys. You supply your own OpenAI, Anthropic, or Ollama setup. All LLM calls run against your credentials, on your account. Nothing is proxied through a third party.


Features

  • Save and classify -- paste a URL and blinks fetches the page content, generates a title, summary, key concepts, and topic tags via LLM
  • Knowledge graph -- resources and topics render as an interactive force-directed graph with drag, zoom, and click-to-inspect
  • Semantic connections -- a second LLM pass finds typed relationships between resources (same subject, builds on, contrasts, applies, source reference, duplicate)
  • Chat -- ask questions about your saved resources; the LLM answers grounded in your knowledge base, with the option to file answers back as notes
  • Search -- full-text search across all topics, resources, titles, summaries, and authors
  • Dark and light mode -- toggle between themes; preference is saved in your browser
  • PWA -- installable on mobile and desktop with share-target support (share URLs directly from other apps into blinks)
  • Authentication -- optional login wall via environment variables to protect access on shared networks
  • Three LLM providers -- OpenAI (cloud), Anthropic/Claude (cloud), or Ollama (local, fully offline)
  • Supported sources -- auto-detects Twitter/X, YouTube, GitHub, ArXiv, Medium, Reddit, Substack, Spotify, Hacker News, Stack Overflow, Wikipedia, and any generic webpage

How to use the app

To add a resource, click + Add (or press Ctrl+N), paste the resource's URL, and submit it. Blinks will fetch and process the resource, then add it to your knowledge graph.


Stack

LayerTechnology
FrameworkNext.js 16 (App Router)
UIReact 19, TypeScript, Tailwind CSS 4
DatabaseCloudflare D1 (SQLite) via Drizzle ORM
Graphreact-force-graph-2d + dagre layout
LLMOpenAI, Anthropic, or Ollama (raw fetch, no SDK)
Content extractiondefuddle (reader mode) + open-graph-scraper
DeploymentOpenNext on Cloudflare Workers (optional)

Quickstart

1. Prerequisites

2. Clone and install

git clone https://github.com/RishabhKodes/blinks.git
cd blinks
npm install

3. Configure environment

cp .env.example .env

Open .env and set your provider. Pick one of the three options below:

Option A -- OpenAI (default)

LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...

Option B -- Anthropic (Claude)

LLM_PROVIDER=claude
ANTHROPIC_API_KEY=sk-ant-...

Option C -- Ollama (local, no key needed)

LLM_PROVIDER=ollama
OLLAMA_MODEL=llama3.2

Make sure ollama serve is running before you start the app. See the Ollama section below for full setup.

4. Initialize the database

npm run db:migrate:local

This applies the SQL migrations from drizzle/ to your local D1 SQLite database stored under .wrangler/state/.

5. Start the app

npm run dev

Open http://localhost:3000.

6. Verify everything works

  1. Go to /settings and confirm your provider and key status show green.
  2. Click + Add (or press Ctrl+N) and paste any URL.
  3. Confirm the resource appears as a node in the graph.
  4. Open Chat (Cmd+J / Ctrl+J) and ask a question to verify LLM streaming works.

LLM Providers

blinks supports three providers. Set LLM_PROVIDER in your .env to switch between them. You must restart the dev server after changing this value.

OpenAI

The default provider. Requires OPENAI_API_KEY.

LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...

OpenAI uses separate models for different tasks. You can override each one individually or use the defaults:

VariableDefaultUsed for
OPENAI_MODELgpt-5.5Chat and general LLM calls
OPENAI_CLASSIFICATION_MODELgpt-5-miniResource classification (title, summary, topics)
OPENAI_CONNECTION_MODELgpt-5.4-miniJudging connections between resources
OPENAI_JSON_FALLBACK_MODELgpt-5.4-miniRetry model when classification JSON parsing fails

Anthropic (Claude)

Requires ANTHROPIC_API_KEY.

LLM_PROVIDER=claude
ANTHROPIC_API_KEY=sk-ant-...

Claude also supports per-task model overrides:

VariableDefaultUsed for
CLAUDE_MODELclaude-sonnet-4-20250514All tasks (chat, classification, connections)
CLAUDE_CLASSIFICATION_MODELSame as CLAUDE_MODELResource classification override
CLAUDE_CONNECTION_MODELSame as CLAUDE_MODELConnection discovery override
CLAUDE_JSON_FALLBACK_MODELclaude-sonnet-4-20250514Retry model for classification

Ollama (Local Models)

Ollama lets you run LLMs locally with no cloud API key and no data leaving your machine. blinks talks to Ollama via its OpenAI-compatible API.

Setup:

  1. Install Ollama from ollama.com.

  2. Pull a model:

    ollama pull llama3.2
    
  3. Start the Ollama server (if not already running):

    ollama serve
    
  4. Set your .env:

    LLM_PROVIDER=ollama
    OLLAMA_MODEL=llama3.2
    # OLLAMA_BASE_URL=http://localhost:11434   # default, change only if needed
    
  5. Start blinks: npm run dev

  6. Go to /settings and confirm the Ollama status badge shows Connected.

Recommended models:

blinks requires models that follow JSON instructions reliably. Good options:

ModelSizeNotes
llama3.23BFast, good JSON output, low RAM usage
mistral7BStrong instruction following
qwen2.57BGood multilingual + JSON support
llama3.370BBest quality, needs 48GB+ RAM

Ollama uses a single model for all tasks (classification, connections, chat). Set it via OLLAMA_MODEL.

blinks has built-in JSON repair for malformed LLM output, so even smaller models work reasonably well. If classification consistently fails, try a larger model.


Authentication

blinks includes an optional login wall. When enabled, all pages (except the login page itself) require a username and password.

To enable:

Set both variables in your .env:

AUTH_USERNAME=admin
AUTH_PASSWORD=your-password-here

To disable: remove or leave both variables unset.

How it works:

  • On login, the credentials are hashed (SHA-256) and stored as a session cookie (blinks-session).
  • Sessions last 30 days.
  • The cookie is HTTP-only and uses the secure flag in production.
  • If either AUTH_USERNAME or AUTH_PASSWORD is missing, auth is completely bypassed and the app is open to anyone who can reach it.

This is useful when you access blinks over your local network or expose it via a tunnel.


Keyboard Shortcuts

ShortcutAction
Ctrl+NOpen the Add Resource modal
Cmd+K / Ctrl+KToggle Search palette
Cmd+J / Ctrl+JToggle Chat panel
EscapeClose any open modal, panel, or palette
EnterSend a chat message
Arrow Up / Arrow DownNavigate search results

Dark and Light Mode

blinks supports both dark and light themes. Toggle using the theme button in the top toolbar. Your preference is saved in localStorage and persists across sessions.

The default theme is dark. An inline script prevents flash-of-unstyled-content on page load by applying the saved theme class before first paint.


Mobile Access

The blinks UI is fully responsive -- the graph, panels, modals, and chat all adapt to small screens.

Browser access (works immediately)

Next.js 16 binds to all network interfaces by default, so npm run dev is already reachable from any device on the same WiFi.

  1. Find your local IP:

    # macOS
    ipconfig getifaddr en0
    
    # Linux
    hostname -I | awk '{print $1}'
    
    # Windows
    ipconfig | findstr IPv4
    
  2. On your phone, open http://<your-ip>:3000

The full app works in the mobile browser -- browse the graph, add URLs, chat, search, everything.

Tip: If you are on a shared network, enable authentication to protect access.

Install as app (requires HTTPS)

For the full PWA experience (standalone app window, "blinks" in the system share sheet), you need HTTPS. Over plain HTTP the app works fine in the browser, but the service worker and install prompt are unavailable.

Quick HTTPS with Cloudflare Tunnel (free, no account for quick tunnels):

# Install: brew install cloudflared (macOS) or see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel --url http://localhost:3000

This gives you a temporary https://....trycloudflare.com URL.

Then install on your device:

  • iOS: Open the HTTPS URL in Safari, tap Share, then Add to Home Screen
  • Android: Open in Chrome, tap the menu, then Install App or Add to Home Screen

Once installed, blinks opens as a standalone fullscreen app. The system share sheet includes "blinks" as a target -- share any URL from your browser or other apps and it auto-saves to your knowledge graph.


How It Works

Ingestion pipeline

When you add a URL:

  1. Content extraction -- blinks fetches the page, runs it through defuddle for reader-mode text extraction, and pulls OpenGraph metadata (title, description, image) via open-graph-scraper. Special handling for Twitter/X (via FxTwitter API, no auth needed), YouTube, GitHub, and other platforms.
  2. LLM classification -- the extracted text (truncated to 4000 characters) is sent to your LLM provider, which returns: a cleaned title, summary, key concepts, "why it matters" analysis, resource type, and topic tags.
  3. Topic creation -- new topics are created as needed, and the resource is linked to its topics. Related topics are connected via topicLinks.
  4. Connection discovery -- blinks runs a second LLM pass using a TF-IDF pre-filter (ranks all existing resources by text similarity, takes the top 18 candidates) and then asks the LLM to judge which are genuinely related. Connections require a minimum 0.72 confidence score and are typed (same_subject, builds_on, contrasts, applies, source_reference, duplicate). Each resource gets at most 4 connections.

Graph

The main page renders an interactive force-directed graph using react-force-graph-2d. Nodes represent resources, sized and colored by topic. Click a node to open its detail panel. Drag to reposition nodes -- positions are saved server-side.

Chat

Chat loads your entire knowledge base (all topics, resources, and links) as context, then streams LLM responses. You can file chat answers back into your knowledge base as either:

  • Enhance topic -- appends the answer to a topic's description under a "Q&A Insights" section
  • New resource -- creates a new note-type resource linked to a topic

Press Cmd+K to open the search palette. It searches across topic names, topic descriptions, resource titles, resource summaries, authors, and sources using SQL LIKE matching.

Graph rebuild

If you change your LLM provider or want to re-evaluate all connections with a different model, go to /settings and click Rebuild. This re-runs the connection discovery pipeline on every resource pair. Existing resource data, topics, and saved node positions are preserved -- only the LLM-generated connections are replaced.

Wiki compilation (API only)

blinks can synthesize all resources under a topic into a coherent article. This is available via the API only (no UI):

# Compile a single topic
curl -X POST http://localhost:3000/api/compile \
  -H 'Content-Type: application/json' \
  -d '{"topicId": "<topic-id>"}'

# Compile all topics
curl -X POST http://localhost:3000/api/compile \
  -H 'Content-Type: application/json' \
  -d '{"topicId": "all"}'

# Check compilation status
curl http://localhost:3000/api/compile

Environment Variables Reference

All variables are set in .env (or .env.local). Restart the dev server after any change.

LLM Provider

VariableRequiredDefaultDescription
LLM_PROVIDERNoopenaiWhich provider to use: openai, claude, or ollama

OpenAI

VariableRequiredDefaultDescription
OPENAI_API_KEYWhen provider is openai--Your OpenAI API key
OPENAI_MODELNogpt-5.5Model for chat and general use
OPENAI_CLASSIFICATION_MODELNogpt-5-miniModel for resource classification
OPENAI_CONNECTION_MODELNogpt-5.4-miniModel for connection discovery
OPENAI_JSON_FALLBACK_MODELNogpt-5.4-miniFallback model when classification JSON parsing fails

Anthropic (Claude)

VariableRequiredDefaultDescription
ANTHROPIC_API_KEYWhen provider is claude--Your Anthropic API key
CLAUDE_MODELNoclaude-sonnet-4-20250514Model for all Claude tasks
CLAUDE_CLASSIFICATION_MODELNoSame as CLAUDE_MODELOverride model for classification
CLAUDE_CONNECTION_MODELNoSame as CLAUDE_MODELOverride model for connections
CLAUDE_JSON_FALLBACK_MODELNoclaude-sonnet-4-20250514Fallback model for classification retries

Ollama

VariableRequiredDefaultDescription
OLLAMA_MODELNollama3.2Ollama model name (used for all tasks)
OLLAMA_BASE_URLNohttp://localhost:11434Ollama server address

Authentication

VariableRequiredDefaultDescription
AUTH_USERNAMENo--Set together with AUTH_PASSWORD to enable login wall
AUTH_PASSWORDNo--Set together with AUTH_USERNAME to enable login wall

Database

blinks uses Cloudflare D1 (SQLite) locally via Wrangler. The database files are stored under .wrangler/state/ (gitignored).

Initialize or reset

# Apply migrations (first run or after pulling new migrations)
npm run db:migrate:local

# Full reset (deletes all data, starts fresh)
rm -rf .wrangler/state/v3/d1
npm run db:migrate:local

Schema

The database has 8 tables:

TablePurpose
topicsTopic names and descriptions
resourcesSaved URLs with title, summary, type, author, source, thumbnail
resourceTopicsMany-to-many link between resources and topics
topicLinksConnections between related topics
resourceLinksLLM-judged connections between resources (with type, reason, confidence)
graphPositionsSaved x/y positions for graph nodes
wikiCompilationsStatus tracking for wiki compilation per topic
lintResultsKnowledge base quality audit findings (internal)

Migrations

Migrations live in the drizzle/ directory and are generated by Drizzle Kit:

# Generate a new migration after changing src/lib/db/schema.ts
npx drizzle-kit generate

# Apply migrations locally
npm run db:migrate:local

Scripts

ScriptCommandDescription
npm run devnext devStart development server
npm run buildnext build --webpackProduction build
npm startnext startStart production server
npm run linteslintRun ESLint
npm testnode --test ...Run unit tests (connection ranking)
npm run db:init:localwrangler d1 execute ...Initialize local D1 from raw SQL
npm run db:migrate:localwrangler d1 migrations apply ...Apply Drizzle migrations locally
npm run build:workernpx @opennextjs/cloudflare buildBuild for Cloudflare Workers
npm run previewbuild:worker + wrangler devLocal Cloudflare Workers preview
npm run deploybuild:worker + wrangler deployBuild and deploy to Cloudflare

Deploy to Cloudflare (Optional)

For a permanent deployment with HTTPS (also enables full PWA on mobile):

  1. Create a D1 database on Cloudflare:

    wrangler d1 create blinks-db
    
  2. Update wrangler.jsonc with the database_id from the output above.

  3. Apply migrations to the remote database:

    wrangler d1 migrations apply blinks-db --remote
    
  4. Set your secrets:

    # For OpenAI
    wrangler secret put OPENAI_API_KEY
    
    # For Claude
    wrangler secret put ANTHROPIC_API_KEY
    
    # For auth (optional)
    wrangler secret put AUTH_USERNAME
    wrangler secret put AUTH_PASSWORD
    
  5. Deploy:

    npm run deploy
    

The deployment uses OpenNext to compile the Next.js app into a Cloudflare Worker. The wrangler.jsonc file configures the Worker name, D1 binding, and static assets.


API Reference

All endpoints are under /api/. Responses are JSON unless noted.

EndpointMethodDescription
/api/ingestPOSTIngest a URL: {url, notes?}. Returns classified resource with topics. 409 if duplicate.
/api/resourcesGETList all resources with topic IDs
/api/resourcesPOSTCreate a resource manually (no LLM classification)
/api/resources/[id]PATCHArchive or unarchive: {action: "archive" | "unarchive"}
/api/resources/[id]DELETEPermanently delete a resource
/api/resources/archivedGETList archived resources
/api/topicsGETList all topics
/api/topicsPOSTCreate a topic
/api/topics/[id]GETGet a topic with its resources
/api/graphGETGet all graph nodes and resource-to-resource links
/api/graphPOSTSave node positions: {positions: {id, x, y}[]}
/api/graph/rebuildGETGet connection stats (resource count, connection count)
/api/graph/rebuildPOSTRe-run connection discovery on all resources
/api/chatPOSTStreaming chat: {messages: ChatMessage[]}
/api/filePOSTFile chat output: {content, action, topicId, title?}
/api/searchGETSearch: ?q=query
/api/compilePOSTWiki compilation: {topicId} or {topicId: "all"}
/api/compileGETCompilation status: ?topicId=... or all
/api/settingsGETCurrent config (no secrets exposed)
/api/auth/loginPOSTAuthenticate: {username, password}

Project Structure

blinks/
  src/
    app/
      api/            # API routes (ingest, chat, graph, resources, etc.)
      login/          # Login page (shown when auth is enabled)
      settings/       # Settings page
      share/          # PWA share target handler
      page.tsx        # Main graph page
      layout.tsx      # Root layout with PWA meta tags
      manifest.ts     # PWA manifest
      globals.css     # Theme variables (light/dark)
    components/
      Graph.tsx       # Force-directed graph visualization
      SidePanel.tsx   # Resource detail panel
      ChatPanel.tsx   # Chat interface
      SearchPalette.tsx  # Search overlay
      AddResourceModal.tsx  # URL input modal
      ArchivedPanel.tsx    # Archived resources
      AppProvider.tsx      # Theme + global state
    lib/
      db/             # Drizzle schema and database connection
      llm/            # LLM abstraction (OpenAI, Claude, Ollama)
      content/        # URL content fetcher and parser
      graph/          # Connection ranking (TF-IDF)
      vault/          # Markdown vault export (stubs)
    middleware.ts     # Optional auth middleware
  drizzle/            # SQL migration files
  public/             # Static assets (icons, service worker)
  wrangler.jsonc      # Cloudflare Workers config
  .env.example        # Environment variable template

Troubleshooting

"OPENAI_API_KEY is required" or "ANTHROPIC_API_KEY is required" Check your .env file, make sure the key is set for your chosen LLM_PROVIDER, then restart npm run dev.

"no such table ..." Run npm run db:migrate:local to apply migrations.

Ollama: classification fails or returns empty results

  • Make sure ollama serve is running.
  • Verify the model is pulled: ollama list should show your model.
  • Try a larger model (mistral or qwen2.5) if JSON output is consistently malformed.
  • Check OLLAMA_BASE_URL if Ollama runs on a non-default port or host.
  • Check /settings -- the Ollama status badge should show Connected.

Ollama: "fetch failed" or connection refused

  • Ollama defaults to http://localhost:11434. If you changed the port, set OLLAMA_BASE_URL in .env.
  • On Linux, check if Ollama is bound to 127.0.0.1 vs 0.0.0.0.

Mobile: can't reach the app from phone

  • Confirm your phone and computer are on the same WiFi network.
  • Check your firewall allows incoming connections on port 3000.
  • Use http://<ip>:3000, not https.

Mobile: "Install App" option not available PWA install requires HTTPS. Use cloudflared tunnel or deploy to Cloudflare (see sections above).

Wrangler / D1 commands fail Upgrade to Node 22+ (node --version).

Graph looks empty after adding resources Check the browser console for errors. The most common cause is an LLM API key issue -- go to /settings to verify.

Chat returns "LLM request failed" This is a generic error to avoid leaking API details. Check the server terminal for the actual error. Common causes: expired API key, rate limit, or network issue.


Roadmap

  • Provider selection from the Settings UI (no restart required)
  • Support for more providers (Gemini, Groq, Mistral API)

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md.

License

MIT -- see LICENSE.

Contributors

RishabhKodes

47 commits

Languages

TypeScript

97.9%

CSS

1.5%