Find a file
psuckrow-routine 35628637fd
feat(zotero): support multiple Zotero libraries via per-source library ID (#93)
* feat(zotero): support multiple libraries via per-source library ID

Embed an optional numeric library ID in the source string to allow
multiple Zotero libraries to be synced from a single daemon instance:

  source: "zotero:123456:"              # all collections in library 123456
  source: "zotero:123456:Research%%ML"  # specific collection

When a library ID is present the connector resolves credentials from
per-library env vars before falling back to the global defaults:

  ZOTERO_API_KEY_<library_id>      → ZOTERO_API_KEY
  ZOTERO_LIBRARY_TYPE_<library_id> → ZOTERO_LIBRARY_TYPE

Existing source strings ("zotero:" or "zotero:Collection%%Sub") are
fully backward-compatible — collection names are not purely numeric so
there is no ambiguity when parsing.

* refactor(zotero): per-library env var resolution via _zotero_env helper

Introduce _zotero_env(key, library_id, default) which checks
ZOTERO_<KEY>_<library_id> before falling back to ZOTERO_<KEY>. Apply
it uniformly to every configurable setting so that each source entry
in .oikb.yaml can be tuned independently when multiple libraries are
in use:

  ZOTERO_CHECKSUM_<id>              overrides  ZOTERO_CHECKSUM
  ZOTERO_INCLUDE_NOTES_<id>                    ZOTERO_INCLUDE_NOTES
  ZOTERO_INCLUDE_ANNOTATIONS_<id>              ZOTERO_INCLUDE_ANNOTATIONS
  ZOTERO_LIBRARY_TYPE_<id>                     ZOTERO_LIBRARY_TYPE
  ZOTERO_EXCLUDE_<id>                          ZOTERO_EXCLUDE
  ZOTERO_UNFILED_DIR_<id>                      ZOTERO_UNFILED_DIR
  ZOTERO_WEBDAV_{URL,USER,PASSWORD}_<id>       ZOTERO_WEBDAV_*

Single-library setups are unaffected — the helper falls through to the
global vars unchanged.

* fix(sync): skip empty file content before upload

Files with no extractable text (e.g. Zotero items with no fulltext and
no downloadable PDF) were uploaded as zero-byte payloads, causing
Open WebUI to reject them with EMPTY_CONTENT errors on every sync cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Pierre-Louis Suckrow <pierreroutine@mac.speedport.ip>
Co-authored-by: Pierre-Louis Suckrow <pierreroutine@MacBook-Air-von-Pierre-Louis.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 15:23:38 -04:00
.github/workflows refac 2026-07-17 16:16:41 -04:00
docs Add hierarchical Confluence paths (#107) 2026-09-16 12:00:22 -04:00
src/oikb feat(zotero): support multiple Zotero libraries via per-source library ID (#93) 2026-09-16 15:23:38 -04:00
tests Add hierarchical Confluence paths (#107) 2026-09-16 12:00:22 -04:00
.dockerignore init 2026-05-20 13:19:16 +04:00
.gitignore init 2026-05-20 13:19:16 +04:00
.python-version init 2026-05-20 13:19:16 +04:00
CHANGELOG.md refac 2026-07-17 15:56:22 -04:00
docker-compose.yaml refac 2026-07-17 15:03:59 -04:00
Dockerfile refac 2026-07-17 16:16:41 -04:00
Dockerfile.alpine refac 2026-07-17 16:16:41 -04:00
LICENSE init 2026-05-20 13:19:16 +04:00
pyproject.toml refac 2026-07-17 15:56:22 -04:00
README.md fix Zotero sync specific collections (#109) 2026-09-16 11:58:53 -04:00

📚 oikb

Keep your Open WebUI Knowledge Bases in sync. Point it at a local directory, a GitHub repo, a Confluence space, an S3 bucket, Zotero, or any of 46 supported sources. Only new and modified files are uploaded via incremental SHA-256 diffing.

Important

Requires Open WebUI 0.9.6+

Quick Start

pip install oikb

export OPEN_WEBUI_URL=http://localhost:3000
export OPEN_WEBUI_API_KEY=sk-your-api-key

# Sync a directory
oikb sync ./docs --kb-id your-kb-id

# Sync a GitHub repo
oikb sync github:owner/repo --kb-id your-kb-id

# Preview first (no upload)
oikb sync ./docs --kb-id your-kb-id --dry-run

For multi-source, scheduled sync, or daemon mode — run oikb init to generate a .oikb.yaml config file, then oikb daemon.

📖 Full Guide — installation, connectors, daemon, enterprise features, deployment, troubleshooting.

Commands

Command Description
oikb init Generate .oikb.yaml interactively
oikb sync <source> Incremental sync to a Knowledge Base
oikb watch <dir> Watch for changes and auto-sync
oikb daemon Long-lived scheduler with HTTP API
oikb diff <source> Preview what a sync would do
oikb validate Validate .oikb.yaml without running
oikb history View sync history
oikb ls List files in a Knowledge Base
oikb status Show KB info and file count
oikb reset Delete all files in a Knowledge Base
oikb config Manage saved URL and API key

Daemon

Run oikb daemon for production deployments. Reads .oikb.yaml and syncs each source on a schedule.

oikb daemon --port 8080

Features:

  • Scheduled sync — simple intervals (30m, 1h) or cron expressions (0 6 * * 1-5)
  • Webhooks — instant sync on push via /webhooks/github, /webhooks/gitlab, /webhooks/slack, /webhooks/confluence
  • Health checks — GET /health for Docker/K8s readiness probes
  • Prometheus metrics — GET /metrics exports sync counters, duration histograms, and error rates
  • Sync history — GET /history queryable log of all syncs
  • On-demand sync — POST /sync/{identifier} trigger by name or kb-id
  • Failure notifications — webhook POST on sync errors, compatible with Slack, PagerDuty, Opsgenie
  • API key auth — set OIKB_API_KEY to secure endpoints (Docker secrets _FILE supported)
  • OpenAPI tool server — add http://oikb:8080 as a Tool Server in Open WebUI (Settings → Connections) and let the LLM trigger syncs, check status, and query history
# .oikb.yaml
defaults:
  interval: 1h
  concurrency: 4
  filter:
    max-size: 50mb
  notify:
    url: https://hooks.slack.com/services/T.../B.../xxx
    on: error

sources:
  - name: wiki
    source: github:owner/repo
    kb-id: 8f3a2b1c-...
    webhook: true

  - name: handbook
    source: confluence:ENG
    kb-id: 4e7d9a0f-...
    interval: "0 6 * * 1-5"   # overrides default
oikb sync --name wiki          # CLI: sync a specific entry
curl -X POST /sync/wiki        # API: trigger by name
curl -X POST /sync/8f3a2b1c-.. # API: trigger by kb-id

Docker

docker run -d \
  -e OPEN_WEBUI_URL=http://open-webui:8080 \
  -e OPEN_WEBUI_API_KEY=sk-... \
  -e OIKB_API_KEY=your-daemon-key \
  -e LOG_FORMAT=json \
  -v ./.oikb.yaml:/app/.oikb.yaml:ro \
  -p 8080:8080 \
  ghcr.io/open-webui/oikb:latest daemon

Docker Compose

services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    ports:
      - "3000:8080"

  oikb:
    image: ghcr.io/open-webui/oikb:latest
    environment:
      - OPEN_WEBUI_URL=http://open-webui:8080
      - OPEN_WEBUI_API_KEY=${OPEN_WEBUI_API_KEY}
      - OIKB_API_KEY=${OIKB_API_KEY}
      - LOG_FORMAT=json
    volumes:
      - ./.oikb.yaml:/app/.oikb.yaml:ro
    command: daemon
    ports:
      - "8080:8080"
    depends_on:
      - open-webui
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/health/ready"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s

46 Connectors

Category Sources
Code Repos GitHub, GitLab, Bitbucket
Cloud Storage S3, GCS, Azure Blob, Dropbox, R2, Google Drive, SharePoint, Nextcloud, Egnyte, Oracle Cloud
Wikis & KBs Confluence, Notion, BookStack, Discourse, GitBook, Guru, Outline, Slab, Document360, DokuWiki, Google Sites
Ticketing Jira, Linear, Zendesk, Freshdesk, Asana, ClickUp, Airtable, ServiceNow, ProductBoard
Messaging Slack, Discord, Microsoft Teams, Gmail, Zulip
Meetings Gong, Fireflies
Forums XenForo
Sales & CRM Salesforce, HubSpot
Web Website / Sitemap crawler
Research Zotero
oikb sync github:owner/repo --kb-id your-kb-id
oikb sync confluence:ENG --kb-id your-kb-id
oikb sync s3://bucket/prefix --kb-id your-kb-id
oikb sync nextcloud:/Documents --kb-id your-kb-id
oikb sync servicenow:incident --kb-id your-kb-id
oikb sync "zotero:Research%%Machine Learning" --kb-id your-kb-id

Some connectors need an optional extra: pip install oikb[gdrive], pip install oikb[s3], pip install oikb[zotero], or pip install oikb[all] for everything.

Zotero

export ZOTERO_LIBRARY_ID=123456
export ZOTERO_API_KEY=...

oikb sync "zotero:" --kb-id your-kb-id # syncs all top-level collections plus _unfiled
oikb sync "zotero:Research" --kb-id your-kb-id # syncs only the 'Research' collection
oikb sync "zotero:Research%%Machine Learning" --kb-id your-kb-id # syncs only the 'Machine Learning' subcollection

Options:

Variable Description
ZOTERO_LIBRARY_TYPE user (default) or group
ZOTERO_INCLUDE_NOTES Append child notes when set to 1, true, yes, or on
ZOTERO_INCLUDE_ANNOTATIONS Append PDF annotation text/comments
ZOTERO_CHECKSUM version (default) or content
ZOTERO_EXCLUDE Comma-separated collection paths to skip
ZOTERO_UNFILED_DIR Directory for root library items, default _unfiled
ZOTERO_WEBDAV_URL WebDAV Zotero storage base; fetches <attachment-key>.zip on Zotero file 404
ZOTERO_WEBDAV_USER / ZOTERO_WEBDAV_PASSWORD WebDAV credentials

Filters

Narrow what gets synced with include/exclude globs and size limits:

sources:
  - name: docs
    source: github:owner/repo
    kb-id: 4e7d9a0f-...
    filter:
      include: ["docs/**/*.md", "*.txt"]
      exclude: ["drafts/**"]
      max-size: 50mb

To split a single source across multiple Knowledge Bases, use separate entries:

sources:
  - name: wiki-docs
    source: github:owner/repo
    kb-id: abc123-...
    filter:
      include: ["docs/**/*.md"]

  - name: wiki-code
    source: github:owner/repo
    kb-id: def456-...
    filter:
      include: ["src/**"]

Configuration

Resolved in order (highest priority wins):

  1. CLI flags (--url, --token)
  2. Environment variables (OPEN_WEBUI_URL, OPEN_WEBUI_API_KEY)
  3. Config file (~/.config/oikb/config.yaml)

All string values in .oikb.yaml support ${VAR} and ${VAR:-default} interpolation:

sources:
  - name: docs
    source: github:${GITHUB_ORG}/docs
    kb-id: ${KB_DOCS_ID}
    token: ${GITHUB_TOKEN}
    notify:
      url: ${SLACK_WEBHOOK:-https://hooks.slack.com/default}

History

oikb history                    # Table view
oikb history --json             # JSON output
oikb history --errors           # Failed syncs only
oikb history --clear --days 7   # Prune old entries

GitHub Actions

- name: Sync docs to Open WebUI
  uses: docker://ghcr.io/open-webui/oikb:latest
  with:
    args: sync /github/workspace/docs --kb-id ${{ secrets.KB_ID }}
  env:
    OPEN_WEBUI_URL: ${{ secrets.OPEN_WEBUI_URL }}
    OPEN_WEBUI_API_KEY: ${{ secrets.OPEN_WEBUI_API_KEY }}

How It Works

  1. Scan source, compute checksums
  2. Send manifest to Open WebUI /sync/diff
  3. Delete stale files, create missing directories
  4. Upload only new and modified files

License

MIT. See LICENSE for details.