From 878e797b5fdd9fff9a09e77c67f79c096170d960 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 21 May 2026 13:17:23 +0400 Subject: [PATCH] 0.3.2: global defaults key, Docker usage docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit defaults: key in .oikb.yaml sets interval, concurrency, filter, notify, etc. once for all entries — per-entry values override. Deep merges nested dicts. Added standalone docker run example and LOG_FORMAT=json to Docker Compose. --- CHANGELOG.md | 6 ++++++ README.md | 29 ++++++++++++++++++++++++----- pyproject.toml | 2 +- src/oikb/__init__.py | 2 +- src/oikb/cli.py | 24 +++++++++++++++++++++++- src/oikb/daemon.py | 2 +- 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0cba2..8183721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.3.2] - 2025-05-21 + +### Added + +- Global `defaults:` key in `.oikb.yaml` — set `interval`, `concurrency`, `filter`, `notify`, or any other key once and have it apply to all entries. Per-entry values override defaults. Deep merges nested dicts (filter, notify). + ## [0.3.1] - 2025-05-21 ### Added diff --git a/README.md b/README.md index c9aab83..dc6ade9 100644 --- a/README.md +++ b/README.md @@ -56,20 +56,25 @@ Features: ```yaml # .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-... - interval: 1h # simple interval webhook: true - notify: - url: https://hooks.slack.com/services/T.../B.../xxx - on: error # error (default) | always - name: handbook source: confluence:ENG kb-id: 4e7d9a0f-... - interval: "0 6 * * 1-5" # cron: weekdays at 6am + interval: "0 6 * * 1-5" # overrides default ``` ```bash @@ -78,6 +83,19 @@ curl -X POST /sync/wiki # API: trigger by name curl -X POST /sync/8f3a2b1c-.. # API: trigger by kb-id ``` +### Docker + +```bash +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 ```yaml @@ -93,6 +111,7 @@ services: - 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 diff --git a/pyproject.toml b/pyproject.toml index 54268fc..04d5660 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oikb" -version = "0.3.1" +version = "0.3.2" description = "Sync anything to Open WebUI Knowledge Bases" readme = "README.md" authors = [ diff --git a/src/oikb/__init__.py b/src/oikb/__init__.py index c83bd10..7a95269 100644 --- a/src/oikb/__init__.py +++ b/src/oikb/__init__.py @@ -1,3 +1,3 @@ """oikb — Open WebUI Knowledge Base CLI.""" -__version__ = "0.3.1" +__version__ = "0.3.2" diff --git a/src/oikb/cli.py b/src/oikb/cli.py index 51e24be..c4ad192 100644 --- a/src/oikb/cli.py +++ b/src/oikb/cli.py @@ -295,6 +295,17 @@ def _interpolate_env(obj: Any) -> Any: return obj +def _deep_merge(base: dict, override: dict) -> dict: + """Deep merge two dicts. Values in override take precedence.""" + result = base.copy() + for key, val in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(val, dict): + result[key] = _deep_merge(result[key], val) + else: + result[key] = val + return result + + def _load_oikb_yaml() -> list[dict] | None: """Load .oikb.yaml from the current directory if it exists.""" import yaml @@ -314,7 +325,15 @@ def _load_oikb_yaml() -> list[dict] | None: # Prefer sources: (new), fall back to sync: (legacy). entries = data.get("sources") or data.get("sync") - return entries or None + if not entries: + return None + + # Apply global defaults to each entry. + defaults = data.get("defaults", {}) + if defaults: + entries = [_deep_merge(defaults, entry) for entry in entries] + + return entries def _build_cli_filter(max_file_size: str | None): @@ -817,6 +836,9 @@ def daemon(port: int, no_server: bool, config_file: str | None, log_format: str data = yaml.safe_load(f) data = _interpolate_env(data) if data else data entries = (data.get("sources") or data.get("sync", [])) if data else [] + defaults = data.get("defaults", {}) if data else {} + if defaults and entries: + entries = [_deep_merge(defaults, e) for e in entries] else: entries = _load_oikb_yaml() diff --git a/src/oikb/daemon.py b/src/oikb/daemon.py index 04fade2..a763d08 100644 --- a/src/oikb/daemon.py +++ b/src/oikb/daemon.py @@ -40,7 +40,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.3.1", + version="0.3.2", ) # Runtime state populated by start_daemon().