OpenKnowledge

v0.10.0

Part of the OpenKnowledge changelog.

Minor Changes

  • Adopt GPL-3.0-or-later. Open Knowledge is now licensed under the GNU General Public License v3.0 or later: every package declares the license field, the GPLv3 LICENSE text ships in the npm tarball and the desktop app bundle, ok --version prints the standard GPL notice (copyright, free software, no warranty), and the desktop About panel carries the same notice.

  • Add opt-in semantic search to the MCP search tool, powered by a remote OpenAI-compatible embeddings API. When enabled, an embeddings signal is fused into full_text ranking so an agent's query surfaces conceptually-related pages even when they share no keywords (e.g. "auth retries" → a page titled "Session Token Refresh"). It is additive and off by default: with the flag off, search is byte-for-byte today's lexical ranking, and the cmd-K omnibar always stays purely lexical and instant.

    Privacy / egress. When the feature is enabled and a key is set, the search query and matching page content are sent to the configured embeddings provider. Egress is explicit and scoped: only corpus content is embedded — anything excluded by .okignore / .gitignore is never sent — embedding is lazy (nothing leaves the machine until an agent actually runs a semantic search), and the key lives only in a 0600 ~/.ok/secrets.yml file (never in config, logs, or telemetry). If the key is missing, the provider errors, or you're offline, search silently degrades to lexical and never blocks.

    Setup (per machine): ok embeddings set-key stores the provider key in ~/.ok/secrets.yml (0600); set search.semantic.enabled: true in project-local config. ok embeddings status shows capability. Provider base URL / model / dimensions are configurable (Azure / self-hosted / other). Vectors cache incrementally under .ok/local/ (content-addressed, keyed by provider + model + dimensions). See the "Semantic search" section of the configuration reference for full setup.

  • Add ok <file> — open a single markdown file in the editor with zero project setup. Run ok notes.md (or ok open notes.md) on any .md / .mdx file and it opens in the WYSIWYG ↔ source editor.

    • Project-aware. When the file lives inside an existing Open Knowledge project (an ancestor .ok/), it opens that project focused on the file — the path is realpath-resolved first, so a symlink into a project routes correctly.
    • No-project mode. For a loose file, it opens an ephemeral single-file session: a throwaway server scoped to just that one file, with git, MCP, and agents off. Edits save straight back to your original file. No .ok/ or other state is written into your directory — all session state lives in a temporary directory that's removed when you close the window.
    • Opening never reformats. A file you open but don't edit is left byte-for-byte identical, even when it has an unstable markdown round-trip.
    • Desktop-first with a browser fallback. Opens in the Open Knowledge desktop app when it's installed; otherwise serves the editor in your browser (Ctrl-C to end the session). Launching from a closed app goes straight to the file in a single window — it doesn't reopen your last project alongside it.
    • Focused chrome. No-project sessions hide the file sidebar, tabs, project switcher, and Settings — just the file and the editor.

    Discoverable via ok --help.

Patch Changes

  • Clicking a link to a non-markdown file (HTML, archives, Office docs, and other non-viewable types) now opens the same in-app preview the sidebar shows when you select that file, instead of handing the file straight to the operating system.

    Previously, once these links resolved, a bare click immediately delegated to the OS (or, for HTML and other scripted types the desktop app refuses to open directly, revealed the file in Finder). A click now lands on the file's preview screen — for non-viewable types that screen offers "Open file" and "Open with built-in text editor" — so the behavior matches selecting the file in the sidebar. Cmd/Ctrl/middle-click still opens the file in the OS default app (desktop) or a new tab (web) as an escape hatch.

  • Fix links to non-markdown files rendering as "non-existent" (red) links even when the file exists.

    Links to HTML, GPX, XML, archive (.7z/.tar/.rar/…), legacy Office (.doc/.xls/.ppt), OpenDocument, iWork, and e-book files now resolve and serve like any other asset. The editor treats every non-markdown link target as an asset, but the set that governs indexing, serving, and link resolution previously omitted these types, so their links always showed as broken.

    ASSET_EXTENSIONS is now a superset of everything you can embed or link, enforced by a runtime guard so the two sets can't drift apart again. Newly admitted types are served as downloads (and open in the OS default app on desktop). HTML files are served inside a sandboxed, opaque origin (Content-Security-Policy: sandbox allow-scripts; connect-src 'none'): their scripts run so interactive documents work, but they can't read your knowledge base's cookies or storage, and connect-src 'none' blocks them from reaching the local API or exfiltrating over the network. On desktop, clicking an HTML link reveals it in Finder rather than failing silently.

  • Rename the gbrain starter pack to Entity vault (GBrain-compatible) and reposition it as a human cockpit for GBrain-style Markdown brains rather than a GBrain reimplementation. This is a breaking pre-release rename: the canonical pack ID is now entity-vault with no gbrain alias, so ok seed --pack gbrain no longer resolves — use ok seed --pack entity-vault. The pack picker, seed dialog, toast, and CLI list now show "Entity vault (GBrain-compatible)". Generated dossier templates are tightened for GBrain-compatible parsing — document-level title: frontmatter, an explicit --- timeline --- sentinel, and parseable - **YYYY-MM-DD** | source | @author — … Confidence: … timeline bullets with path-qualified [[folder/slug|Label]] links. The workflow doc moved from /workflows/gbrain to /workflows/entity-vault. OK edits and reviews the Markdown; Garry Tan's gbrain, if installed, can still import/sync the same vault — interop is Markdown + Git, with no deep integration implied.

  • Restore the repository field on the published CLI package, pointing at inkeep/open-knowledge. It was dropped when the mirror was re-pointed off open-knowledge-legacy, which left npm Trusted Publishing unable to verify provenance and failed every release publish with E422 ... "repository.url" is "".

  • Desktop: the main process no longer crashes with a recurring "A JavaScript error occurred in the main process — Error: write EPIPE" dialog after every window is closed. On macOS the main process outlives its windows, and the auto-updater's periodic timer keeps writing to the stdout/stderr inherited from the launching terminal; once that terminal closes, each write throws EPIPE, which — with no stream error listener — escalated to an uncaught exception and Electron's fatal-error modal, reappearing every few minutes until the process was killed. A broken-pipe guard installed at the earliest point of main-process boot now handles these benign writes at the stream boundary so they never become an uncaught exception, while genuine (non-broken-pipe) stream errors are still surfaced to the file log. The guard is logger-agnostic, so it covers every stdout/stderr writer in the main process, not just the updater.

  • Fix the editor toolbar's "Open with AI" menu freezing the rest of the app in the macOS desktop app. Once the menu became openable on the desktop host, its default modal behavior disabled pointer events on everything outside the menu while it was open. Because the menu lives in the macOS title-bar drag region — where the outside-click that normally dismisses a modal doesn't reliably reach the menu — the only way to close it was to pick an agent, and meanwhile the rest of the chrome (notably the bottom-left project switcher) couldn't be clicked. The menu is now non-modal: opening it no longer blocks the rest of the UI, and clicking anywhere outside dismisses it. Browsers (ok ui) are unaffected.

  • Fix the sidebar project switcher not opening, and not dismissing on outside click, in the macOS desktop app. Radix's DropdownMenu opens on pointerdown, but the desktop renderer does not receive real pointerdown events (only mousedown/click), so clicking the trigger did nothing; and because the menu was modal, clicking outside could not dismiss it either. The trigger now also opens from the click event on the desktop host (mirroring the editor's "Open with AI" menu), and the menu is non-modal so outside-click dismissal works and the rest of the UI stays interactive while it's open — matching the Cloud/Sync popover. Browsers are unaffected.

  • Fix the project switcher in the sidebar footer not opening in the macOS desktop app. Clicking it showed a press state but the dropdown never opened. A Radix tooltip recently added to the trigger (to show the project path on hover) interfered with the menu on the desktop host: once the tooltip had opened on hover, clicking the trigger raced the tooltip's teardown against the menu's open, so the menu never stayed open. The trigger no longer uses a Radix tooltip — the full project path is shown via a native tooltip instead — and the menu opens reliably.

  • fix(open-knowledge): source-view tables no longer render indented left of surrounding text

    GFM table rows in the Markdown source view rendered roughly 2ch to the left of the surrounding prose, headings, and lists. The source-polish view-plugin set the .cm-table-row / .cm-table-header line classes but never set the --list-hang variable that the base .cm-line rule consumes, so the base !important padding-inline-start overrode the standalone table padding while the table's negative text-indent still applied, pulling table lines into the gutter. Tables now participate in the same hanging-indent mechanism as lists and fenced code, so they line up with body text. (PRD-6922)

  • fix(sync): track push and pull errors separately so a sync error stops flashing in the popover

    The sync status popover would show a sync error (e.g. a failed push) for only a split second before it vanished. The engine stored the last failure in a single shared error field, so a successful fetch on the pull leg cleared a still-unresolved push error — and trigger('sync') runs push then pull, so the push error was wiped by the immediately-following fetch within the same "Sync now". The same alternation happened continuously under auto-sync.

    Push and pull errors are now tracked independently (pushError/pushErrorCode and pullError/pullErrorCode). A success on one leg only clears that leg's error, so a real push failure stays visible until a push actually succeeds (or sync is toggled).

    When both legs fail with the same root cause (e.g. an auth failure that blocks fetch and push alike) the popover collapses them into a single neutral line instead of repeating two near-identical messages. When the two legs fail for different reasons, each line is labeled "Push:" / "Pull:" so it's clear which direction failed. A lone failure renders unlabeled, with pull-specific copy for read-side auth failures.

  • Remove the dismiss X from the "Relaunching to install the update…" banner. That card is a terminal in-progress state shown the instant Relaunch is clicked — there is nothing to dismiss, and the card already disappears when the app restarts, so a manual close would only hide live progress. The armed "Version X ready to install" card keeps its X (the user can still close it and relaunch later); only the post-click in-progress swap is now non-dismissible, via a new dismissible flag on the update-notice shape.

  • Stop surfacing macOS Finder metadata files (.DS_Store, .localized) in the file sidebar's "Show All Files" mode.

    These files were already hidden in the normal sidebar (they're seeded into .gitignore on ok init), but "Show All Files" deliberately bypasses .gitignore / .okignore to surface gitignored content like dist/ and build/ — which re-surfaced .DS_Store as a sidebar asset row. They are now pruned by an always-on junk-file floor, the file-level analogue of the existing directory floor that keeps .git/, node_modules/, and .ok/ hidden even under Show All Files. Legitimate gitignored content still shows.

  • Fix the log and telemetry file-sinks writing into a second .ok/ when content.dir is a sub-folder. Both per-machine sinks (.ok/local/logs/server-current.jsonl, .ok/local/telemetry/spans-current.jsonl) now anchor on the project root like server.lock / principal.json / state.json, so a project with e.g. content.dir: docs keeps a single .ok/ at the root instead of growing a second one buried inside the content sub-folder where backups, git, sync, and visual inspection wouldn't expect it. ok diagnose bundle reads the sinks and server.lock from the same project-root-anchored .ok/local/ so it keeps harvesting them after the move.

  • Open .base and .canvas files (Obsidian Bases / Canvas) directly in the read-only text viewer. Previously, clicking a [[file.base]] or [[file.canvas]] wiki-link opened a chooser pane; clicking "Open file" from the chooser then replaced the editor view with a raw 415 JSON error envelope from the API. Now both file types open immediately in the built-in text viewer (.canvas with JSON syntax highlighting) — no chooser, no broken "Open file". The "Open file" affordance for all other downloadable types (.docx, .zip, etc.) is also hardened: it now routes through the sanctioned asset-dispatch path (OS-handoff on desktop, new tab on web) instead of a same-frame navigation to the asset API.

  • Fix the editor toolbar's "Open with AI" menu not opening in the macOS desktop app. The header sits in a macOS title-bar drag region (-webkit-app-region: drag), and macOS swallows the pointerdown event on its children — even on the no-drag button — before the DOM sees it. Radix's dropdown opens on pointerdown, so clicking the button did nothing. The synthesized click still fires, so on the desktop host the menu now opens from the click instead. Browsers (ok ui) are unaffected and keep Radix's default behavior. This regressed when the button's redundant hover tooltip was removed, which had incidentally kept pointer events flowing to the trigger.

  • fix(open-knowledge): MCP writes reflect on-disk truth — reconcile out-of-band edits instead of silently clobbering them (PRD-6832)

    An OK MCP read could authoritatively return content older than the bytes on disk, with no warning. The root cause was upstream of the read: a doc loaded in the server holds stale in-memory CRDT state, an out-of-band edit (a script, git pull, manual edit) makes disk newer, and the next MCP write serializes the stale CRDT over the newer file — making disk itself stale, so the next read faithfully returns bad bytes. In one incident this gave an agent a stale spec scope and cost a multi-file revert.

    This change makes disk authoritative on the write/reconcile path:

    • Reconcile-before-apply (L1). Before a content write (write_document / edit_document / edit_frontmatter) or a rename applies, the server compares the on-disk bytes to the last-synced base and, on divergence, ingests the disk edit first (through the existing sanctioned file-watcher path) so the agent's edit lands on top of current reality. Both edits survive.
    • Store-time backstop (L3). For the residual few-millisecond window where disk changes between the reconcile check and the store, the store re-checks disk before overwriting. On divergence it aborts the overwrite (disk wins) and the handler returns a hard urn:ok:error:disk-divergence (409) — the agent's edit was NOT applied; re-read and retry (a retry re-applies exactly once). This is the only guard for undo / rollback, which have no L1.
    • Heads-up on reconcile. When a write reconciles an out-of-band edit, the success response carries a disk-edit-reconciled warning (structuredContent.contentDivergence) so the agent re-reads to see the combined result. Observational — the write still landed and both edits are on disk.

    Human-editor (browser) writes are unaffected: the backstop only fires on agent-triggered stores, so in-progress typing is never reverted.

  • Show a human-readable message when a file is too large for the built-in text editor, instead of a bare "Failed to load file (HTTP 413)".

    The built-in text editor caps the file it will load at 1 MB. Opening a larger file (a long GPS track .gpx, a big .csv, a verbose .log) previously surfaced only the raw status code. It now explains that the file exceeds the 1 MB limit and points to the "Open file" action to open it in another app. Other load failures map to plain-language messages too; unexpected statuses keep the code appended so they stay diagnosable.

View v0.10.0 on GitHub