0.2.3: remove routes, simplify config to one entry = one KB

Drop the ambiguous 'routes' key from .oikb.yaml. Multi-KB from a
single source is now done with multiple entries + filter.include.
Every entry is strictly source + kb-id, no branching logic.
This commit is contained in:
Timothy Jaeryang Baek 2026-05-21 12:01:12 +04:00
parent 3c8edaf93f
commit 7c3d36f1b5
6 changed files with 53 additions and 71 deletions

View file

@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.2.3] - 2025-05-21
### Removed
- **Breaking:** `routes` key in `.oikb.yaml`. Use multiple entries with `filter.include` instead — each entry is one source → one KB.
## [0.2.2] - 2025-05-21
### Added
@ -35,7 +41,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Webhook support: /webhooks/github, /webhooks/gitlab, /webhooks/slack, /webhooks/confluence for real-time sync triggers.
- `--json` output flag for `history` command.
- 13 new connectors: Document360, Slab, Outline, Google Sites, Egnyte, Oracle Cloud Storage, ProductBoard, XenForo, Zulip, Gong, Fireflies, DokuWiki, ServiceNow. Total: 44 connectors.
- Multi-KB routing: `routes` key in `.oikb.yaml` to route files by glob pattern to different KBs.
- Selective sync filters: `filter.include` / `filter.exclude` glob patterns to narrow sync scope.
- Daemon doubles as an OpenAPI tool server for Open WebUI (Settings → Connections → Tool Server).

View file

@ -124,21 +124,7 @@ oikb sync servicenow:incident --kb-id your-kb-id
Some connectors need an optional extra: `pip install oikb[gdrive]`, `pip install oikb[s3]`, or `pip install oikb[all]` for everything.
## Multi-KB Routing
Route files from a single source to different Knowledge Bases by glob pattern:
```yaml
sources:
- name: wiki
source: github:owner/repo
kb-id: 8f3a2b1c-...
routes:
"docs/**/*.md": docs-kb-id
"src/**": code-kb-id
```
## Selective Sync Filters
## Filters
Narrow what gets synced with include/exclude globs:
@ -152,6 +138,23 @@ sources:
exclude: ["drafts/**"]
```
To split a single source across multiple Knowledge Bases, use separate entries:
```yaml
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):

View file

@ -1,6 +1,6 @@
[project]
name = "oikb"
version = "0.2.2"
version = "0.2.3"
description = "Sync anything to Open WebUI Knowledge Bases"
readme = "README.md"
authors = [

View file

@ -1,3 +1,3 @@
"""oikb — Open WebUI Knowledge Base CLI."""
__version__ = "0.2.0"
__version__ = "0.2.3"

View file

@ -344,70 +344,44 @@ def sync(
entry_kb = entry.get("kb-id")
entry_branch = entry.get("branch")
entry_path = entry.get("path")
entry_routes = entry.get("routes")
entry_filter = entry.get("filter", {})
if not entry_source or (not entry_kb and not entry_routes):
click.echo(click.style(f"Skipping invalid entry (needs source + kb-id or routes): {entry}", fg="yellow"), err=True)
if not entry_source or not entry_kb:
click.echo(click.style(f"Skipping invalid entry (needs source + kb-id): {entry}", fg="yellow"), err=True)
continue
try:
connector = _resolve_connector(entry_source, entry_branch, entry_path)
client = _make_client(url, token)
# ── Multi-KB routing mode ──
if entry_routes:
if not quiet:
click.echo(f"\n{'' * 40}")
click.echo(f"Syncing: {entry_source}{entry_kb}")
mf = None
if entry_filter.get("include") or entry_filter.get("exclude"):
from oikb.sync import build_manifest_filter
for pattern, route_kb in entry_routes.items():
if not quiet:
click.echo(f"\n{'' * 40}")
click.echo(f"Syncing: {entry_source} [{pattern}] → {route_kb}")
mf = build_manifest_filter(include=[pattern])
result = run_sync(
client=client,
connector=connector,
kb_id=route_kb,
dry_run=dry_run,
verbose=verbose,
quiet=quiet,
manifest_filter=mf,
)
if not quiet:
prefix = "Dry run" if dry_run else "Done"
click.echo(f" {prefix}: {result.summary()}")
if result.errors:
has_errors = True
# ── Standard single-KB mode ──
else:
if not quiet:
click.echo(f"\n{'' * 40}")
click.echo(f"Syncing: {entry_source}{entry_kb}")
mf = None
if entry_filter.get("include") or entry_filter.get("exclude"):
from oikb.sync import build_manifest_filter
mf = build_manifest_filter(
include=entry_filter.get("include"),
exclude=entry_filter.get("exclude"),
)
result = run_sync(
client=client,
connector=connector,
kb_id=entry_kb,
dry_run=dry_run,
verbose=verbose,
quiet=quiet,
manifest_filter=mf,
mf = build_manifest_filter(
include=entry_filter.get("include"),
exclude=entry_filter.get("exclude"),
)
if not quiet:
prefix = "Dry run" if dry_run else "Done"
click.echo(f" {prefix}: {result.summary()}")
result = run_sync(
client=client,
connector=connector,
kb_id=entry_kb,
dry_run=dry_run,
verbose=verbose,
quiet=quiet,
manifest_filter=mf,
)
if result.errors:
has_errors = True
if not quiet:
prefix = "Dry run" if dry_run else "Done"
click.echo(f" {prefix}: {result.summary()}")
if result.errors:
has_errors = True
client.close()

View file

@ -38,7 +38,7 @@ async def verify_api_key(
app = FastAPI(
title="oikb",
description="Sync engine for Open WebUI Knowledge Bases. Trigger syncs, check status, and query history.",
version="0.2.2",
version="0.2.3",
)
# Runtime state populated by start_daemon().