# OpenKnowledge Documentation

> Reference for the local-first markdown editor, `ok` CLI, Hocuspocus collaboration server, and MCP tool surface that agents use to read and write knowledge bases stored as git-versioned files.

## Context Links

- [Agent index](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/llms.txt)
- [Human interactive docs](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e)
- [GitHub repository](https://github.com/sashimikun/open-knowledge)

## Repository Metadata

- Repository: sashimikun/open-knowledge

- Generated: 2026-06-25T22:59:07.884Z
- Updated: 2026-06-25T23:00:14.631Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 22

## Page Index

- 01. [Overview](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/01-overview.md) - Monorepo packages, runtime surfaces (desktop app, `ok` CLI web editor, MCP server), and the shortest path from install to first agent-driven edit.
- 02. [Installation](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/02-installation.md) - Install paths for the macOS desktop DMG and the `@inkeep/open-knowledge` npm CLI, including Node.js 24+, git, and Bun toolchain prerequisites for contributors.
- 03. [Quickstart](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/03-quickstart.md) - Run `ok init`, `ok start --open`, verify the editor loads, and confirm MCP tools respond from a connected agent.
- 04. [Core concepts](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/04-core-concepts.md) - Three-layer model (editor, knowledge engine, markdown files), filesystem-as-database persistence, link graph and backlinks, and git-backed attribution.
- 05. [Project scaffold](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/05-project-scaffold.md) - `.ok/` directory layout, three config scopes (project, user, project-local), `.okignore` exclusions, and `content.dir` content-root semantics.
- 06. [Collaboration server](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/06-collaboration-server.md) - Hocuspocus/Yjs CRDT server lifecycle, per-project `ok start` locks, server-free MCP reads (`exec`, `preview_url`) versus server-routed writes, and protocol version boundaries.
- 07. [Initialize a project](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/07-initialize-a-project.md) - Run `ok init` to scaffold `.ok/`, initialize git, install project and user skills, and register MCP entries for detected editors (Claude Code, Cursor, Codex).
- 08. [Wire agent editors](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/08-wire-agent-editors.md) - Connect Claude Code, Cursor, and Codex via MCP registration, bundled skills, `ok repair-skills`, and verification prompts that exercise the `exec` tool.
- 09. [Editor workflows](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/09-editor-workflows.md) - WYSIWYG and source-mode editing, ephemeral single-file sessions (`ok notes.md`), Open with AI handoffs, properties pane, timeline panel, and rich MDX embeds.
- 10. [GitHub sync and conflicts](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/10-github-sync-and-conflicts.md) - Clone with `ok clone`, enable auto-sync, push/pull via CLI, and resolve merge conflicts through MCP `conflicts` and `resolve_conflict` tools.
- 11. [Team sharing](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/11-team-sharing.md) - Publish projects to GitHub, construct share URLs, and manage read-only doc sharing via `ok share` and `ok sharing` commands.
- 12. [Semantic search setup](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/12-semantic-search-setup.md) - Enable embeddings-based MCP search ranking with `ok embeddings set-key`, project-local `search.semantic.*` config, and content-egress constraints.
- 13. [Folders and templates](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/13-folders-and-templates.md) - Folder properties in `.ok/frontmatter.yml`, leaf-to-root template resolution, and MCP `write({ document: { template } })` instantiation semantics.
- 14. [CLI reference](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/14-cli-reference.md) - All `ok` subcommands, global flags (`--cwd`, `--log-level`), server management (`start`, `stop`, `ps`, `status`, `clean`), and single-file dispatch behavior.
- 15. [MCP tools reference](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/15-mcp-tools-reference.md) - Seventeen MCP tools (`exec`, `search`, `links`, `write`, `edit`, `delete`, `move`, `history`, `checkpoint`, `workflow`, and others), input nesting conventions, preview envelopes, and conflict guards.
- 16. [Configuration reference](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/16-configuration-reference.md) - YAML config keys in `.ok/config.yml`, `~/.ok/global.yml`, and `.ok/local/config.yml`; precedence order; published JSON Schema paths; and environment-only variables.
- 17. [HTTP API reference](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/17-http-api-reference.md) - Editor and agent HTTP routes on the Hocuspocus server (`/api/agent-write-md`, `/api/search`, `/api/documents`, `/api/share/*`, `/api/local-op/*`) used by the web app and desktop shell.
- 18. [Auth reference](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/18-auth-reference.md) - GitHub OAuth device flow (`ok auth login`), PAT storage, `gh` CLI credential reuse, git credential helper, and token scopes for clone, sync, and share operations.
- 19. [Karpathy LLM wiki workflow](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/19-karpathy-llm-wiki-workflow.md) - Source-grounded knowledge base pattern with `research/` and `articles/` folders, starter packs, and MCP `workflow` kinds `ingest`, `research`, and `consolidate`.
- 20. [Entity vault workflow](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/20-entity-vault-workflow.md) - GBrain-compatible entity dossiers with compiled truth and append-only timelines, scaffolded via starter packs and maintained through MCP write and checkpoint tools.
- 21. [Troubleshooting](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/21-troubleshooting.md) - Diagnose server lock state, run `ok diagnose health` and `ok diagnose bundle`, file bug reports, repair stale skills/MCP configs, and recover from crash leftovers with `ok clean`.
- 22. [Contributing](https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/22-contributing.md) - Public mirror PR flow, `bun install` and `bun run check` gate, package layout, changeset requirements, and local dev commands for app and docs site.

## Source File Index

- `.bun-version`
- `.changeset/config.json`
- `.env.example`
- `.github/workflows/monorepo-pr-bridge.yml`
- `.node-version`
- `AGENTS.md`
- `CONTRIBUTING.md`
- `docs/content/advanced/folders-and-templates.mdx`
- `docs/content/features/agent-activity.mdx`
- `docs/content/features/assets-and-embeds.mdx`
- `docs/content/features/editor.mdx`
- `docs/content/features/github-sync.mdx`
- `docs/content/features/ignore-patterns.mdx`
- `docs/content/features/share.mdx`
- `docs/content/features/timeline-and-recovery.mdx`
- `docs/content/get-started/overview.mdx`
- `docs/content/get-started/quickstart.mdx`
- `docs/content/integrations/claude-code.mdx`
- `docs/content/integrations/codex.mdx`
- `docs/content/integrations/cursor.mdx`
- `docs/content/reference/cli.mdx`
- `docs/content/reference/configuration.mdx`
- `docs/content/reference/core-concepts.md`
- `docs/content/reference/mcp.mdx`
- `docs/content/workflows/entity-vault.mdx`
- `docs/content/workflows/karpathy-llm-wiki.mdx`
- `docs/package.json`
- `package.json`
- `packages/app/package.json`
- `packages/cli/package.json`
- `packages/cli/scripts/build-config-schema.mjs`
- `packages/cli/scripts/postinstall.mjs`
- `packages/cli/src/cli.ts`
- `packages/cli/src/commands/auth/git-credential.ts`
- `packages/cli/src/commands/auth/index.ts`
- `packages/cli/src/commands/auth/login.ts`
- `packages/cli/src/commands/auth/pat.ts`
- `packages/cli/src/commands/auth/status.ts`
- `packages/cli/src/commands/bug-report.ts`
- `packages/cli/src/commands/clean.ts`
- `packages/cli/src/commands/clone.ts`
- `packages/cli/src/commands/config.ts`
- `packages/cli/src/commands/diagnose-health.ts`
- `packages/cli/src/commands/diagnose.ts`
- `packages/cli/src/commands/editors.ts`
- `packages/cli/src/commands/embeddings/index.ts`
- `packages/cli/src/commands/init.ts`
- `packages/cli/src/commands/install-skill.ts`
- `packages/cli/src/commands/lock-state.ts`
- `packages/cli/src/commands/mcp.ts`
- `packages/cli/src/commands/open.ts`
- `packages/cli/src/commands/ps.ts`
- `packages/cli/src/commands/pull.ts`
- `packages/cli/src/commands/push.ts`
- `packages/cli/src/commands/repair-mcp-configs.ts`
- `packages/cli/src/commands/repair-skills.ts`
- `packages/cli/src/commands/seed.ts`
- `packages/cli/src/commands/share/publish.ts`
- `packages/cli/src/commands/sharing/share.ts`
- `packages/cli/src/commands/single-file-dispatch.ts`
- `packages/cli/src/commands/single-file-open.ts`
- `packages/cli/src/commands/start.ts`
- `packages/cli/src/commands/status.ts`
- `packages/cli/src/commands/stop.ts`
- `packages/cli/src/commands/sync.ts`
- `packages/cli/src/constants.ts`
- `packages/cli/src/integrations/write-project-skill.ts`
- `packages/cli/src/sharing/git-exclude.ts`
- `packages/core/package.json`
- `packages/core/src/config/bind-okignore-doc.ts`
- `packages/core/src/config/field-registry.ts`
- `packages/core/src/config/merge-layered.ts`
- `packages/core/src/config/schema-version.ts`
- `packages/core/src/config/schema.ts`
- `packages/core/src/config/validate-patch-scopes.ts`
- `packages/core/src/frontmatter/tags.ts`
- `packages/core/src/markdown/entity-ref-guard.ts`
- `packages/core/src/protocol-version.ts`
- `packages/core/src/templates/template-format.ts`
- `packages/desktop/package.json`
- `packages/server/package.json`
- `packages/server/src/api-agent-write-summary.test.ts`
- `packages/server/src/api-config.test.ts`
- `packages/server/src/api-extension.ok-init.test.ts`
- `packages/server/src/api-extension.ts`
- `packages/server/src/api-search-semantic.test.ts`
- `packages/server/src/api-search.test.ts`
- `packages/server/src/github-permissions.ts`
- `packages/server/src/handoff-api.ts`
- `packages/server/src/index.ts`
- `packages/server/src/init-project.ts`
- `packages/server/src/mcp/agent-identity.ts`
- `packages/server/src/mcp/tools/checkpoint.ts`
- `packages/server/src/mcp/tools/conflicts.ts`
- `packages/server/src/mcp/tools/consolidate-body.ts`
- `packages/server/src/mcp/tools/discover-body.ts`
- `packages/server/src/mcp/tools/edit.ts`
- `packages/server/src/mcp/tools/exec.ts`
- `packages/server/src/mcp/tools/history.ts`
- `packages/server/src/mcp/tools/index.ts`
- `packages/server/src/mcp/tools/ingest-body.ts`
- `packages/server/src/mcp/tools/links.ts`
- `packages/server/src/mcp/tools/palette.ts`
- `packages/server/src/mcp/tools/research-body.ts`
- `packages/server/src/mcp/tools/resolve-conflict.ts`
- `packages/server/src/mcp/tools/search.ts`
- `packages/server/src/mcp/tools/share-link.ts`
- `packages/server/src/mcp/tools/shared.ts`
- `packages/server/src/mcp/tools/verb-schemas.ts`
- `packages/server/src/mcp/tools/workflow.ts`
- `packages/server/src/mcp/tools/write.ts`
- `packages/server/src/share/construct-url.ts`
- `packages/server/src/share/publish.ts`
- `README.md`

---

## 01. Overview

> Monorepo packages, runtime surfaces (desktop app, `ok` CLI web editor, MCP server), and the shortest path from install to first agent-driven edit.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/01-overview.md
- Generated: 2026-06-25T22:41:11.213Z

### Source Files

- `README.md`
- `packages/cli/package.json`
- `packages/app/package.json`
- `packages/server/package.json`
- `packages/desktop/package.json`
- `packages/core/package.json`
- `docs/content/get-started/overview.mdx`

---
title: "Overview"
description: "Monorepo packages, runtime surfaces (desktop app, `ok` CLI web editor, MCP server), and the shortest path from install to first agent-driven edit."
---

OpenKnowledge is a Bun-workspaced monorepo that ships a local-first markdown knowledge base: the `@inkeep/open-knowledge` npm package exposes the `ok` CLI, which scaffolds a `.ok/` project, boots a Hocuspocus/Yjs collaboration server from `@inkeep/open-knowledge-server`, serves the Vite/React editor from `@inkeep/open-knowledge-app`, and registers an MCP stdio server named `open-knowledge` for any MCP-capable agent.

## Runtime surfaces

Three entry points share the same server and content directory. Pick one based on platform; all paths converge on the same project files and MCP toolset.

| Surface | How you launch it | Typical platform |
| --- | --- | --- |
| Desktop app | Download the arm64 DMG, drag **Open Knowledge** to **Applications**, launch | macOS Apple Silicon |
| CLI web editor | `npm install -g @inkeep/open-knowledge`, then `ok start --open` | Linux, Windows, Intel Mac |
| MCP stdio server | Registered by `ok init` as `open-knowledge`; agents invoke `ok mcp` (or the desktop bundle shim) | Any MCP client |

<Note>
Running bare `ok` with no subcommand launches the desktop app when it is installed; otherwise it falls back to `ok start`.
</Note>

The desktop shell (`@inkeep/open-knowledge-desktop`) embeds the same web app and boots `@inkeep/open-knowledge-server` in a utility process. The CLI bundles the built app assets under `dist/public` and can spawn a detached `ok ui` child for the browser UI.

## Three-layer model

OpenKnowledge separates editing, agent access, and persistence. All three operate on the same markdown files.

| Layer | Package / protocol | Responsibility |
| --- | --- | --- |
| Editor | `@inkeep/open-knowledge-app` | WYSIWYG and source-mode editing, backlinks, frontmatter, timeline, rich MDX embeds |
| Knowledge engine | MCP server `open-knowledge` in `@inkeep/open-knowledge-server` | Seventeen agent tools for read, write, search, links, history, conflicts, and workflows |
| Content | Files under the configured `content.dir` (default `.`) | Plain `.md`/`.mdx` on disk, version-controlled by git; no separate database |

Reads such as `exec` and `preview_url` can resolve against project files without a running collaboration server. Mutations (`write`, `edit`, `delete`, `move`, and related tools) route through the server so Yjs CRDT state, link indexes, and git attribution stay consistent.

## Monorepo packages

The root workspace (`open-knowledge`) uses Bun 1.3.13+ workspaces and Turbo for builds. Published consumers install only `@inkeep/open-knowledge`; contributors work across all packages.

:::files
open-knowledge/
├── packages/
│   ├── cli/          @inkeep/open-knowledge     CLI (`ok`), npm publish target
│   ├── app/          @inkeep/open-knowledge-app Web editor (Vite + React)
│   ├── server/       @inkeep/open-knowledge-server Hocuspocus server, HTTP API, MCP
│   ├── core/         @inkeep/open-knowledge-core Shared domain logic, config schema
│   ├── desktop/      @inkeep/open-knowledge-desktop Electron macOS app
│   └── plugin/       @inkeep/open-knowledge-plugin Claude plugin metadata
└── docs/             Documentation site
:::

| Package | npm name | Role |
| --- | --- | --- |
| `packages/cli` | `@inkeep/open-knowledge` | Commander-based CLI; bins `ok` and `open-knowledge`; Node.js ≥ 24 |
| `packages/app` | `@inkeep/open-knowledge-app` | Editor UI; connects to server via Hocuspocus provider |
| `packages/server` | `@inkeep/open-knowledge-server` | `bootServer()`, Yjs CRDT, REST/MCP HTTP routes, MCP tool registry |
| `packages/core` | `@inkeep/open-knowledge-core` | Markdown pipeline, config merge, shadow-repo layout, editor constants |
| `packages/desktop` | `@inkeep/open-knowledge-desktop` | Electron shell, auto-update, embedded server utility |
| `packages/plugin` | `@inkeep/open-knowledge-plugin` | Claude Code plugin descriptor |
| `docs` | — | Public docs site (Fumadocs-style MDX) |

<Info>
Agent integration is provider-neutral: the knowledge engine speaks MCP over stdio. Bring Claude Code, Cursor, Codex, or any other MCP client and the model credentials you already use (BYOC/BYOK). `ok init` detects installed editors and writes MCP entries; bundled skills ship inside the server package.
</Info>

## Architecture at runtime

```mermaid
flowchart TB
  subgraph surfaces["Runtime surfaces"]
    Desktop["@inkeep/open-knowledge-desktop"]
    CLI["ok start / ok ui"]
    MCP["ok mcp → open-knowledge"]
  end

  subgraph server_pkg["@inkeep/open-knowledge-server"]
    Hocus["Hocuspocus + Yjs CRDT"]
    HTTP["HTTP API /api/*"]
    MCPReg["MCP tool registry"]
  end

  subgraph app_pkg["@inkeep/open-knowledge-app"]
    Editor["React editor"]
  end

  subgraph core_pkg["@inkeep/open-knowledge-core"]
    Domain["Markdown, config, indexes"]
  end

  subgraph storage["Project on disk"]
    OK[".ok/ config + locks"]
    MD["content.dir markdown files"]
    Git["git + shadow repo"]
  end

  Desktop --> Hocus
  CLI --> Hocus
  MCP --> MCPReg
  Editor <-->|WebSocket| Hocus
  Hocus --> Domain
  MCPReg --> Domain
  HTTP --> Domain
  Domain --> MD
  Domain --> Git
  Hocus --> OK
```

## MCP tool surface

`registerAllTools()` in `@inkeep/open-knowledge-server` exposes seventeen tools. Agents use these instead of raw filesystem reads when a project has `.ok/`.

| Tool | Primary use |
| --- | --- |
| `exec` | Read-only shell allowlist (`cat`, `grep`, `find`, …) with per-file enrichment |
| `search` | Full-text and optional semantic search across the knowledge base |
| `links` | Link-graph views (`dead`, `orphans`, `hubs`, `suggest`, …) |
| `write` | Create documents, optionally from folder templates |
| `edit` | Patch existing document bodies or frontmatter |
| `delete` | Remove documents |
| `move` | Rename or relocate documents |
| `history` | Per-document edit bursts with attribution |
| `checkpoint` | Named save points on the shadow timeline |
| `restore_version` | Roll back to a prior burst |
| `conflicts` | List merge conflicts after git sync |
| `resolve_conflict` | Apply a resolution choice |
| `workflow` | Procedural guides (`ingest`, `research`, `consolidate`, `discover`) |
| `palette` | Authoring component catalog |
| `config` | Read project configuration slices |
| `preview_url` | Editor preview URL for a document path |
| `share_link` | Construct read-only share URLs |

The registered MCP server name is `open-knowledge`. Cursor and other clients may prompt you to approve it on first connect.

## Shortest path to a first agent-driven edit

<Steps>

<Step title="Install a runtime surface">

<Tabs>
<Tab title="macOS desktop">

Download the latest arm64 DMG from GitHub Releases, install **Open Knowledge** to **Applications**, and launch it. Git is required for timeline and recovery features.

</Tab>
<Tab title="CLI web editor">

```bash
npm install -g @inkeep/open-knowledge
```

Requires Node.js 24+ and git on your PATH.

</Tab>
</Tabs>

</Step>

<Step title="Initialize a project">

```bash
mkdir my-knowledge-base && cd my-knowledge-base
ok init
```

`ok init` scaffolds `.ok/` (including `config.yml`), ensures a git repository, installs bundled skills, and registers the `open-knowledge` MCP server for detected editors (Claude, Claude Desktop, Cursor, Codex). Use `--no-mcp` to scaffold without touching editor config, or `--scope user|project|both` to control where MCP entries are written.

</Step>

<Step title="Start the editor">

```bash
ok start --open
```

Starts the collaboration server, acquires per-project locks under `.ok/local/`, and opens the web editor in your default browser. Without `--open`, the CLI prints the local URL.

<Warning>
If `.ok/` is missing, `ok start` exits with guidance to run `ok init` first.
</Warning>

</Step>

<Step title="Connect your agent">

Open the project folder in your agent harness. If prompted, approve the `open-knowledge` MCP server. `ok init` should have already written the stdio entry; run `ok repair-skills` if tools are missing.

Send a prompt that exercises write tools, for example:

```text
Create a knowledge base about Large Language Models. Include an overview page and separate pages for three key concepts.
```

</Step>

<Step title="Verify the edit landed">

In the editor sidebar, confirm new `.md` files appear. Click the agent activity indicator (top right) to watch cross-file edits. Use the timeline panel on a document to see git-backed attribution for the agent burst.

</Step>

</Steps>

## Project scaffold in brief

Every initialized project carries an `.ok/` directory. The marker file `.ok/config.yml` defines the content root (`content.dir`, defaulting to `.`), sharing posture, and search settings. Machine-local state (server locks, embeddings keys, logs) lives under `.ok/local/`. See the dedicated scaffold and configuration pages for the full layout and precedence order across project, user (`~/.ok/global.yml`), and project-local scopes.

## What OpenKnowledge adds beyond plain markdown

- **Rich editing** — TipTap WYSIWYG with Mermaid, LaTeX, embeds, callouts, and source-mode CodeMirror 6.
- **Agent-consistent writes** — MCP tools maintain frontmatter, wikilinks, backlinks, and shadow-repo activity metadata.
- **Git-backed timeline** — Per-burst history with selective rollback, plus GitHub sync and conflict tools.
- **Starter workflows** — `ok seed` scaffolds Karpathy-style `external-sources/` → `research/` → `articles/` layouts; `workflow` MCP kinds guide ingest/research/consolidate passes.

## Prerequisites summary

| Audience | Requirement |
| --- | --- |
| End users (CLI path) | Node.js 24+, git |
| End users (desktop) | macOS Apple Silicon, git |
| Contributors | Bun 1.3.13+, Node.js 24+; `bun install` then `bun run check` |

License: GPL-3.0-or-later.

## Next

<CardGroup>
<Card title="Installation" href="/installation">
Install paths for the macOS DMG and the `@inkeep/open-knowledge` npm CLI, plus contributor toolchain prerequisites.
</Card>
<Card title="Quickstart" href="/quickstart">
Step-by-step `ok init`, `ok start --open`, editor verification, and MCP smoke checks.
</Card>
<Card title="Core concepts" href="/core-concepts">
Three-layer model, filesystem-as-database, link graph, and git-backed attribution.
</Card>
<Card title="Initialize a project" href="/initialize-project">
What `ok init` scaffolds, git initialization, skill install, and MCP registration scopes.
</Card>
<Card title="Wire agent editors" href="/wire-agent-editors">
Connect Claude Code, Cursor, and Codex; repair stale MCP configs and verify with `exec`.
</Card>
<Card title="Collaboration server" href="/collaboration-server">
Hocuspocus/Yjs lifecycle, `ok start` locks, and server-free versus server-routed MCP paths.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
All seventeen tools, input nesting, preview envelopes, and conflict guards.
</Card>
<Card title="CLI reference" href="/cli-reference">
Every `ok` subcommand, global flags, and single-file dispatch (`ok notes.md`).
</Card>
</CardGroup>

---

## 02. Installation

> Install paths for the macOS desktop DMG and the `@inkeep/open-knowledge` npm CLI, including Node.js 24+, git, and Bun toolchain prerequisites for contributors.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/02-installation.md
- Generated: 2026-06-25T22:40:55.869Z

### Source Files

- `README.md`
- `packages/cli/package.json`
- `packages/cli/scripts/postinstall.mjs`
- `packages/desktop/package.json`
- `.node-version`
- `.bun-version`
- `docs/content/get-started/quickstart.mdx`

---
title: "Installation"
description: "Install paths for the macOS desktop DMG and the `@inkeep/open-knowledge` npm CLI, including Node.js 24+, git, and Bun toolchain prerequisites for contributors."
---

Open Knowledge ships on two consumer install surfaces: a signed **macOS arm64 DMG** that installs the Electron desktop app (with a bundled `ok` CLI), and the **`@inkeep/open-knowledge`** npm package that exposes `ok` and `open-knowledge` on your PATH for Linux, Windows, and Intel Mac. Contributors clone the monorepo and use **Bun 1.3.13+** and **Node.js 24+** as the build toolchain.

## Install paths

| Surface | Audience | Host requirements | Delivers |
| --- | --- | --- | --- |
| macOS DMG (`Open-Knowledge-arm64.dmg`) | Apple Silicon Mac users who want a native app | macOS on Apple Silicon (M1+), `git` | Desktop editor, auto-update, bundled `ok` via `ELECTRON_RUN_AS_NODE` |
| npm global (`@inkeep/open-knowledge`) | Linux, Windows, Intel Mac, or terminal-first macOS users | Node.js 24+, `git` | `ok` / `open-knowledge` CLI, local web editor, MCP server |
| Monorepo clone | Contributors and package maintainers | Bun 1.3.13+, Node.js 24+, `git` | Full workspace for app, CLI, server, desktop, and docs development |

<Note>
The desktop DMG is **arm64-only** today. Intel Mac users should install the npm CLI and run the editor as a local web app in the browser.
</Note>

## Prerequisites

| Dependency | End users | Contributors | Notes |
| --- | --- | --- | --- |
| **Node.js** | 24+ (CLI path only) | 24+ | Pinned in `.node-version` and `engines.node` across packages. Desktop app users do not need a separate Node install. |
| **git** | Required | Required | Minimum version `2.31.0`. Used for project initialization, timeline, and GitHub sync. |
| **Bun** | Not required | 1.3.13+ | Monorepo package manager and build runner. Pinned in `.bun-version` and root `packageManager`. |

<Warning>
Running the CLI on Node.js versions below 24 violates the published `engines` constraint. Installs may succeed, but runtime behavior is unsupported.
</Warning>

## macOS desktop app (DMG)

The desktop build targets **Apple Silicon only** (`arch: [arm64]`) and publishes a stable, version-less asset name so `releases/latest/download/` resolves consistently.

<Steps>

<Step title="Download the DMG">

Download the latest arm64 DMG from GitHub Releases:

```
https://github.com/inkeep/open-knowledge/releases/latest/download/Open-Knowledge-arm64.dmg
```

</Step>

<Step title="Install to Applications">

Open the DMG, drag **Open Knowledge** to **Applications**, and launch the app from Launchpad or Spotlight.

The DMG layout includes a drag-to-Applications alias. The shipped `.app` is code-signed and notarized for Gatekeeper.

</Step>

<Step title="Verify the install">

Launch **Open Knowledge** and confirm the app opens without Gatekeeper warnings. The desktop bundle ships:

- The editor UI (Vite build from `packages/app`)
- A bundled CLI at `Contents/Resources/cli/dist/cli.mjs`
- An `ok` wrapper script that reuses the Electron runtime as a Node host via `ELECTRON_RUN_AS_NODE=1` — no separate Node.js install is required on the Mac.

</Step>

</Steps>

<Tip>
The desktop app auto-updates via `electron-updater`, which downloads the arm64 ZIP variant (not the DMG) for in-place swaps. Beta builds are available through the docs site's `/download/beta` redirect.
</Tip>

## CLI and web app (npm)

On **Linux**, **Windows**, **Intel Mac**, or any environment where you prefer a terminal-first workflow, install the published npm package globally.

<Tabs>

<Tab title="npm (recommended)">

<Steps>

<Step title="Install Node.js 24+">

Download and install Node.js 24 or newer from [nodejs.org](https://nodejs.org/en/download/).

</Step>

<Step title="Install git">

Install `git` 2.31.0 or newer. On macOS, `xcode-select --install` provides the Xcode Command Line Tools including git. On Linux and Windows, use [git-scm.com](https://git-scm.com) or your package manager.

</Step>

<Step title="Install the CLI globally">

```bash
npm install -g @inkeep/open-knowledge
```

This places two equivalent binaries on your PATH:

| Binary | Entry point |
| --- | --- |
| `ok` | `dist/cli.mjs` |
| `open-knowledge` | `dist/cli.mjs` |

</Step>

<Step title="Verify the CLI">

```bash
ok --version
```

The version output includes the package version and GPL-3.0-or-later license notice.

</Step>

</Steps>

</Tab>

</Tabs>

### Post-install skill wiring

The npm package runs a `postinstall` script that attempts to install the bundled Open Knowledge agent skill to detected agent hosts (Claude Code, Cursor, Codex, and others). Outcomes:

| Result | Behavior |
| --- | --- |
| `installed` | Prints `[open-knowledge] Agent Skill installed to detected agent hosts.` |
| `failed` | Prints a remediation hint to run `npx skills@~1.5.0 add <bundled-path> --agent '*' -g -y --copy` manually |
| `skip-current` | Skill already at the current version; no action |

Project-level MCP registration and skill installation also run during `ok init`. If agent hosts miss the skill after install, use `ok repair-skills` or see [Wire agent editors](/wire-agent-editors).

### Optional native dependency

`@parcel/watcher` is listed as an optional dependency for efficient filesystem watching. If it fails to install on your platform, the CLI falls back to polling-based watchers.

## Contributor toolchain

Contributors work from a monorepo clone and use Bun as the package manager and Turbo for orchestrated builds, lint, typecheck, and tests.

<Steps>

<Step title="Install toolchain versions">

Pin these versions before cloning:

- **Bun 1.3.13+** — read from `.bun-version`
- **Node.js 24+** — read from `.node-version`

Version managers:

```bash
# fnm
fnm install

# volta
volta install node@24

# mise
mise install
```

</Step>

<Step title="Clone and install dependencies">

```bash
git clone https://github.com/inkeep/open-knowledge.git
cd open-knowledge
bun install
```

`bun install` warns with `EBADENGINE` if Node is below 24. The install usually succeeds, but builds and tests may fail — pin Node 24+ before reporting issues.

</Step>

<Step title="Run the public PR gate">

```bash
bun run check
```

This runs Biome lint, then Turbo `build`, `typecheck`, and `test` across workspaces. No environment variables are required for a fresh clone.

</Step>

<Step title="Run local dev surfaces (optional)">

```bash
# Web editor dev server
bun run --filter @inkeep/open-knowledge-app dev

# Docs site
cd docs && bun run dev

# Desktop Electron dev
bun run --filter @inkeep/open-knowledge-desktop dev
```

</Step>

</Steps>

<Info>
Contributor setup requires no secrets or `.env` files. See `.env.example` for optional observability variables (OpenTelemetry, custom dev server port).
</Info>

## Verify your environment

After installing the CLI and initializing a project, run health checks from the project directory:

```bash
ok init          # if not already initialized
ok diagnose health
```

`ok diagnose health` runs checks for `git`, `bun`, `config-yaml`, `content-dir`, `server-lock`, `shadow-repo`, `shadow-health`, and `macos-codesig`. The `bun` check passes only when Bun is on PATH (relevant for contributors; end users can ignore a `bun` failure). The `macos-codesig` check applies to the packaged desktop app.

To confirm the editor serves correctly:

```bash
ok start --open
```

The browser should load the WYSIWYG editor. If something fails, see [Troubleshooting](/troubleshooting) for `ok diagnose bundle`, server lock recovery, and `ok clean`.

## Install surface architecture

```text
┌──────────────────────────────────────────────────────────────┐
│                     Open Knowledge                           │
└───────────────┬──────────────────────────┬───────────────────┘
                │                          │
     macOS arm64 DMG                npm @inkeep/open-knowledge
                │                          │
    ┌───────────▼──────────┐    ┌──────────▼──────────┐
    │  Open Knowledge.app    │    │  ok / open-knowledge │
    │  ├─ Editor UI          │    │  ├─ Web editor       │
    │  ├─ Bundled CLI        │    │  ├─ MCP server       │
    │  └─ ok.sh wrapper      │    │  └─ postinstall skill│
    │     (ELECTRON_RUN_     │    │     wiring           │
    │      AS_NODE=1)        │    └──────────┬──────────┘
    └───────────┬────────────┘               │
                │                            │
                └────────────┬───────────────┘
                             │
                    ┌────────▼────────┐
                    │  .ok/ project   │
                    │  git repo       │
                    │  markdown docs  │
                    └─────────────────┘
```

## Related pages

<CardGroup>

<Card title="Quickstart" href="/quickstart">
Run `ok init`, `ok start --open`, and confirm MCP tools respond from a connected agent.
</Card>

<Card title="Initialize a project" href="/initialize-project">
Scaffold `.ok/`, initialize git, install skills, and register MCP entries for detected editors.
</Card>

<Card title="Contributing" href="/contributing">
Public mirror PR flow, changeset requirements, and local dev commands for the monorepo.
</Card>

<Card title="Troubleshooting" href="/troubleshooting">
Diagnose server lock state, repair stale skills, and recover from crash leftovers.
</Card>

</CardGroup>

---

## 03. Quickstart

> Run `ok init`, `ok start --open`, verify the editor loads, and confirm MCP tools respond from a connected agent.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/03-quickstart.md
- Generated: 2026-06-25T22:40:45.685Z

### Source Files

- `packages/cli/src/commands/init.ts`
- `packages/cli/src/commands/start.ts`
- `packages/server/src/init-project.ts`
- `packages/cli/src/cli.ts`
- `docs/content/get-started/quickstart.mdx`
- `docs/content/reference/cli.mdx`

---
title: "Quickstart"
description: "Run `ok init`, `ok start --open`, verify the editor loads, and confirm MCP tools respond from a connected agent."
---

The `ok` CLI scaffolds a project with `ok init` (`.ok/` config, git, MCP registration for detected editors) and boots a per-project Hocuspocus collaboration server plus an auto-spawned `ok ui` web editor with `ok start`. Agents connect through the `open-knowledge` MCP server (`ok mcp`) that `ok init` registers; read tools like `exec` work server-free, while writes route through the running server.

## Prerequisites

<Note>
Install **Node.js 24+** and **git** before using the CLI path. See [Installation](/installation) for npm install, desktop DMG, and contributor toolchain details.
</Note>

| Requirement | Used for |
| --- | --- |
| Node.js 24+ | `npm install -g @inkeep/open-knowledge` and the `npx` MCP launch chain |
| `git` | `ok init` calls `ensureProjectGit`; timeline, shadow-repo, and sync features depend on it |

## Runtime layout

```mermaid
sequenceDiagram
  participant You
  participant CLI as ok CLI
  participant Server as Hocuspocus server
  participant UI as ok ui
  participant Agent as Agent editor
  participant MCP as open-knowledge MCP

  You->>CLI: ok init
  CLI->>CLI: Scaffold .ok/, git, MCP configs, skills
  You->>CLI: ok start --open
  CLI->>Server: bootServer (collab + HTTP API)
  CLI->>UI: spawnOkUi (sibling process)
  CLI-->>You: Banner with Editor URL
  CLI-->>You: openBrowser(localUrl)
  Agent->>MCP: stdio (ok mcp)
  MCP->>MCP: exec (server-free reads)
  MCP->>Server: write / edit / search (server-routed)
  UI->>Server: Yjs CRDT sync
```

On `ok start`, the CLI also runs reclaim sweeps for MCP configs, `launch.json`, and bundled skills before the server accepts connections. These repairs are best-effort and non-blocking.

## Initialize the project

<Steps>

<Step title="Install the CLI">

<CodeGroup>

```bash title="npm (global)"
npm install -g @inkeep/open-knowledge
```

```bash title="Verify"
ok --version
```

</CodeGroup>

</Step>

<Step title="Create a project directory and run ok init">

```bash
mkdir my-knowledge-base && cd my-knowledge-base
ok init
```

`ok init` performs these actions in order:

1. Resolves the project root (promotes to a git root or ancestor `.ok/` when applicable).
2. Initializes git when no repository exists (`ensureProjectGit`).
3. Scaffolds `.ok/config.yml`, `.ok/.gitignore`, and `.okignore` via `initContent`.
4. Registers the `open-knowledge` MCP server for detected editors (Claude Code, Cursor, Codex, Claude Desktop).
5. Writes project-local skills (`.claude/skills/`, `.cursor/skills/`, `.agents/skills/`).
6. Installs the user-global `open-knowledge` skill via `npx skills`.
7. Scaffolds `.claude/launch.json` when Claude Code is detected.

<ParamField body="--no-mcp" type="flag">
Scaffold `.ok/` only; skip all MCP and skill registration.
</ParamField>

<ParamField body="--scope" type="user | project | both">
Control where MCP entries are written. Default in an interactive terminal: both user-level (`~/.claude.json`, `~/.cursor/mcp.json`, …) and project-level (`.mcp.json`, `.cursor/mcp.json`, …). Non-TTY defaults to `both`.
</ParamField>

<ParamField body="--shared" type="flag">
Commit OK config alongside content (default for fresh repos).
</ParamField>

<ParamField body="--local-only" type="flag">
Append OK artifact paths to `.git/info/exclude` so config stays per-clone.
</ParamField>

<Check>
Successful init prints **Content scaffolded at `.ok/`**, MCP registration lines for each detected editor, and **Next steps** when at least one editor was configured.
</Check>

</Step>

<Step title="Start the server and open the editor">

```bash
ok start --open
```

`ok start` requires a `.ok/` directory at the project root. Without it, the CLI exits with:

```
This directory isn't set up yet. Run `ok init` first, then `ok start` again.
```

<ParamField body="--open" type="flag">
Open the editor URL in the default browser after the server finishes initialization.
</ParamField>

<ParamField body="-p, --port" type="number">
Pin the collaboration server port. Falls back to the `PORT` environment variable; auto-allocated when unset.
</ParamField>

<ParamField body="-H, --host" type="string">
Bind host. Defaults to `localhost` (`DEFAULT_SERVER_HOST`). Override with the `HOST` environment variable.
</ParamField>

<ParamField body="--cwd" type="path">
Global flag: run against a different project directory.
</ParamField>

<Info>
`ok start` auto-spawns an `ok ui` sibling unless a UI lock is already alive or `--react-shell-dist-dir` suppresses it. The startup banner labels the editor URL **Editor:** when a UI port is resolved, otherwise it falls back to the API URL.
</Info>

</Step>

</Steps>

## Verify the editor loads

Use these checks after `ok start --open`:

| Signal | Expected result |
| --- | --- |
| Startup banner | Boxed output with `open-knowledge v<version>`, an **Editor:** URL (`http://localhost:<ui-port>`), and optionally an **API:** URL for the collab server |
| Browser | Editor shell loads at the **Editor:** URL when `--open` is set |
| `ok status` | Both lines report `alive` with pid and port |

```bash
ok status
```

<RequestExample>

```bash
ok status
```

</RequestExample>

<ResponseExample>

```text
server  alive  pid=12345 port=3847 started=2026-06-25T12:00:00.000Z
ui      alive  pid=12346 port=5173 started=2026-06-25T12:00:01.000Z
```

</ResponseExample>

An empty knowledge base shows the create screen in the editor sidebar. No markdown files are created by `ok init` alone; use an agent or `ok seed` to add content.

## Verify MCP tools from a connected agent

`ok init` registers MCP for editors it detects. Open the project folder in your agent (Claude Code, Cursor, or Codex), approve the `open-knowledge` MCP server when prompted, and run a read-only verification.

<Warning>
Cursor and other hosts may require explicit approval of a new MCP server. Approve **`open-knowledge`** — that is the `MCP_SERVER_NAME` constant wired by `ok init`.
</Warning>

Send this prompt in the agent chat:

```
List the first 5 documents you come across in this project.
```

The agent should invoke the Open Knowledge **`exec`** tool (for example `exec({ command: "ls -A" })` or `cat` on markdown files). `exec` is server-free: it reads the filesystem directly and returns enriched metadata (frontmatter, backlinks, history) for knowledge-base files.

| MCP behavior | Server required? |
| --- | --- |
| `exec`, `preview_url` | No — work with editor down |
| `search`, `links`, `history`, and other reads | Yes — route through Hocuspocus |
| `write`, `edit`, `delete`, `move`, and other writes | Yes — Hocuspocus starts on first write if not already running |

<Check>
A working MCP connection returns document paths or `exec` output referencing your project directory. An empty project may return an empty listing — that still confirms the tool responded.
</Check>

<Tip>
Set your agent to auto-approve tool calls (auto-mode, full-access, or equivalent) to avoid approving every `exec` and `write` during setup.
</Tip>

### Optional: stdio smoke test

For a terminal-only check without an agent UI, pipe MCP over stdio from the project directory:

```bash
# From another terminal while ok start is running (or for server-free exec)
cd my-knowledge-base
ok mcp
```

The E2E harness expects `tools/list` to include `exec`, `search`, `write`, and `edit`, and verifies `exec` read-back after a `write` round-trip.

## First agent-driven edit

After MCP verification, ask the agent to create initial content:

```
Create a knowledge base about Large Language Models. Include an overview page and separate pages for three key concepts.
```

Writes appear live in the editor through Yjs CRDT sync. The agent icon in the editor chrome shows activity across files once writes begin.

## Troubleshooting

<AccordionGroup>

<Accordion title="ok start says the directory is not set up">
Run `ok init` in the project root (the directory that should contain `.ok/`), then retry `ok start`.
</Accordion>

<Accordion title="Server already running or lock collision">
Check `ok status`. Stop the existing instance with `ok stop`, or use `--cwd` to point at a different project. For stale lock files after a crash, run `ok clean` (never touches live servers).
</Accordion>

<Accordion title="Git errors on init or start">
`ok init` requires git to initialize a parent repository. Install git or run `git init` yourself, then re-run `ok init`. `ok start` exits with code `78` when git is missing or too old for server features.
</Accordion>

<Accordion title="Agent does not call exec">
Restart the agent so it reloads MCP config written by `ok init`. Run `ok repair-skills` or restart with `ok start` (which re-runs skill and MCP repair sweeps). See [Wire agent editors](/wire-agent-editors) for per-editor paths and manual registration.
</Accordion>

<Accordion title="Writes fail with server not running">
Start the collaboration server with `ok start` (or let MCP auto-start it on the first write). Read-only `exec` works without the server; `write` and `edit` do not.
</Accordion>

</AccordionGroup>

## Related pages

<CardGroup>

<Card title="Installation" href="/installation">
Node.js 24+, git, npm CLI install, and macOS desktop DMG paths.
</Card>

<Card title="Initialize a project" href="/initialize-project">
Full `ok init` semantics: config scopes, sharing modes, and MCP registration details.
</Card>

<Card title="Wire agent editors" href="/wire-agent-editors">
Per-editor MCP paths, `ok repair-skills`, and verification prompts.
</Card>

<Card title="Collaboration server" href="/collaboration-server">
Hocuspocus lifecycle, locks, idle shutdown, and server-free vs server-routed tools.
</Card>

<Card title="MCP tools reference" href="/mcp-tools-reference">
All 17 tools, input nesting, preview envelopes, and conflict guards.
</Card>

<Card title="Troubleshooting" href="/troubleshooting">
`ok diagnose health`, `ok clean`, and bug-report bundles.
</Card>

</CardGroup>

---

## 04. Core concepts

> Three-layer model (editor, knowledge engine, markdown files), filesystem-as-database persistence, link graph and backlinks, and git-backed attribution.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/04-core-concepts.md
- Generated: 2026-06-25T22:40:45.414Z

### Source Files

- `docs/content/reference/core-concepts.md`
- `packages/server/src/mcp/tools/index.ts`
- `packages/server/src/mcp/tools/links.ts`
- `packages/core/src/protocol-version.ts`
- `packages/core/src/markdown/entity-ref-guard.ts`
- `packages/server/src/mcp/agent-identity.ts`

---
title: "Core concepts"
description: "Three-layer model (editor, knowledge engine, markdown files), filesystem-as-database persistence, link graph and backlinks, and git-backed attribution."
---

Open Knowledge stores knowledge as markdown under a configured `content.dir`, serves it through a Hocuspocus-backed editor and MCP server (`packages/server`), and maintains a per-branch link index plus a shadow git repo (`.git/ok` or `.git/ok-{slug}`) for attribution history. All three surfaces operate on the same files; protocol compatibility is pinned at `PROTOCOL_VERSION = 1`.

## Three-layer model

The runtime splits into an editing surface, a consistency engine, and the content tree. Each layer reads and writes the same markdown files on disk.

```mermaid
flowchart TB
  subgraph surface["Editing surface"]
    Editor["Web editor / desktop app<br/>packages/app"]
    CLI["ok CLI — start, init, share"]
  end

  subgraph engine["Knowledge engine"]
    MCP["MCP server — 17 tools<br/>packages/server"]
    Hocus["Hocuspocus / Yjs server<br/>collaboration + HTTP APIs"]
    Index["BacklinkIndex<br/>per-branch link graph"]
    Shadow["Shadow git repo<br/>.git/ok"]
  end

  subgraph storage["Content layer"]
    FS["Markdown files<br/>content.dir"]
    Upstream["Project git<br/>.git"]
  end

  Editor --> Hocus
  CLI --> MCP
  MCP --> Hocus
  MCP --> FS
  Hocus --> FS
  Hocus --> Index
  Hocus --> Shadow
  FS --> Upstream
```

| Layer | Package / surface | Responsibility |
| --- | --- | --- |
| Editor | `packages/app`, desktop shell, `ok start --open` | WYSIWYG and source-mode editing, properties pane, timeline, rich MDX embeds |
| Knowledge engine | `packages/server` MCP + Hocuspocus | Frontmatter consistency, link-graph maintenance, agent writes, version history |
| Content | Files under `content.dir` | Durable, portable markdown; versioned by project git |

All three layers target the **same files**. Edit through the editor, through MCP write tools, or with any text editor on disk. The engine enforces consistency when you route through it, but raw file edits remain valid.

The MCP surface is **agent-agnostic**: any MCP-capable client (Claude Code, Cursor, Codex, or others) connects via the registered `open-knowledge` server entry. Agent identity is captured per connection (`connectionId`, `displayName`, `colorSeed`) and forwarded on writes through the `x-ok-connection-id` header.

<Info>
  Two read paths are server-free: `exec` and `preview_url` hit the filesystem directly. Graph reads (`links`), search, history, and all writes route through the running Hocuspocus server. See [Collaboration server](/collaboration-server).
</Info>

## Filesystem as database

Open Knowledge has **no separate database**. Persistence is the file system plus git.

| Property | Mechanism |
| --- | --- |
| Content root | `content.dir` in `.ok/config.yml` (default `"."` — project root) |
| Exclusions | `.okignore` patterns; system paths under `.ok/` |
| Engine scope | Only files admitted by the content filter inside `content.dir` |
| Portability | Plain `.md` / `.mdx`; readable with `cat`, `grep`, `git diff` |

<ParamField body="content.dir" type="string">
  Relative path from the project root to the knowledge-base directory. Resolved by `resolveContentDir(config, projectDir)`. Change it in `.ok/config.yml` to narrow scope (for example `docs`).
</ParamField>

The knowledge engine is a **management layer**, not a gatekeeper. It maintains frontmatter, link targets, and reference integrity when edits flow through MCP or the editor, but direct filesystem edits are always permitted. On the next server reconcile, the backlink index and shadow repo pick up offline changes.

## Link graph and backlinks

Internal cross-references use standard markdown links or wiki-link syntax. The `BacklinkIndex` parses both forms per git branch and maintains directed `backward` and `forward` maps.

**Markdown links**

```markdown
[Auth overview](./auth/overview.md)
[Root note](/getting-started.md)
```

**Wiki links**

```markdown
[[target-doc]]
[[target-doc#section|display label]]
```

`classifyMarkdownHref` resolves relative paths against the source document; `[[...]]` targets are classified separately. Entity references (`&amp;`, `&#123;`) are protected during the markdown pipeline so link parsing does not corrupt HTML entities.

### Backlinks

When document A links to document B, the index records the inverse on B automatically. You never author backlinks by hand — they are derived from forward links at index time.

<ResponseField name="backlinks" type="array">
  Entries with `source` (referring doc), optional `anchor`, and `snippet` (context around the link).
</ResponseField>

### Link-graph views

The MCP `links` tool exposes six `kind` values. Pass one kind or an array (for example `["dead", "orphans", "hubs"]`) for a single-call graph audit. Multi-kind failures land in an `errors` map; successful kinds still merge into the payload.

| `kind` | Scope | Requires `document` | Key parameters |
| --- | --- | --- | --- |
| `backlinks` | Pages linking **to** the target | Yes | — |
| `forward` | Links **from** the target (doc + external) | Yes | — |
| `dead` | Internal links whose target file does not exist | No | `sourceDocuments` (OR filter) |
| `orphans` | Disconnected pages | No | `mode`: `incoming` \| `outgoing` \| `both` (default `both`) |
| `hubs` | Most-linked-to pages | No | `limit` (default 20, max 100) |
| `suggest` | Prose mentions not yet wrapped in link syntax | Yes | Returns `mentions[{ source, excerpt, offset }]` |

<RequestExample>

```json
{
  "kind": ["dead", "orphans", "hubs"],
  "mode": "incoming",
  "limit": 10
}
```

</RequestExample>

<Warning>
  `links` requires a running Hocuspocus server. Without it, the tool returns `HOCUSPOCUS_NOT_RUNNING_ERROR`. Use `exec` for filesystem reads when the server is down; graph queries need `ok start`.
</Warning>

Orphan detection follows three modes:

| `mode` | Orphan definition |
| --- | --- |
| `incoming` | No pages link to this document |
| `outgoing` | This document links to nothing |
| `both` | No inbound **and** no outbound edges |

The `exec` read path enriches every matched wiki file with backlink counts, forward-link counts, and recent shadow-repo activity — the primary alternative to `links` for quick inspection during server-free reads.

## Git-backed attribution

Edit history lives in a **shadow git repository** alongside the project's upstream git. The shadow repo stores per-writer WIP chains, checkpoints, and upstream-import markers without replacing the project's own commit history.

### Shadow repo layout

| Location | When |
| --- | --- |
| `.git/ok` | Project root is the git worktree root |
| `.git/ok-{slug}` | Project is a subdirectory of a larger worktree |
| `{projectRoot}/.git/ok` | No ancestor `.git` found |

Each writer gets an isolated ref: `refs/wip/{branch}/{writerId}`. Commits carry `GIT_AUTHOR_NAME` / `GIT_AUTHOR_EMAIL` from the resolved writer identity and `GIT_COMMITTER_NAME = openknowledge`.

### Writer classification

| Writer ID pattern | Classification | Typical source |
| --- | --- | --- |
| `agent-{id}` | `agent` | MCP-connected AI agent |
| `principal-{id}` | `principal` | Authenticated human principal |
| `file-system` | `classified-file-system` | Direct filesystem edits outside OK |
| `git-upstream` | `classified-git-upstream` | Upstream git import |
| `openknowledge-service` | `classified-openknowledge-service` | Internal service operations |

Agent writes resolve identity from the MCP session: `displayName` and `colorSeed` come from the client name (sanitized, max 128 chars) or fall back to `connectionId`. Optional `summary` fields (≤80 chars) on `write`, `edit`, `move`, and `checkpoint` persist as commit subjects and appear on document timelines.

### History and recovery

| MCP tool | Purpose |
| --- | --- |
| `history` | List version timeline for a `document` or folder activity; entries carry `version` (40-char SHA), `author`, `kind` (`checkpoint` / `wip` / `upstream`), `contributors` |
| `checkpoint` | Project-wide snapshot; returns `{ version }` |
| `restore_version` | Restore one document to a historical `version` from `history` or `checkpoint` |

History reads require the Hocuspocus server and query the shadow repo sorted by timestamp descending. Filter by `kind`, `author`, or `excludeAuthor`; paginate with `limit` (default 50, max 200) and `offset`.

<Tip>
  Before deleting a heavily linked document, run `links({ kind: "backlinks", document: "…" })` to find referrers that would become dead links. Call `checkpoint()` first when you may need a project-wide rollback.
</Tip>

Upstream project git and shadow attribution git serve different roles: upstream git tracks what you push to GitHub; the shadow repo tracks **who** changed **what** and **when** at edit granularity, including agent sessions.

## Protocol boundary

`PROTOCOL_VERSION` is currently `1`. Editor, server, and MCP clients negotiate compatibility against this constant. Mismatched protocol versions between the collaboration server and connected clients indicate an upgrade is needed on one side.

## Related pages

<CardGroup>
  <Card title="Overview" href="/overview">
    Monorepo packages, runtime surfaces, and the shortest path from install to first agent-driven edit.
  </Card>
  <Card title="Project scaffold" href="/project-scaffold">
    `.ok/` layout, config scopes, `.okignore`, and `content.dir` semantics.
  </Card>
  <Card title="Collaboration server" href="/collaboration-server">
    Hocuspocus lifecycle, server-free reads versus routed writes, and lock behavior.
  </Card>
  <Card title="MCP tools reference" href="/mcp-tools-reference">
    All 17 tools, input nesting, preview envelopes, and conflict guards.
  </Card>
  <Card title="Editor workflows" href="/editor-workflows">
    WYSIWYG editing, timeline panel, and rich MDX embeds.
  </Card>
</CardGroup>

---

## 05. Project scaffold

> `.ok/` directory layout, three config scopes (project, user, project-local), `.okignore` exclusions, and `content.dir` content-root semantics.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/05-project-scaffold.md
- Generated: 2026-06-25T22:42:17.235Z

### Source Files

- `packages/server/src/init-project.ts`
- `packages/core/src/config/schema.ts`
- `packages/core/src/config/merge-layered.ts`
- `packages/core/src/config/bind-okignore-doc.ts`
- `packages/cli/src/constants.ts`
- `packages/core/src/config/field-registry.ts`
- `packages/core/src/config/schema-version.ts`

---
title: "Project scaffold"
description: "`.ok/` directory layout, three config scopes (project, user, project-local), `.okignore` exclusions, and `content.dir` content-root semantics."
---

An Open Knowledge project is anchored by a regular file at `.ok/config.yml` under the **project root**. `ok init` (via `initContent`) scaffolds that marker plus `.ok/.gitignore`, a commented `.ok/config.yml` template, and a root-level `.okignore` — it does not create content folders, `.ok/local/`, or editor integrations. The collaboration server, MCP tools, and CLI resolve the project root by walking ancestors until `.ok/config.yml` exists as a file.

## Project root anchor

The canonical marker is `.ok/config.yml` (`OK_PROJECT_MARKER`). A bare `.ok/` directory, a directory at that path, or a sibling `.ok/frontmatter.yml` without `config.yml` does **not** qualify.

| Signal | Meaning |
| --- | --- |
| `.ok/config.yml` is a regular file | Valid project root |
| `.ok/` exists but `config.yml` is missing | Not a project root; ancestor walk continues |
| `.ok/config.yml` is a directory | Rejected; walk continues |
| No marker found above `cwd` | `findProjectDir` / MCP throw *No Open Knowledge project found* |

`resolveProjectRoot` (used by `ok init`) may place `.ok/` at a git working-tree root when `cwd` sits inside a repo and no ancestor already owns a project — `defaultContentDir` stays `.` in all promotion paths.

## `.ok/` directory layout

`initContent` creates a **config-only** scaffold. Runtime state appears later under `.ok/local/` when you run `ok start` or open the editor.

:::files
project-root/
├── .okignore                 # created at project root (if missing)
├── .ok/
│   ├── config.yml            # committed project config (created if missing)
│   └── .gitignore            # ignores machine-local paths (created/merged)
└── [content tree]            # markdown docs; default = project root itself

.ok/local/                    # created at runtime; entire tree gitignored
├── config.yml                # project-local overrides
├── server.lock               # collaboration server lock
├── principal.json            # (legacy path at .ok/ root also gitignored)
├── state.json
├── telemetry/
├── logs/
└── diagnostics/
:::

<Note>
Only `.ok/config.yml` is intended for git commit at the `.ok/` root. `.ok/.gitignore` excludes `local/` and per-machine files (`principal.json`, `state.json`, `server.lock`, `ui.lock`, `sync-state.json`, `last-spawn-error.log`). Legacy runtime files may still exist directly under `.ok/`; populated `.ok/local/` is the current lock and diagnostics location.
</Note>

`initContent` refuses to follow symlinks on `.ok/`, `.ok/config.yml`, `.ok/.gitignore`, or project-root `.okignore` — a defense against upstream repos redirecting scaffold writes.

## Three config scopes

Configuration splits across three YAML files. Each field in `ConfigSchema` carries a `scope` in the field registry (`user`, `project`, or `project-local`) that governs merge behavior.

| Scope | Path | Git | Typical keys |
| --- | --- | --- | --- |
| **User** | `~/.ok/global.yml` | Per user, not in repo | `appearance.theme`, `editor.wordWrap`, `appearance.preview.autoOpen` |
| **Project** | `.ok/config.yml` | Committed, shared | `content.dir`, `autoSync.default`, `telemetry.localSink.*` |
| **Project-local** | `.ok/local/config.yml` | Gitignored, per machine | `autoSync.enabled`, `search.semantic.*`, `terminal.enabled`, `appearance.sidebar.showHiddenFiles` |

Published JSON Schema (major version `v0`):

- `config.project.schema.json` → `.ok/config.yml`
- `config.user.schema.json` → `~/.ok/global.yml`
- `config.project-local.schema.json` → `.ok/local/config.yml`

Scaffolded `config.yml` includes a yaml-language-server `$schema` comment pointing at the project schema on unpkg.

### Precedence and merge rules

Effective config is built from built-in Zod defaults plus the three YAML layers. `mergeLayered(user, project, projectLocal)` applies **scope-aware leaf short-circuiting**:

- **`user` scope** — value comes only from `~/.ok/global.yml`; project and project-local values are ignored even when set.
- **`project` scope** — value comes only from `.ok/config.yml`; user and project-local overrides are ignored (`content.dir` is project-scoped).
- **`project-local` scope** — value comes from `.ok/local/config.yml` when present; otherwise falls through to project, then user for that leaf.

For object branches without a registered leaf scope, later layers win: project-local → project → user.

```mermaid
flowchart TB
  subgraph layers [Config layers]
    D[Built-in defaults]
    U["~/.ok/global.yml"]
    P[".ok/config.yml"]
    L[".ok/local/config.yml"]
  end
  subgraph merge [mergeLayered]
    M[Scope-aware leaf pick]
    E[Effective Config]
  end
  D --> M
  U --> M
  P --> M
  L --> M
  M --> E
```

<Info>
The CLI `loadConfig` merges **user + project** only (deep merge, then `ConfigSchema.parse`). Project-local values load separately — in the web editor via Hocuspocus CRDT docs (`__config__/project`, `__local__/project`, `__user__/config.yml`), and in the server via `readConfigSafely` for features like `autoSync` and semantic search.
</Info>

<Warning>
Do not put `content.include` or `content.exclude` in `config.yml`. Those keys are removed and rejected at load time. Use `content.dir` for subdirectory scoping and `.okignore` for pattern exclusions. Run `ok config migrate` to strip obsolete keys.
</Warning>

## `content.dir` content root

<ParamField body="content.dir" type="string" default=".">
Folder where Open Knowledge reads and writes documents, **relative to the project root** (the directory containing `.ok/`), not relative to `config.yml`. Project-scoped — only `.ok/config.yml` applies; user and project-local files cannot override it.
</ParamField>

Resolution:

```ts
resolveContentDir(config, projectRoot) → resolve(projectRoot, config.content.dir)
```

| `content.dir` value | Resolved path (project root `/repo`) |
| --- | --- |
| `.` (default) | `/repo` |
| `docs` | `/repo/docs` |
| `./content` | `/repo/content` |
| `/var/vault` (absolute) | `/var/vault` (ignores `projectRoot`) |

When `ok init` passes a non-`.` `contentDir` option, the scaffold writes an **uncommented** `content.dir` block instead of the commented placeholder. Git-root promotion does not auto-set a subfolder — `defaultContentDir` remains `.`; narrow scope by editing `content.dir` after init.

The suggested three-tier lifecycle (`external-sources/`, `research/`, `articles/`) in the scaffold template is documentation only — `initContent` does not create those directories.

Verify indexing scope:

```bash
ok preview
```

## `.okignore` exclusions

`.okignore` lives at the **project root** (scaffolded beside `.ok/`). It uses **gitignore syntax** (parsed by the `ignore` package) and combines with `.gitignore` in a single ignore instance inside `createContentFilter`.

| Behavior | Detail |
| --- | --- |
| Root patterns | Read from `<projectRoot>/.okignore` and `<projectRoot>/.gitignore` |
| Content-root patterns | When `content.dir` is a subfolder, also reads `<contentDir>/.okignore` and `<contentDir>/.gitignore`, prefixing patterns to project-relative paths |
| Nested files | Walks the content tree; honors nested `.okignore` / `.gitignore` at any folder depth (skips already-ignored directories) |
| Re-include | Leading `!` re-includes a path that `.gitignore` excluded |
| Git excludes | Also loads `.git/info/exclude` and global `core.excludesfile` when git is available |

Hard skips (independent of `.okignore`) include `.git`, `.ok`, `node_modules`, build caches, and secret-bearing paths (`.env`, `.ssh`, key files).

In the editor, `.okignore` syncs through the `__config__/okignore` CRDT document (`bindOkignoreDoc`); filesystem edits trigger a `ContentFilter` rebuild.

<RequestExample>

```gitignore title=".okignore"
# Exclude drafts from the document index
drafts/
*.draft.md

# Re-include a file gitignored elsewhere
!keep.md
```

</RequestExample>

<Tip>
Run `ok preview` after editing `.okignore` to see indexed document count and sample paths before starting the server.
</Tip>

## Scaffold lifecycle

<Steps>
<Step title="Run init">

```bash
ok init
```

`initContent` creates `.ok/config.yml`, `.ok/.gitignore`, and `.okignore` when missing. Existing files are never overwritten (idempotent). `.ok/.gitignore` entries are merged if the file already exists.

</Step>
<Step title="Optional: set content root">

Edit `.ok/config.yml` and uncomment or set:

```yaml
content:
  dir: docs
```

Paths are relative to the project root. Use `.okignore` for exclusions, not removed `content.include` / `content.exclude` keys.

</Step>
<Step title="Verify anchor">

Confirm `.ok/config.yml` exists as a file and `ok preview` reports the expected content directory and document count.

</Step>
<Step title="Start server">

```bash
ok start --open
```

Creates `.ok/local/` (config, locks, telemetry). Project-local settings written here stay off git.

</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Initialize a project" href="/initialize-project">
Run `ok init` end-to-end: git setup, skills, and MCP registration beyond the bare scaffold.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
Full YAML key catalog, precedence details, JSON Schema paths, and environment-only variables.
</Card>
<Card title="Core concepts" href="/core-concepts">
Three-layer model, filesystem-as-database, link graph, and git-backed attribution.
</Card>
<Card title="Folders and templates" href="/folders-and-templates">
Nested `<folder>/.ok/frontmatter.yml` and template resolution inside the content tree.
</Card>
</CardGroup>

---

## 06. Collaboration server

> Hocuspocus/Yjs CRDT server lifecycle, per-project `ok start` locks, server-free MCP reads (`exec`, `preview_url`) versus server-routed writes, and protocol version boundaries.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/06-collaboration-server.md
- Generated: 2026-06-25T22:42:41.394Z

### Source Files

- `packages/server/src/index.ts`
- `packages/cli/src/commands/start.ts`
- `packages/cli/src/commands/mcp.ts`
- `packages/cli/src/commands/lock-state.ts`
- `packages/server/src/mcp/tools/exec.ts`
- `packages/server/src/mcp/tools/shared.ts`
- `packages/core/src/protocol-version.ts`

---
title: "Collaboration server"
description: "Hocuspocus/Yjs CRDT server lifecycle, per-project `ok start` locks, server-free MCP reads (`exec`, `preview_url`) versus server-routed writes, and protocol version boundaries."
---

Open Knowledge runs one **collaboration server per project**. That process hosts a Hocuspocus/Yjs CRDT layer for real-time editing, an HTTP API for agent writes and search, and (when not in ephemeral mode) an HTTP MCP endpoint at `/mcp`. The editor UI is a separate sibling process (`ok ui`) that connects over WebSocket; agents typically reach the server through MCP, which may auto-start it on demand.

## Architecture

The server is built on **Hocuspocus** with **Yjs** documents. Each markdown file maps to a Y.Doc; a persistence extension debounces writes back to disk and coordinates with the shadow-repo timeline. Extensions also maintain live derived indexes (backlinks, tags), agent sessions, and CC1 config broadcast.

```mermaid
flowchart TB
  subgraph clients [Clients]
    Editor["Editor UI (`ok ui`)"]
    Agent["Agent MCP client"]
    Desktop["OK Desktop"]
  end

  subgraph server [Collaboration server (`ok start`)]
    HTTP["HTTP server"]
    WS["WebSocket `/collab`"]
    Hocus["Hocuspocus + Yjs"]
    Persist["Persistence extension"]
    MCP["HTTP MCP `/mcp`"]
    API["HTTP API `/api/*`"]
  end

  subgraph disk [Filesystem]
    Content["Content dir (`content.dir`)"]
    Local["`.ok/local/` locks + state"]
  end

  Editor --> WS
  Agent --> MCP
  Desktop --> HTTP
  WS --> Hocus
  MCP --> API
  API --> Hocus
  Hocus --> Persist
  Persist --> Content
  server --> Local
```

| Surface | Path / entry | Purpose |
| --- | --- | --- |
| CRDT sync | WebSocket `/collab` | Editor and in-process agent sessions |
| Keepalive | WebSocket `/collab/keepalive` | MCP session presence (loopback only) |
| Agent HTTP API | `/api/*` | Writes, search, documents, share, local-ops |
| MCP over HTTP | `/mcp` | Streamable HTTP MCP when server is running |
| Global MCP stdio | `ok mcp` | Per-call project routing; proxies to `/mcp` or auto-starts server |

Loopback and host-header checks apply to `/mcp` and collaboration sockets. The React editor shell is **not** served by the collab server unless you pass `--react-shell-dist-dir`; normally `ok start` auto-spawns `ok ui` as a sibling.

## Server lifecycle

### Start

<Steps>
<Step title="Prerequisites">

Run `ok init` so the project has `.ok/config.yml`. From the project root:

```bash
ok start
```

Use `ok start --open` to open the editor URL after the server is ready.

</Step>

<Step title="Boot sequence">

`ok start` calls `bootStartServer`, which:

1. Verifies the project scaffold (unless `--single-file` ephemeral mode).
2. Runs reclaim sweeps for MCP configs, launch.json, and skills.
3. Boots the collab server via `bootServer` → `createServer` (acquires `server.lock`, asserts state manifest compatibility, constructs Hocuspocus).
4. Mounts HTTP handlers (`/mcp`, `/api/*`, optional content assets).
5. Listens on the chosen host/port and writes the bound port into `server.lock`.
6. Optionally auto-spawns `ok ui` when no live `ui.lock` exists.

Default idle shutdown threshold is **30 minutes** with no WebSocket clients on `/collab`.

</Step>

<Step title="Verify">

```bash
ok status
```

Expect `server` and `ui` entries showing `alive` with pid and port. The start banner prints the editor URL and API URL.

</Step>
</Steps>

<ParamField body="--port" type="number">
Collab server listen port. Also respected via `PORT` env unless `--ui-port` is set (worktree-preview connect path).
</ParamField>

<ParamField body="--ui-port" type="number">
Pin the UI sibling to a port. If a server already holds the project lock, `ok start --ui-port <n>` connects a UI sibling instead of starting a second server.
</ParamField>

<ParamField body="--host" type="string">
Bind address. Defaults to `localhost`; overridable via `HOST` env.
</ParamField>

<ParamField body="--single-file" type="string">
Ephemeral mode: scope the server to one markdown file. Git and MCP HTTP are off; useful for quick notes without a full project.
</ParamField>

### Lock kinds

When the server acquires `server.lock`, it records a **kind**:

| Kind | Set by | Typical behavior |
| --- | --- | --- |
| `interactive` | `ok start`, desktop | Stays up until you quit or `ok stop` |
| `mcp-spawned` | MCP auto-start (`OK_LOCK_KIND=mcp-spawned`) | Subject to idle shutdown (~30 min with no `/collab` clients) |

Collision errors are tailored: an `interactive` lock means the desktop or CLI server is already running; an `mcp-spawned` lock suggests waiting for idle shutdown or running `ok stop`.

### Idle shutdown

`attachIdleShutdown` watches WebSocket upgrades on `/collab`. When the client count hits zero, a timer starts (default 30 min, warn 5 min before). On fire, the handler SIGTERM/SIGKILLs the UI sibling if needed, then runs `destroy()`.

MCP-spawned servers rely on this path to release locks without manual `ok stop`.

### Graceful shutdown

On SIGINT/SIGTERM, `ok start` prints a shutdown notice and calls `destroy()`, which sequentially:

1. Detaches idle-shutdown watcher
2. Shuts down mount layer (WebSocket server, keepalive timers)
3. Closes MCP HTTP handler
4. Closes HTTP server
5. Destroys Hocuspocus (flushes Y.Doc persistence)
6. Releases `ui.lock` if owned
7. Flushes telemetry and log sinks

Each step has a 5 s timeout; partial failures are aggregated but the lock is still released when the owning process exits cleanly.

## Per-project locks

Runtime state lives under **`.ok/local/`** (the lock directory). Two JSON lock files coordinate processes:

:::files
.ok/local/
├── server.lock    # Collaboration server (pid, port, kind, versions)
├── ui.lock        # Editor UI sibling (pid, port)
├── state.json     # State manifest (schema + version metadata)
└── last-spawn-error.log
:::

### Lock metadata

Each lock file stores:

| Field | Meaning |
| --- | --- |
| `pid` | Owning process |
| `hostname` | Machine that created the lock |
| `port` | Bound HTTP port (0 until listen completes) |
| `startedAt` | ISO timestamp |
| `worktreeRoot` | Project directory the lock covers |
| `kind` | `interactive` or `mcp-spawned` (server only) |
| `protocolVersion` | Integer collab protocol version (currently `1`) |
| `runtimeVersion` | Package semver of the owning binary |

### Lock inspection states

`ok status` and `ok stop` classify locks through `inspectLock`:

| Status | Meaning | Action |
| --- | --- | --- |
| `missing` | No lock file | Safe to start |
| `alive` | Local host, live pid | Server/UI running |
| `dead-pid` | Stale lock | Run `ok clean` |
| `corrupt` | Unparseable JSON | Run `ok clean` |
| `foreign-host` | Different hostname | Cannot verify locally; `ok stop` may still signal remote pid |

`ok stop` sends SIGTERM to alive `server` and `ui` targets. `ok clean` removes stale locks. `ok ps` lists locks across discovered projects.

## MCP routing: server-free reads vs server-routed writes

MCP tools split into two classes based on whether they need a live Hocuspocus instance.

### Server-free (no collab server required)

These tools work against the filesystem and project config. They do **not** call `/api/*` and do not mutate Yjs state.

| Tool | Behavior without server |
| --- | --- |
| `exec` | Read-only bash allowlist (`cat`, `grep`, `ls`, …) over content; enriches paths with frontmatter, backlinks (batch fetch is optional when server URL resolves), shadow-repo activity |
| `workflow` | Returns role-specific prose frames; `previewUrl: null` |
| `config` | Reads merged YAML config scopes |
| `palette` | Lists editor command palette entries from config |

`exec` enforces a security invariant: if content mtimes change during a read-only call, the tool fails with `security_invariant_violation`.

For literal-string search without embeddings, prefer `exec({ command: "grep -rn …" })` over `search` when the server is down.

### Server-routed (Hocuspocus required)

These tools resolve a server URL and POST/PUT/DELETE to HTTP API routes. Without a running server they return:

```
Error: Hocuspocus server is not running. Start it with `ok start`, then retry.
For disk-only writes without real-time sync, use your native Edit tool directly.
```

| Tool | Needs server for |
| --- | --- |
| `write`, `edit`, `delete`, `move` | Yjs-backed document mutations + disk persistence |
| `search` | Semantic / indexed search via `/api/search` |
| `links` | Link graph queries |
| `history`, `checkpoint`, `restore_version` | Shadow-repo timeline |
| `conflicts`, `resolve_conflict` | Merge conflict state |
| `share_link` | Share URL generation |

### `preview_url` — special case

`preview_url` is not a CRDT write, but it **can auto-start** the collab server (same `OK_MCP_AUTOSTART` gate as write tools) to bring up the UI and return a browser-reachable URL. Per-response `previewUrl` fields on `exec`/`write`/`edit` are **route-only** (`/#/<doc>`); call `preview_url` for a full openable URL.

When the server runs but no UI has bound, the tool returns a hint to retry or run `ok ui`.

### Global MCP (`ok mcp`) routing

The global stdio MCP server registers all tools with a **per-call** `serverUrl` resolver:

1. Resolve project root from explicit `cwd`, sticky session anchor, or the client's single MCP `roots` entry.
2. Read `server.lock`; if alive, return `http://localhost:<port>`.
3. If no server and `OK_MCP_AUTOSTART` is not `0`, spawn detached `ok start` with `OK_LOCK_KIND=mcp-spawned`, poll until port appears (default 5 s timeout, overridable via `OK_MCP_SPAWN_TIMEOUT_MS`).
4. Start a keepalive WebSocket per project so agent presence is visible in the editor.

<ParamField body="cwd" type="string" required={false}>
Absolute path inside an Open Knowledge project. Required for global MCP when the client advertises zero or multiple roots; optional when anchored to one project or exactly one root is advertised.
</ParamField>

<ParamField body="OK_MCP_AUTOSTART" type="env">
Set to `0` to disable auto-start. Write tools and `preview_url` then fail until you run `ok start` manually.
</ParamField>

<ParamField body="OK_MCP_SPAWN_TIMEOUT_MS" type="env">
Milliseconds to wait for auto-spawned server to bind (default `5000`).
</ParamField>

On macOS, `ok mcp` may proxy to the Desktop bundle's `ok` binary when installed; use `--no-bundle-proxy` or `OK_BUNDLE_PROXY=0` to run the npm-fetched server in-process.

### Per-project HTTP MCP shim

When the collab server is already running, editors can register MCP against `http://localhost:<port>/mcp` directly:

```bash
ok mcp --port 4321
```

This bridges stdio MCP to the HTTP endpoint without auto-start logic.

## Protocol version boundaries

Open Knowledge tracks three independent version numbers:

| Constant | Value | Role |
| --- | --- | --- |
| `PROTOCOL_VERSION` | `1` | Collab wire protocol; must match across clients and lock metadata |
| `STATE_SCHEMA_VERSION` | `1` | `.ok/local/state.json` manifest schema |
| `RUNTIME_VERSION` | package semver | npm/DMG release identity |

### State manifest (`.ok/local/state.json`)

On boot, `assertCompatibleStateManifest` either:

- **Refuses boot** if `stateSchemaVersion` is incompatible (no on-the-fly migration).
- **Writes a fresh manifest** for new projects.
- **Adopts** pre-versioned projects at schema `0` when a shadow repo already exists.

The manifest records `createdBy` and `lastWriteBy` with `runtimeVersion` and `protocolVersion`.

### Lock version gates

New locks auto-populate `protocolVersion` and `runtimeVersion`. A live lock missing either field is treated as **incompatible** (`missing-fields`) and will not be reused for routing decisions that require version metadata.

### Client ↔ server drift

HTTP and WebSocket clients send version headers (`x-ok-client-protocol`, `x-ok-client-runtime`, `x-ok-client-kind`). The desktop shell classifies attached servers:

| Drift | Result |
| --- | --- |
| `protocolVersion` differs | `older` or `newer` on **protocol** dimension — hard boundary |
| Same protocol, different runtime semver | `older` / `newer` on **runtime** dimension |
| Missing version fields in lock | `indeterminate` |

Protocol mismatches are the hard line: they indicate incompatible CRDT or API contracts. Runtime drift is informational (semver compare) unless versions are unresolvable.

## Operational commands

| Command | Purpose |
| --- | --- |
| `ok start` | Boot collab server (+ auto UI sibling) |
| `ok stop` | SIGTERM server and UI from lock pids |
| `ok status` | Human-readable lock report |
| `ok ps` | List locks across projects |
| `ok clean` | Remove stale/corrupt locks |
| `ok diagnose health` | Include server-lock health check |

<RequestExample>

```bash
ok start --open
```

</RequestExample>

<ResponseExample>

```
open-knowledge 0.x.x

  Editor   http://localhost:5173
  API      http://localhost:4321

  Open the Editor URL in your browser to start editing.
```

</ResponseExample>

## Failure modes

<AccordionGroup>
<Accordion title="Server lock collision">

Another process holds `server.lock` with a live pid. Run `ok status` to see pid/port/kind. Use `ok stop`, quit the desktop app, or `--cwd` to point at a different project. For `mcp-spawned` locks, wait for idle shutdown or stop explicitly.

</Accordion>

<Accordion title="MCP write fails with Hocuspocus not running">

The global MCP resolver could not find or spawn a server. Start manually with `ok start`, or ensure `OK_MCP_AUTOSTART` is not `0`. Check `.ok/local/last-spawn-error.log` after failed auto-start.

</Accordion>

<Accordion title="Stale or corrupt locks">

`ok status` reports `dead-pid` or `corrupt`. Run `ok clean` before restarting.

</Accordion>

<Accordion title="State manifest incompatible">

Boot aborts with `StateManifestError` when `stateSchemaVersion` does not match the binary. Upgrade/downgrade the Open Knowledge package to a compatible release; on-the-fly migration is intentionally out of scope.

</Accordion>

<Accordion title="Degraded subsystems at start">

After `ready`, `ok start` may warn about degraded components (`shadow-repo`, `file-watcher`, `head-watcher`). The server stays up but version history, external file sync, or branch-switch safety may be limited — check server logs.

</Accordion>

<Accordion title="exec security invariant violation">

A read-only `exec` call detected content file mutations. This indicates a parser bug; do not trust the output. Report via `ok diagnose bundle`.

</Accordion>
</AccordionGroup>

## Related pages

<Card href="/quickstart" title="Quickstart" icon="rocket">
Run `ok init`, `ok start --open`, and verify MCP tools respond.
</Card>

<Card href="/mcp-tools-reference" title="MCP tools reference" icon="wrench">
Full tool catalog, input nesting, and preview envelope semantics.
</Card>

<Card href="/http-api-reference" title="HTTP API reference" icon="globe">
Agent and editor routes the server-routed MCP tools call.
</Card>

<Card href="/cli-reference" title="CLI reference" icon="terminal">
`start`, `stop`, `status`, `ps`, and `clean` flags and behavior.
</Card>

<Card href="/troubleshooting" title="Troubleshooting" icon="life-buoy">
Lock diagnosis, `ok diagnose health`, and crash recovery with `ok clean`.
</Card>

<Card href="/wire-agent-editors" title="Wire agent editors" icon="plug">
Register MCP, verify with `exec`, and configure auto-start behavior.
</Card>

---

## 07. Initialize a project

> Run `ok init` to scaffold `.ok/`, initialize git, install project and user skills, and register MCP entries for detected editors (Claude Code, Cursor, Codex).

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/07-initialize-a-project.md
- Generated: 2026-06-25T22:42:09.786Z

### Source Files

- `packages/cli/src/commands/init.ts`
- `packages/server/src/init-project.ts`
- `packages/cli/src/integrations/write-project-skill.ts`
- `packages/cli/src/commands/editors.ts`
- `packages/cli/src/commands/seed.ts`
- `packages/cli/src/sharing/git-exclude.ts`

---
title: "Initialize a project"
description: "Run `ok init` to scaffold `.ok/`, initialize git, install project and user skills, and register MCP entries for detected editors (Claude Code, Cursor, Codex)."
---

`ok init` is the one-shot bootstrap command that turns any directory into an Open Knowledge project. It resolves the project root, scaffolds `.ok/` and `.okignore`, initializes git when needed, registers the `open-knowledge` MCP server in detected agent editors, installs bundled skills at both project and user scope, and optionally configures config-sharing posture. The command is idempotent: re-running updates MCP entries and merges missing `.ok/.gitignore` lines without destroying existing configuration.

## Prerequisites

Before running `ok init`, ensure the following are available on the machine:

| Requirement | Why |
| --- | --- |
| [Installation](/installation) of `@inkeep/open-knowledge` or OK Desktop | Provides the `ok` CLI and bundled skill assets |
| `git` on `PATH` | Required to initialize a parent repo when none exists |
| At least one supported editor config directory | MCP registration skips editors whose config root is absent |
| Network access (first run) | User-global skill install invokes `npx skills@~1.5.0` |

Supported editors: **Claude** (Claude Code), **Claude Desktop**, **Cursor**, and **Codex**. Init detects installed editors by probing each editor's config directory; editors without a detectable config root are skipped with `skipped-missing`.

## Quick start

<Steps>
<Step title="Navigate to your project directory">

```bash
cd /path/to/my-knowledge-base
```

Init uses the current working directory unless you pass `--cwd` through a parent command.

</Step>
<Step title="Run init">

```bash
ok init
```

On an interactive terminal, init prompts for MCP scope (user-level, project-level, or both) and sharing mode (shared vs local-only). Non-interactive runs default MCP scope to `both` and sharing mode to `shared` (or preserve an existing local-only posture).

</Step>
<Step title="Verify scaffold and editor wiring">

Confirm these artifacts exist:

- `.ok/config.yml` — project marker (required for `ok seed` and server commands)
- `.ok/.gitignore` — excludes machine-local runtime files under `.ok/local/`
- `.okignore` — document-index exclusions (gitignore syntax)
- Editor MCP entries showing `registered` or `updated` in the command output

</Step>
<Step title="Approve MCP in your editor">

Open a configured editor, approve the `open-knowledge` MCP server when prompted, and run a simple tool call (for example, `exec` with `pwd`) to confirm connectivity.

</Step>
</Steps>

## What init creates

Init does **not** scaffold content folders (`articles/`, `research/`, etc.) or starter templates. Those come from a separate `ok seed` step. Init only lays down project infrastructure and editor integration.

:::files
my-project/
├── .git/                          # created if absent (branch: main)
├── .gitignore                     # seeded on fresh git init (.DS_Store)
├── .okignore                      # index-exclusion template
├── .ok/
│   ├── config.yml                 # commented defaults + JSON Schema pointer
│   └── .gitignore                 # ignores .ok/local/, locks, PII sidecars
├── .mcp.json                      # project MCP (Claude Code), if scope includes project
├── .cursor/
│   ├── mcp.json                   # project MCP (Cursor), if scope includes project
│   └── skills/open-knowledge/     # project skill bundle
├── .claude/
│   ├── launch.json                # UI preview server (Claude Code only)
│   └── skills/open-knowledge/     # project skill bundle
├── .codex/
│   └── config.toml                # project MCP (Codex), if scope includes project
└── .agents/skills/open-knowledge/ # project skill bundle (Codex)
:::

### `.ok/config.yml`

The scaffolded config is entirely commented defaults with a `$schema` URL pointing at the published JSON Schema. No keys are active until uncommented. The file serves as the project marker (`OK_PROJECT_MARKER` = `.ok/config.yml`); commands like `ok seed` refuse to run without it.

### `.ok/.gitignore`

Auto-ignores machine-local runtime artifacts:

- `.ok/local/` (caches, locks, manifests)
- `principal.json`, `state.json`, `server.lock`, `ui.lock`, `sync-state.json`, `last-spawn-error.log`

Only `config.yml` at the `.ok/` root is intended for version control.

### `.okignore`

A commented template for paths excluded from the Open Knowledge document index. Patterns use gitignore syntax and combine with `.gitignore` via a single ignore-lib instance.

## Init pipeline

```mermaid
sequenceDiagram
    participant User
    participant CLI as ok init
    participant Root as resolveProjectRoot
    participant Git as ensureProjectGit
    participant Content as initContent
    participant MCP as writeEditorMcpConfig
    participant Skills as writeProjectSkill / installUserSkill
    participant Share as applySharingMode

    User->>CLI: ok init
    CLI->>Root: resolve project root
    Root-->>CLI: projectRoot, promotion flags
    CLI->>Git: ensureProjectGit(projectRoot)
    Git-->>CLI: didInit
    CLI->>Content: initContent(projectRoot)
    Content-->>CLI: created / updated / skipped
    CLI->>MCP: register per editor + scope
    MCP-->>CLI: written / overwritten / skipped
    CLI->>Skills: project skills + user-global skill
    Skills-->>CLI: installed / skip-current / failed
    CLI->>Share: applySharingMode
    Share-->>CLI: shared / local-only outcome
    CLI-->>User: formatted summary + preview
```

### Project root resolution

Before scaffolding, `resolveProjectRoot` walks ancestors (up to 30 levels) looking for an existing `.ok/config.yml`. If found, init opens that ancestor project instead of creating a nested one.

If no ancestor project exists and the cwd sits inside a git working tree below the home directory, init promotes to the git root. A message explains the promotion:

```
[ok] Initialized OK at <git-root> — opened parent of <subdir> because it contains a .git folder
```

Otherwise, init uses the realpath of the current directory as `projectRoot`.

## Git initialization

`ensureProjectGit` runs before content scaffolding:

| Condition | Behavior |
| --- | --- |
| No `.git/` and not inside an existing work tree | Runs `git init --initial-branch=main` |
| Valid `.git/HEAD` present | Skips init (`didGitInit: false`) |
| Partial `.git/` missing `HEAD` | Repairs via `git init` |
| Inside an existing work tree (subdirectory of a repo) | Skips init |

On a fresh `git init`, init also seeds a root `.gitignore` with `.DS_Store` when none exists.

If `git init` fails (git not installed, permission error), init exits with code 1 and prints:

```
open-knowledge requires git to initialize a parent repo. Install git or run 'git init' yourself, then re-run.
```

## MCP server registration

By default, MCP registration is enabled (`--mcp` defaults to `true`). Pass `--no-mcp` to scaffold `.ok/` only.

### Detected editors and config paths

| Editor | User-level config | Project-level config | MCP key |
| --- | --- | --- | --- |
| Claude (Code) | `~/.claude.json` | `.mcp.json` | `mcpServers` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) | — | `mcpServers` |
| Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` | `mcpServers` |
| Codex | `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`) | `.codex/config.toml` | `mcp_servers` |

All editors register the server name `open-knowledge`.

### MCP entry format

Published mode (default) writes a shell chain entry that resolves the CLI in order: OK Desktop user bundle → system bundle → `npx @inkeep/open-knowledge@latest mcp` → common Node install paths.

<RequestExample>

```json title="Claude Code MCP entry (published)"
{
  "command": "/bin/sh",
  "args": ["-l", "-c", "# ok-mcp-v1\n...chain resolves to ok mcp..."]
}
```

</RequestExample>

Dev mode (`--dev-mcp`) replaces the chain with a direct `node packages/cli/dist/cli.mjs mcp` invocation and sets `MCP_DEBUG=1` plus `OK_LOG_FILE=/tmp/ok-mcp.log`. Intended for contributors running the local CLI from the monorepo.

### MCP scope

<ParamField body="--scope" type="user | project | both">
Controls where MCP config is written. Default on TTY: interactive checkbox (both checked). Default non-TTY: `both`.

- `user` — writes only to home-directory editor configs
- `project` — writes only to project-local configs (`.mcp.json`, `.cursor/mcp.json`, `.codex/config.toml`)
- `both` — writes to both levels
</ParamField>

Claude Desktop does not support project-level MCP config; when project scope is selected, init reports that Claude Desktop was skipped for project-level registration.

### Claude Code launch.json

When Claude is detected and MCP is enabled, init also creates or merges `.claude/launch.json` with an `open-knowledge-ui` configuration. This wires Claude Code Desktop's embedded browser to start the OK web editor via a published shell chain (`ok start --ui-port`).

### MCP overwrite semantics

Init **replaces** the entire `open-knowledge` MCP entry on each run. Custom fields (`cwd`, `env`, alternate commands) on a prior entry are not merged — they are overwritten with the managed chain. Other MCP servers in the same config file are preserved.

## Skill installation

Init installs skills at two scopes. Skill packs are bundled file assets copied from the CLI package — they are provider-neutral and work with any MCP-connected agent host.

### Project-local skills

For every editor that defines a `projectSkillPath`, init copies the bundled **project** skill directory:

| Editor | Project skill path |
| --- | --- |
| Claude | `.claude/skills/open-knowledge/SKILL.md` |
| Cursor | `.cursor/skills/open-knowledge/SKILL.md` |
| Codex | `.agents/skills/open-knowledge/SKILL.md` |

Existing skill directories are removed and replaced (overwrite semantics). Writes are guarded against symlink escape outside the project root.

### User-global skill

`installUserSkill` runs on every init via `npx skills@~1.5.0 add <discovery-bundle> --agent '*' -g -y --copy`. This installs the **discovery** skill bundle to `~/.agents/skills/open-knowledge-discovery` across detected agent hosts.

| Outcome | Meaning |
| --- | --- |
| `installed` | Fresh install at current package version |
| `skip-current` | Already at current version with files present |
| `failed` | `npx` unavailable, timeout, or subprocess error — MCP config still written |

On failure, init prints a manual recovery command. MCP registration is independent of skill install success.

If Claude Desktop is detected, init suggests running `ok install-skill` separately for Claude Chat and Cowork (a `.skill` zip handoff, not part of the standard init path).

## Config sharing mode

Init configures whether OK artifacts are committed with project content or kept per-clone via `.git/info/exclude`.

<Tabs>
<Tab title="Shared (default)">

OK config files (`.ok/`, `.mcp.json`, project skills, `.claude/launch.json`, `.okignore`) are intended for version control. Teammates inherit the same MCP and skill setup on clone.

```bash
ok init --shared
```

</Tab>
<Tab title="Local only">

OK artifacts are appended to `.git/info/exclude` — a per-clone, uncommitted file. Teammates do not see your OK wiring.

```bash
ok init --local-only
```

Switch back later with `ok config-sharing share`.

</Tab>
</Tabs>

### Artifacts managed by sharing mode

The exclude list covers:

- `.ok/`
- `.okignore`
- `.mcp.json`
- `.cursor/mcp.json` and `.cursor/skills/open-knowledge/`
- `.claude/skills/open-knowledge/` and `.claude/launch.json`
- `.codex/config.toml`
- `.agents/skills/open-knowledge/`

### Sharing failure modes

| Situation | Behavior |
| --- | --- |
| `--local-only` but no git repo | Option ignored; warning printed |
| `--local-only` but OK files already tracked upstream | Refused with `git rm --cached` remediation steps; init still exits 0 |
| Re-run without flags on local-only repo | Preserves local-only posture |
| Explicit `--shared` after local-only | Removes OK paths from exclude file |

## CLI flags reference

<ParamField body="--mcp" type="boolean" default="true">
Register the MCP server for detected editors.
</ParamField>

<ParamField body="--no-mcp" type="boolean">
Scaffold `.ok/` only. Skips all MCP config, launch.json, and editor registration. Project skills are still written.
</ParamField>

<ParamField body="--dev-mcp" type="boolean">
Register a local dev MCP entry using `node packages/cli/dist/cli.mjs mcp` with debug logging. For monorepo contributors.
</ParamField>

<ParamField body="--scope" type="user | project | both">
Non-interactive MCP scope selection. Conflicts with TTY checkbox prompt when omitted on a terminal.
</ParamField>

<ParamField body="--shared" type="boolean">
Commit OK config alongside content. Default for fresh repos. Conflicts with `--local-only`.
</ParamField>

<ParamField body="--local-only" type="boolean">
Keep OK config out of git via `.git/info/exclude`. Conflicts with `--shared`.
</ParamField>

## Exit codes and output

| Exit code | Condition |
| --- | --- |
| `0` | Success, or partial success (e.g., sharing refusal, skill install failure without MCP failure) |
| `1` | Any editor MCP write failed, or `ProjectGitInitError` |

The formatted output includes:

- Git init status and `.gitignore` seed confirmation
- Content scaffold summary (`Created` / `Updated` / `Skipped`)
- Per-editor MCP registration status with relative config paths
- Project-local skill install status
- User-global skill install status
- Sharing mode outcome
- Content preview block (when preview loads successfully)
- Next-steps checklist when at least one MCP entry was written

## Idempotent re-runs

Re-running `ok init` on an initialized project is safe:

- `.ok/config.yml` and `.okignore` are not overwritten if they already exist
- `.ok/.gitignore` gains any missing required lines
- MCP entries for `open-knowledge` are refreshed to the current managed chain
- Project skills are replaced with the latest bundled version
- User-global skill skips install when version matches and files exist
- Sharing posture is preserved unless an explicit `--shared` or `--local-only` flag overrides

Init does **not** start the collaboration server, index documents, or create starter content.

## What comes after init

<AccordionGroup>
<Accordion title="Scaffold starter content">

```bash
ok seed              # Karpathy three-layer knowledge base (default pack)
ok seed --list-packs # see all starter packs
```

`ok seed` requires `.ok/config.yml` from init. Without it, seed exits with a prerequisite error.

</Accordion>
<Accordion title="Start the editor">

```bash
ok start --open
```

See [Quickstart](/quickstart) for the full first-edit path.

</Accordion>
<Accordion title="Repair stale editor wiring">

If MCP or skills drift after an upgrade:

```bash
ok repair-skills
```

Or re-run `ok init` to refresh MCP entries and project skills.

</Accordion>
</AccordionGroup>

## Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `No supported editor config directories detected` | No editor installed or config dir missing | Install an editor, create its config directory, re-run `ok init` |
| `git init failed` | Git not installed | Install git or run `git init` manually, then re-run |
| `open-knowledge install failed` | `npx` unavailable or network error | Run the manual `npx skills@~1.5.0 add ...` command from output |
| `Content scaffolding failed` | Symlink at `.ok/` or permission error | Remove untrusted symlinks, check write permissions |
| `Refusing to write through a symbolic link` | Symlink in project skill path | Remove the symlink, re-run |
| MCP `FAILED` for one editor | Corrupt or locked config file | Fix JSON/TOML syntax or remove stale lock file, re-run |
| `--local-only` ignored | No git repo at project root | Run `git init`, then `ok config-sharing unshare` |
| Sharing switch refused | OK files already tracked in git | Follow `git rm --cached` remediation in output |

For deeper diagnostics, see [Troubleshooting](/troubleshooting).

## Related pages

<Card href="/quickstart" title="Quickstart" icon="rocket">
Run `ok init`, `ok start --open`, and verify MCP tools respond from a connected agent.
</Card>

<Card href="/project-scaffold" title="Project scaffold" icon="folder-tree">
`.ok/` directory layout, config scopes, `.okignore`, and `content.dir` semantics.
</Card>

<Card href="/wire-agent-editors" title="Wire agent editors" icon="plug">
MCP registration details, bundled skills, `ok repair-skills`, and verification prompts.
</Card>

<Card href="/cli-reference" title="CLI reference" icon="terminal">
All `ok` subcommands, global flags, and server management commands.
</Card>

<Card href="/configuration-reference" title="Configuration reference" icon="settings">
YAML config keys, precedence order, and published JSON Schema paths.
</Card>

---

## 08. Wire agent editors

> Connect Claude Code, Cursor, and Codex via MCP registration, bundled skills, `ok repair-skills`, and verification prompts that exercise the `exec` tool.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/08-wire-agent-editors.md
- Generated: 2026-06-25T22:42:16.529Z

### Source Files

- `packages/cli/src/commands/editors.ts`
- `packages/cli/src/commands/repair-mcp-configs.ts`
- `packages/cli/src/commands/install-skill.ts`
- `packages/cli/src/commands/repair-skills.ts`
- `docs/content/integrations/claude-code.mdx`
- `docs/content/integrations/cursor.mdx`
- `docs/content/integrations/codex.mdx`

---
title: "Wire agent editors"
description: "Connect Claude Code, Cursor, and Codex via MCP registration, bundled skills, `ok repair-skills`, and verification prompts that exercise the `exec` tool."
---

Open Knowledge registers the `open-knowledge` MCP server and installs bundled agent skills into Claude Code, Cursor, and Codex. `ok init` performs the first-time wiring; every `ok start` runs reclaim sweeps that refresh stale MCP entries and skill bundles. The project-local `open-knowledge` skill teaches agents to route markdown reads through the MCP `exec` tool instead of native file tools.

## Supported editors

| Editor | MCP server name | User config path | Project config path | Project skill path |
| --- | --- | --- | --- | --- |
| Claude Code | `open-knowledge` | `~/.claude.json` (`mcpServers`) | `.mcp.json` | `.claude/skills/open-knowledge/SKILL.md` |
| Claude Desktop | `open-knowledge` | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) | — | — |
| Cursor | `open-knowledge` | `~/.cursor/mcp.json` (`mcpServers`) | `.cursor/mcp.json` | `.cursor/skills/open-knowledge/SKILL.md` |
| Codex | `open-knowledge` | `~/.codex/config.toml` (`mcp_servers`; override home with `CODEX_HOME`) | `.codex/config.toml` | `.agents/skills/open-knowledge/SKILL.md` |

`ok init` detects installed editors by probing each editor's config directory. Editors that are not installed are skipped with `skipped-missing` rather than creating config files for absent tools.

<Note>
Claude Desktop's **Code** tab uses the same MCP wiring as Claude Code. Claude **Chat** and **Cowork** use a separate upload flow via `ok install-skill` — they do not read project MCP config.
</Note>

## MCP entry format

Published installs write a managed shell-chain entry tagged with the `# ok-mcp-v1` sentinel. The chain resolves the `ok mcp` subprocess in order:

1. `~/Applications/Open Knowledge.app/Contents/Resources/cli/bin/ok.sh mcp` (user-installed Desktop app)
2. `/Applications/Open Knowledge.app/Contents/Resources/cli/bin/ok.sh mcp` (system Desktop app)
3. `npx -y @inkeep/open-knowledge@latest mcp`
4. `npx` from common Node version-manager paths (`nvm`, `fnm`, `asdf`, Homebrew, Volta, and others)

If none resolve, the chain exits 127 with an install hint.

<ParamField body="command" type="string">
Always `/bin/sh` for published installs.
</ParamField>

<ParamField body="args" type="string[]">
`["-l", "-c", "<chain script>"]` where the script body includes `# ok-mcp-v1`.
</ParamField>

Contributor local development uses `--dev-mcp` on `ok init`, which registers `node packages/cli/dist/cli.mjs mcp` with `MCP_DEBUG=1` and `OK_LOG_FILE=/tmp/ok-mcp.log` instead of the published chain.

## Two skill bundles

Open Knowledge ships two bundled skills from `packages/server/assets/skills/`:

| Bundle | Skill name | Installed where | Purpose |
| --- | --- | --- | --- |
| **Project** | `open-knowledge` | Project tree under each editor's `skills/` directory | Runtime contract: STOP rules for native file tools on in-scope `.md`/`.mdx`, MCP tool routing table, preview handshake, grounding rules |
| **Discovery** | `open-knowledge-discovery` | `~/.agents/skills/open-knowledge-discovery/` plus per-host copies when the host directory exists | Install and sharing guidance only — no runtime read/write rules |

`ok init` always writes project-local `open-knowledge` skills for Claude Code, Cursor, and Codex. It also runs `installUserSkill` to place the discovery skill in the user's global skill directories.

<Warning>
Agents editing markdown inside an initialized project must load the **project-local** `open-knowledge` skill, not the discovery skill. If only discovery is loaded, advise the user to re-run `ok init`.
</Warning>

## Wire editors

<Tabs items={['macOS Desktop app', 'CLI (`ok init`)']}>

<Tab>

On first project open, OK Desktop shows a consent dialog that detects installed MCP-capable editors and offers to register `open-knowledge`. Click **Add** to write user-level MCP entries for each detected editor.

To re-trigger the dialog after skipping or changing editors, delete `~/.ok/mcp-status.json` and relaunch the Desktop app.

</Tab>

<Tab>

From the project root:

```bash
ok init
```

Non-interactive environments default to writing MCP config at **both** user and project scopes. In a TTY, `ok init` prompts for scope:

- **User-level** — `~/.claude.json`, `~/.cursor/mcp.json`, `~/.codex/config.toml`
- **Project-level** — `.mcp.json`, `.cursor/mcp.json`, `.codex/config.toml`

<ParamField body="--scope" type="user | project | both">
Override the interactive scope prompt.
</ParamField>

<ParamField body="--no-mcp" type="flag">
Scaffold `.ok/` without touching any editor MCP config.
</ParamField>

<ParamField body="--dev-mcp" type="flag">
Register a local dev MCP entry (`node` + `packages/cli/dist/cli.mjs`) instead of the published chain.
</ParamField>

<ParamField body="--shared" type="flag">
Commit OK config artifacts alongside content (default for fresh repos).
</ParamField>

<ParamField body="--local-only" type="flag">
Keep OK config out of git via `.git/info/exclude`.
</ParamField>

For Claude Code, `ok init` also scaffolds `.claude/launch.json` with an `open-knowledge-ui` configuration that starts the editor preview via the same resolution chain pattern (`# ok-ui-v1` sentinel).

</Tab>

</Tabs>

```mermaid
sequenceDiagram
  participant User
  participant CLI as ok init / ok start
  participant Config as Editor MCP config
  participant Skill as Bundled SKILL.md
  participant Agent as Claude / Cursor / Codex
  participant MCP as ok mcp (stdio)

  User->>CLI: ok init
  CLI->>Config: write open-knowledge entry (# ok-mcp-v1 chain)
  CLI->>Skill: copy project + discovery skills
  User->>Agent: open project, approve MCP server
  Agent->>MCP: spawn ok mcp subprocess
  MCP-->>Agent: tools/list (exec, search, write, …)
  Agent->>MCP: exec("ls -A …")
  MCP-->>Agent: enriched document listing
```

## Approve the MCP server

After wiring, each editor prompts you to approve the new `open-knowledge` MCP server on next project open.

| Editor | Approval behavior |
| --- | --- |
| Claude Code | May pre-approve via `--settings '{"enabledMcpjsonServers":["open-knowledge"]}'` when launched from Open with AI handoffs |
| Cursor | Shows an MCP approval prompt — approve `open-knowledge` before tools are visible |
| Codex | Restart Codex after wiring if tools do not appear |

Until approval completes, the agent cannot call Open Knowledge MCP tools.

## Automatic reclaim on `ok start`

Every `ok start` runs three reclaim sweeps before booting the collaboration server. Failures are logged and do not block server startup.

| Sweep | Function | What it fixes |
| --- | --- | --- |
| MCP config repair | `repairMcpConfigs` | Rewrites stale `open-knowledge` entries (legacy bare `npx`, direct bundle paths, symlinks) to the current `# ok-mcp-v1` chain |
| Launch JSON repair | `repairLaunchJson` | Updates `.claude/launch.json` `open-knowledge-ui` entry to the current `# ok-ui-v1` chain |
| Skill repair | `repairSkills` | Refreshes bundled `SKILL.md` files at project and user scopes |

<ParamField body="OK_RECLAIM_DISABLE" type="string">
Set to `1` to skip all three sweeps. Only the exact value `1` disables reclaim; other values (`0`, `true`, empty) do not.
</ParamField>

### MCP config repair outcomes

`repairMcpConfigs` scans user-level and project-level config for every editor in `ALL_EDITOR_IDS`. For each config file:

| Outcome | Meaning |
| --- | --- |
| `no-entry` | Config exists but has no `open-knowledge` server entry — left untouched |
| `canonical` | Entry already contains `# ok-mcp-v1` — no write |
| `repaired` | Stale entry rewritten to the managed chain |
| `write-failed` | Lock or I/O error during atomic write |

There is no standalone `ok repair-mcp-configs` command — MCP repair runs only as part of `ok start` (and Desktop launch reclaim).

## `ok repair-skills`

Force an explicit skill reclaim sweep outside of `ok start`:

```bash
ok repair-skills
```

**Project sweep** — for each editor host directory (`.claude`, `.cursor`, `.agents`):

| Outcome | Condition |
| --- | --- |
| `reclaimed` | Existing `SKILL.md` replaced with current project bundle |
| `created` | No skill file, but MCP config contains `# ok-mcp-v1` — skill created |
| `no-token` | No skill and no wired MCP entry — skipped |
| `failed` | Path safety or I/O error |

**User sweep** — refreshes `open-knowledge-discovery` at `~/.agents/skills/open-knowledge-discovery/` and per-host copies (`.claude/skills/`, `.cursor/skills/`) when the host directory exists. Skips when the recorded version in `~/.ok/skill-state.yml` already matches the bundled server package version.

<RequestExample>

```bash
ok repair-skills
```

</RequestExample>

<ResponseExample>

```text
Skill reclaim complete.
  Project: 2 reclaimed, 1 created, 0 no-token, 0 failed.
  User (0.19.0): 3 written, 0 skipped, 0 failed.
```

</ResponseExample>

Exit code is non-zero when the project sweep is skipped (bundle missing), user sweep fails (except `version-current`), or any individual write fails.

## Verify wiring with `exec`

Registration — not top-level tool-name visibility — is the test for whether Open Knowledge MCP is active. After approving the server, confirm the agent routes reads through MCP by asking:

<CopyPrompt>List the first 5 documents you come across in this project.</CopyPrompt>

A correctly wired agent calls the Open Knowledge `exec` tool (for example `exec("ls -A …")`) and returns document names with enriched metadata (frontmatter, backlink counts, recent activity). It should **not** use native `Read`, `Grep`, or `Glob` on in-scope markdown.

| Signal | Meaning |
| --- | --- |
| Agent lists project documents via `exec` | MCP + project skill wiring is working |
| Agent uses native file tools on `.md` files | Project skill not loaded, or MCP not approved — re-run `ok init`, restart editor |
| Agent reports MCP unavailable | `ok mcp` subprocess failed to start — check Node 24+, Desktop app, or `npx` resolution |
| `exec` works but writes fail | Collaboration server may be down — run `ok start` for server-routed writes |

<Info>
The `exec` tool is server-free for reads: it runs an allowlisted bash subset (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut`) against the content directory and returns enriched metadata per referenced wiki file. One command or pipe per call — no `&&`, `;`, or redirects.
</Info>

If verification fails after approval, restart the editor so it reloads MCP config and skills.

## Claude Desktop Chat and Cowork

`ok init` wiring does not reach Claude Chat or Cowork. For those surfaces:

```bash
ok install-skill
```

This builds `~/Downloads/openknowledge.skill` and opens Claude Desktop for manual upload (**Customize → Skills → + → Create skill → Upload skill**). Use `--force` to bypass the install-state gate and rebuild unconditionally.

## Troubleshooting

<AccordionGroup>

<Accordion title="Editor does not see Open Knowledge tools">

1. Confirm MCP config contains an `open-knowledge` entry with `# ok-mcp-v1` in the command chain.
2. Approve the server in the editor's MCP settings.
3. Restart the editor.
4. Run `ok start` to trigger MCP and skill reclaim sweeps, or `ok repair-skills` for skills only.
5. Re-run `ok init` to rewrite config and project skills.

</Accordion>

<Accordion title="Stale MCP entry after upgrade">

Legacy entries (`npx @inkeep/open-knowledge mcp`, direct bundle paths, symlinks) are rewritten automatically on `ok start`. Check stderr for `mcp-config-migrate` JSON events with `surface: cli-repair`.

</Accordion>

<Accordion title="Project skill missing but MCP is wired">

`ok repair-skills` creates the project skill when MCP config contains `# ok-mcp-v1` even if `SKILL.md` is absent. Without either signal, the sweep reports `no-token` for that editor.

</Accordion>

<Accordion title="Disable automatic reclaim">

Set `OK_RECLAIM_DISABLE=1` before `ok start` or `ok repair-skills` to skip MCP, launch.json, and skill sweeps. Useful when debugging custom MCP entries.

</Accordion>

<Accordion title="Re-trigger Desktop consent dialog">

Delete `~/.ok/mcp-status.json` and relaunch OK Desktop.

</Accordion>

</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Initialize a project" href="/initialize-project">
Run `ok init` to scaffold `.ok/`, install skills, and register MCP for detected editors.
</Card>
<Card title="Quickstart" href="/quickstart">
End-to-end path from install through first agent-driven edit.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Full `exec`, `search`, `write`, and 14 other MCP tool contracts.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
`ok diagnose health`, repair stale configs, and recover from crash leftovers.
</Card>
</CardGroup>

---

## 09. Editor workflows

> WYSIWYG and source-mode editing, ephemeral single-file sessions (`ok notes.md`), Open with AI handoffs, properties pane, timeline panel, and rich MDX embeds.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/09-editor-workflows.md
- Generated: 2026-06-25T22:44:12.537Z

### Source Files

- `docs/content/features/editor.mdx`
- `docs/content/features/assets-and-embeds.mdx`
- `packages/cli/src/commands/single-file-open.ts`
- `packages/cli/src/commands/single-file-dispatch.ts`
- `packages/cli/src/commands/open.ts`
- `packages/server/src/handoff-api.ts`
- `docs/content/features/timeline-and-recovery.mdx`

---
title: "Editor workflows"
description: "WYSIWYG and source-mode editing, ephemeral single-file sessions (`ok notes.md`), Open with AI handoffs, properties pane, timeline panel, and rich MDX embeds."
---

The Open Knowledge editor is a CRDT-backed web surface (`packages/app`) served by the collaboration server and opened via `ok start`, `ok ui`, `ok open`, or the `ok <file>` single-file dispatch in `packages/cli`. Each open document binds a TipTap WYSIWYG view and a CodeMirror source view to the same Yjs `source` text; the right document panel hosts Outline, Links, Graph, and Timeline tabs, while frontmatter properties render as a form above the WYSIWYG body.

## Editor layout

```text
┌─────────────────────────────────────────────────────────────────┐
│ File sidebar (project mode only) │ Editor canvas │ Document panel │
│                                   │  Properties*  │ Outline/Links/ │
│                                   │  WYSIWYG or   │ Graph/Timeline │
│                                   │  Source       │                │
│                                   │  Ask AI*      │                │
└─────────────────────────────────────────────────────────────────┘
  * Properties + Ask AI hidden in source mode / embedded / single-file constraints
```

| Surface | Default at ≥1024px | Embedded AI-editor webview | Single-file (`ok notes.md`) |
| --- | --- | --- | --- |
| File sidebar (left) | Open | Collapsed | Hidden |
| Document panel (right) | Open | Collapsed | Outline only |
| Properties form | WYSIWYG only | WYSIWYG only | WYSIWYG only |
| Ask AI composer | Desktop, non-embedded | Hidden | Hidden |

Toggle sidebars with `Cmd+Option+S` (file sidebar) and `Cmd+Option+B` (document panel). On Windows and Linux, use `Ctrl` in place of `Cmd`. Pin state persists per layout context in browser storage.

## WYSIWYG and source mode

The editor defaults to WYSIWYG (`wysiwyg`). The toolbar mode toggle switches to source mode (`source`), which lazy-loads CodeMirror on first visit per document. Both views edit the same CRDT-backed `source` Y.Text, so human edits, agent MCP writes, and concurrent collaborators stay synchronized.

<ParamField body="editorMode" type="'wysiwyg' | 'source'">
Persisted in `localStorage` under `ok-editor-mode-v1`. Source mode is disabled when the collab provider is disconnected (`sourceDisabled`).
</ParamField>

| Mode | Rendering | Properties pane | Find/replace |
| --- | --- | --- | --- |
| WYSIWYG | TipTap markdown | Form above editor body | WYSIWYG find bar |
| Source | CodeMirror raw markdown | Hidden (edit YAML in `---` fence) | CodeMirror find bar |

Type `/` in WYSIWYG to open the slash insert menu for headings, lists, code blocks, callouts, and registered MDX components.

<Note>
Source-mode copy always places styled code blocks on the clipboard `text/html` payload — never rendered HTML. Cross-app paste into Gmail or Slack shows literal markdown bytes, consistent with VS Code and Obsidian source mode.
</Note>

## Ephemeral single-file sessions

Run `ok <file>` to open one markdown file without initializing a project:

<CodeGroup>
```bash title="Loose file"
ok notes.md
```

```bash title="File inside a project"
ok ./specs/foo/SPEC.md
```

```bash title="Explicit open alias"
ok open ./start.md
```
</CodeGroup>

`decideSingleFileTarget` in the CLI root argv scanner routes operands that look like files (`.md`/`.mdx` extension or `existsSync`) before subcommand parsing. `prepareSingleFileOpen` chooses between two modes:

| Mode | When | Behavior |
| --- | --- | --- |
| `project` | File sits under an enclosing `.ok/` project root | Delegates to `ok open <docName>` — full project UI, focused on the doc |
| `ephemeral` | No enclosing project | Throwaway session scoped to one file |

Ephemeral sessions write **nothing** into the file's directory. `createEphemeralProjectDir` creates a temp `ok-ephemeral-*` directory with `.ok/config.yml` pointing `content.dir` at the file's parent folder. The CLI boots a local server with `singleFile: true`, opens the desktop app via `openknowledge://open?file=…` when installed, or starts a browser session (press `Ctrl-C` to tear down).

```text
ok notes.md
    │
    ├─ inside .ok/ project? ──yes──► ok open (project mode)
    │
    └─ no ──► ephemeral temp .ok/
              ├─ desktop? ──yes──► openknowledge:// deep link
              └─ no ──► boot server + browser (Ctrl-C teardown)
```

In single-file mode the UI drops the file sidebar, project switcher, settings shortcut, Ask AI composer, and document-panel tabs other than Outline. `/api/config` returns `singleFile: true`, which `useSingleFileMode` reads to gate layout.

<Warning>
Ephemeral sessions have no MCP wiring, no git project scaffold, and no agent handoff surfaces. Edits save directly back to the original file on disk; opening without editing leaves the file byte-identical.
</Warning>

## Properties pane

Document properties live in YAML frontmatter — the fenced `---` block at the top of each `.md`/`.mdx` file. In WYSIWYG mode, `PropertyPanel` renders above the editor body as a collapsible form bound to the same CRDT frontmatter region via `bindFrontmatterDoc`.

Supported property types:

| Type | Widget | Notes |
| --- | --- | --- |
| `text` | Text input | Default for new properties |
| `number` | Numeric input | |
| `boolean` | Checkbox | |
| `date` | Date picker | ISO `YYYY-MM-DD` |
| `list` | Sortable list rows | Drag-reorder via `@dnd-kit` |
| `object` | Nested key/value editor | |

Add a property from the toolbar **Add property** button (WYSIWYG only) or edit the raw frontmatter block in source mode. Changes propagate live in both directions. The key `frontmatter` is reserved.

<Tip>
When frontmatter YAML has a parse error, the panel shows a read-only warning. Switch to source mode to fix the fence, then return to WYSIWYG.
</Tip>

Folder-level default properties and templates are configured separately — see [Folders and templates](/folders-and-templates).

## Document panel

The right **document panel** (`DocPanel`) hosts informational tabs for the active document:

| Tab | Purpose |
| --- | --- |
| Outline | Heading tree; click navigates to heading (source mode places cursor on the heading line, skipping frontmatter) |
| Links | Forward links and backlinks for the open doc |
| Graph | Project link graph centered on the active doc |
| Timeline | Edit history with inline diffs and restore |

Properties are **not** a document-panel tab — they render in the editor column above the body.

### Timeline panel

Open Timeline from the clock icon in the document panel tab strip. `TimelineContent` polls `GET /api/history?docName=<name>&limit=100` every 10 seconds (backoff to 60s on errors, paused when the tab is hidden).

Each entry shows contributor attribution, timestamp, change summary, and entry type:

- Agent and human edits
- Upstream sync (git)
- File-system changes outside Open Knowledge

Click an entry to expand an inline diff against the current document. Toggle unified or split layout from the panel header. Identical versions show `no changes`. Frontmatter-only differences are stripped before diffing.

Restore uses `POST /api/rollback` with `{ docName, commitSha }`. Restore is append-only: it writes a new version with the old content rather than deleting history. A confirmation dialog warns that the current content is already preserved in the timeline.

## Open with AI handoffs

**Open with AI** dispatches the project, a folder, or a file to installed agent apps. Handoffs are provider-neutral: the editor composes scope-specific prompts and posts them to `POST /api/handoff`, which launches the target via OS protocol handlers (Claude, Codex) or the Cursor CLI binary.

| Scope | Menu trigger | Prompt shape |
| --- | --- | --- |
| Project | Sidebar empty-space menu | `Let's work on this project using Open Knowledge.` |
| Folder | Folder row menu | ``Let's work on the `<folder>` folder using Open Knowledge.`` |
| File | File row menu | ``Let's work on `<path>` using Open Knowledge.`` |

When `appearance.preview.autoOpen` is `true` (default), prompts append `Open the OK editor in web view.` Set `appearance.preview.autoOpen` to `false` in user config to omit that trailer so agents do not auto-navigate your preview.

<ParamField body="appearance.preview.autoOpen" type="boolean" default="true">
Resolved fresh on each handoff from merged config. Toggle in **Settings → Preferences → Open preview when agent edits**.
</ParamField>

### Handoff targets

| Section | Targets | Launch mechanism |
| --- | --- | --- |
| Desktop | Claude (Cowork + Code), Codex, Cursor | `POST /api/handoff` → `claude://`, `codex://`, or `cursor://` URL |
| Terminal (desktop only) | Claude CLI, Codex CLI, Cursor CLI | Docked terminal with scoped prompt |

Install detection probes macOS app bundles, Windows registry keys, or Linux `xdg-mime` handlers via `GET /api/installed-agents`. On `localhost`, results are accurate; on remote hosts the submenu lists all supported agents and relies on the browser's protocol-dispatch dialog.

The Claude **claude.ai** (Cowork) row appears on file-scope dispatches only. Folder and project dispatches hide it because the cloud agent has no project-root parameter.

<Note>
Inside an AI editor's embedded webview (Cursor, Codex, Claude Desktop), Open with AI menus and the Ask AI composer are hidden to prevent handoff loops. MCP tools and the document itself remain fully available.
</Note>

:::endpoint POST /api/handoff
Dispatches a composed agent URL from the editor UI. Body: `{ target, url, workspacePath? }`. `target` is one of `claude-cowork`, `claude-code`, `codex`, `cursor`. `url` max 4096 chars. Cursor recipes require `workspacePath` within the project content directory. Returns `200` on successful spawn, `404`/`422` when the target is not installed.
:::

## Ask AI composer

The bottom **Ask AI** composer (desktop, non-embedded, project mode) accepts free-text instructions scoped to the active document. `Shift+Enter` inserts a newline; `Enter` dispatches.

- Default agent: first installed in Claude → Codex → Cursor order
- Agent picker overrides per send; choice persists on the machine
- Active doc is threaded as an `@`-mention via `composeAskPrompt` / `assembleHandoffPrompt`
- Text selection in WYSIWYG shows a Sparkles **Ask AI** button that pins the passage as a removable context pill

Selection handoffs embed the passage inline when under the 4096-char URL budget; larger selections fall back to locus mode (anchor + directive to read the full passage via MCP).

## Rich MDX embeds and assets

WYSIWYG editing supports standard markdown plus MDX-style JSX components registered in `@inkeep/open-knowledge-core` and rendered in `packages/app`.

### Slash-insert components

| Component | Category | Purpose |
| --- | --- | --- |
| `Callout` | Content | Tips, warnings, notes (16 `type` variants, optional collapsible) |
| `Accordion` | Content | Collapsible section |
| `Tabs` / `Tab` | Content | Horizontal pill panels |
| `img` | Media | Image with alt text |
| `video` | Media | Native player; YouTube, Vimeo, Loom embeds |
| `audio` | Media | Native audio player |
| `Pdf` | Media | Multi-page inline PDF viewer |
| `Embed` | Media | External iframe (docs, Figma, CodeSandbox) |
| `MermaidFence` | Content | Mermaid diagram from fenced source |
| `Math` | Content | KaTeX block equation |
| `Mirror` / `MirrorSource` | Content | Live cross-doc content mirrors |
| `File` | Media | Downloadable attachment row (drag-drop or picker) |
| `Tag` | Inline | `#tagname` cross-doc hashtag |

### Wiki-style asset embeds

Beyond JSX components, markdown supports wiki embeds and standard image syntax:

| Syntax | Semantics |
| --- | --- |
| `![alt](./path.png)` | Markdown image (relative path) |
| `![[diagram.png]]` | Wiki embed (opaque assets render as click-dispatch File rows) |
| `![[file.pdf#page=3\|Page 3]]` | Wiki embed with anchor and alias |
| `<Pdf src="./report.pdf" />` | Opt-in inline PDF viewer |

Public `https://` URLs pass through unchanged on cross-app copy. Private, loopback, and link-local URLs degrade to styled code blocks in HTML clipboard payloads so recipients see literal source instead of broken images.

Drop files into the editor to upload and insert; image uploads can emit wiki-embed or markdown-link shape depending on file type and config.

## Real-time collaboration

All editor state — body text, frontmatter, sidebar toggles — is CRDT-backed via Hocuspocus/Yjs. Multiple humans and agents can edit concurrently. Per-user presence appears in the editor header. For agent burst review and selective undo, switch the document panel to agent activity mode.

<Steps>
<Step title="Open a project doc">

Run `ok start --open` or `ok open <doc-path>` from the project root. Select a file from the sidebar.

</Step>

<Step title="Edit in WYSIWYG or source">

Use the toolbar toggle to switch modes. Add structure with `/` slash commands or edit raw markdown in source mode.

</Step>

<Step title="Set properties">

Expand the Properties section above the editor body. Add `status`, `owner`, `tags`, or custom keys. Verify the `---` fence updates in source mode.

</Step>

<Step title="Hand off to an agent">

Right-click a file in the sidebar → **Open with AI**, or type an instruction in the Ask AI composer. Confirm the agent receives the scoped prompt and can reach Open Knowledge MCP tools.

</Step>

<Step title="Review history">

Open the Timeline tab. Expand an entry to diff against the current version. Restore if needed via the undo icon (append-only rollback).

</Step>
</Steps>

## Related pages

<CardGroup>
<Card title="Core concepts" href="/core-concepts">
Three-layer model, filesystem-as-database, link graph, and git-backed attribution.
</Card>
<Card title="Collaboration server" href="/collaboration-server">
Hocuspocus/Yjs CRDT lifecycle and server-routed writes.
</Card>
<Card title="Wire agent editors" href="/wire-agent-editors">
MCP registration, bundled skills, and `exec` verification.
</Card>
<Card title="Folders and templates" href="/folders-and-templates">
Folder frontmatter and template resolution for new docs.
</Card>
<Card title="CLI reference" href="/cli-reference">
`ok <file>` dispatch, `ok open`, and server management commands.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
`write`, `edit`, `history`, `checkpoint`, and `restore_version` for agent-driven edits.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`appearance.preview.autoOpen`, `appearance.sidebar.*`, and config precedence.
</Card>
</CardGroup>

---

## 10. GitHub sync and conflicts

> Clone with `ok clone`, enable auto-sync, push/pull via CLI, and resolve merge conflicts through MCP `conflicts` and `resolve_conflict` tools.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/10-github-sync-and-conflicts.md
- Generated: 2026-06-25T22:45:56.538Z

### Source Files

- `docs/content/features/github-sync.mdx`
- `packages/cli/src/commands/clone.ts`
- `packages/cli/src/commands/sync.ts`
- `packages/cli/src/commands/push.ts`
- `packages/cli/src/commands/pull.ts`
- `packages/server/src/mcp/tools/conflicts.ts`
- `packages/server/src/mcp/tools/resolve-conflict.ts`

---
title: "GitHub sync and conflicts"
description: "Clone with `ok clone`, enable auto-sync, push/pull via CLI, and resolve merge conflicts through MCP `conflicts` and `resolve_conflict` tools."
---

Open Knowledge keeps project markdown in a git working tree and, when a GitHub `origin` remote is present, runs a background sync engine inside the Hocuspocus collaboration server. The engine fetches remote commits, commits local edits, and pushes them back on a schedule when auto-sync is enabled. The `ok` CLI exposes `clone`, `sync`, `push`, and `pull`; when a project server is running, those commands route through `POST /api/sync/trigger` instead of calling `git` directly. Merge conflicts are tracked in `.ok/local/conflicts.json`, surfaced in the editor, and resolvable through the unified diff UI or the MCP `conflicts` and `resolve_conflict` tools.

<Warning>
Only enable auto-sync on repositories where you accept Open Knowledge writing commits to remote history. Automated sync can clutter or reshape Git history on active branches.
</Warning>

## Clone with `ok clone`

`ok clone` clones a repository, scaffolds Open Knowledge if needed, excludes `.ok/` from git tracking, and opens the editor.

```bash
ok clone owner/repo
ok clone https://github.com/owner/repo.git
ok clone owner/repo ./my-folder -b feature-branch
```

| Flag / argument | Behavior |
| --- | --- |
| `<url>` | Full HTTPS/SSH URL or `owner/repo` shorthand (shorthand resolves to `https://github.com/owner/repo`) |
| `[dir]` | Target directory; defaults to `./<repo-name>`. Must not exist or must be empty |
| `-b, --branch <branch>` | Check out a branch; if upstream branch is missing, falls back to the default branch |
| `--json` | Emit JSONL progress events (`progress`, `complete`, `error`) instead of stderr UI |

**Authentication.** Public `https://github.com` repositories clone without credentials. Private repos require a stored token (`ok auth login`) or credentials from the `gh` CLI. On auth failure the CLI prints actionable recovery steps (`ok auth login` or `ok auth pat` with `repo` scope).

**Post-clone steps.** After a successful clone, `ok clone` runs `ok init` (without MCP registration), appends `.ok/` to the repo's git exclude file when possible, and—in non-JSON mode—`chdir`s into the clone and runs `ok start`.

<Note>
JSON mode (`--json`) prints machine-readable events only; it does not auto-start the server or open the editor.
</Note>

## Enable auto-sync

Auto-sync is controlled by two config keys with different scopes:

| Key | Scope | File | Values |
| --- | --- | --- | --- |
| `autoSync.enabled` | project-local | `.ok/local/config.yml` | `true`, `false`, or `null` (not chosen) |
| `autoSync.default` | project | `.ok/config.yml` | `true`, `false`, or `null` (ask on first open) |

Precedence: a per-machine `autoSync.enabled` choice overrides the committed `autoSync.default`. When `autoSync.enabled` is `null` and no local choice exists, the engine reads `autoSync.default` from project config; `null` triggers the editor onboarding prompt on first remote-detected open.

While sync is active the engine:

- Fetches and pulls remote commits on an interval
- Commits local content edits with the configured Git identity
- Pushes commits so collaborators see changes

Toggle sync from the editor sync indicator, **Settings → Sync**, or by writing `autoSync.enabled` into `.ok/local/config.yml`. Set a team default with `autoSync.default` in committed `.ok/config.yml` under **Sync → Shared default**.

<Callout type="warn">
Pulls can overwrite uncommitted local file changes. Commit or discard work-in-progress before enabling sync on a dirty tree.
</Callout>

## Manual sync via CLI

Three CLI commands share one implementation (`runSync`):

| Command | `op` sent to server | Direct-git fallback (no server) |
| --- | --- | --- |
| `ok sync` | `sync` | `git pull`, then `git push` |
| `ok pull` | `pull` | `git pull` only |
| `ok push` | `push` | `git push` only |

When `.ok/local/server.lock` records a running server port, the CLI posts to `http://127.0.0.1:<port>/api/sync/trigger` with body `{ "op": "sync" \| "pull" \| "push" }` and returns `202` on acceptance. If the trigger fails or no server is running, the CLI falls back to direct `simple-git` operations in the current project directory.

```bash
ok sync          # pull + push (server: push cycle then pull cycle)
ok pull --json   # JSONL: step, pull, complete events
ok push
```

Add `--json` on any command for JSONL telemetry (`triggered`, `step`, `pull`, `push`, `complete`, `error`).

<Info>
Server-triggered `sync` runs push before pull; the direct-git fallback runs pull before push. Both paths reconcile local and remote history but ordering differs.
</Info>

## Sync status and engine states

`GET /api/sync/status` (also polled by the editor sync indicator) returns:

| Field | Meaning |
| --- | --- |
| `state` | Current phase: `dormant`, `idle`, `fetching`, `pulling`, `pushing`, `conflict`, `offline`, `auth-error`, or `disabled` |
| `ahead` / `behind` | Commits relative to tracked remote branch |
| `conflictCount` | Unresolved merge conflicts |
| `syncEnabled` | Whether auto-sync is on for this machine |
| `identityUnresolved` | Git user.name/email not configured |
| `pausedReason` | Why sync paused (e.g. dirty tree, external changes pending) |
| `pushPermission` | Whether the signed-in account can push |

The indicator also exposes last sync time, manual sync trigger, and auth-error recovery entry points.

```mermaid
stateDiagram-v2
  [*] --> dormant: no remote
  dormant --> disabled: sync off
  disabled --> idle: sync enabled
  idle --> fetching: scheduled fetch
  fetching --> pulling: behind remote
  fetching --> pushing: ahead local
  pulling --> conflict: merge conflict
  pushing --> idle: success
  pulling --> idle: success
  conflict --> idle: all conflicts resolved
  idle --> auth-error: token invalid
  idle --> offline: network failure
  auth-error --> idle: reconnected
  offline --> idle: network restored
```

## When conflicts block work

During a merge conflict Open Knowledge:

- Pins a **Conflicts** section at the top of the file tree
- Badges conflicted tabs with `⚠`
- Swaps the editor to a unified diff view for human resolution

Conflicted documents receive `lifecycle.status === "conflict"`. All mutating MCP tools (`write`, `edit`, `delete`, `move`, `restore_version`, and agent undo) refuse with HTTP `409` / `DocInConflictError` until the conflict is resolved.

Tracked conflicts persist in `.ok/local/conflicts.json` (version 1 schema: `branch`, `conflicts[]` with `file`, `detectedAt`, optional stage SHAs).

## Resolve conflicts in the editor

<Steps>
<Step title="Open the conflict">
Click the badged tab or a row in the **Conflicts** section. The editor shows base, yours, and theirs in a unified diff.
</Step>
<Step title="Choose a side or merge">
Pick **Mine**, **Theirs**, or edit merged content in the diff view. Delete-vs-modify shapes offer deletion-specific choices.
</Step>
<Step title="Verify sync resumes">
When every conflict is cleared the engine creates a merge commit (`git commit --no-edit`). The sync indicator returns to `idle` and MCP writes succeed again.
</Step>
</Steps>

## Resolve conflicts via MCP

Both tools require the Hocuspocus server (`ok start`). They proxy to HTTP routes under `/api/sync/*`.

### `conflicts`

<ParamField body="kind" type='"list" | "content"' required>
`list` enumerates tracked conflicts. `content` fetches merge stages for one file.
</ParamField>

<ParamField body="file" type="string">
Required when `kind` is `"content"`. Relative path **with** `.md` or `.mdx` extension (e.g. `notes/sso.md`). Git stages key on the exact path.
</ParamField>

<ParamField body="cwd" type="string">
Optional project directory override for multi-root agents.
</ParamField>

**`kind: "list"`** returns `{ list: [{ file, detectedAt, ... }] }`. Empty list means no tracked conflicts.

**`kind: "content"`** returns `{ content: { file, base, ours, theirs, shape, lifecycleStatus } }`:

| `shape` | Meaning | Typical `resolve_conflict` strategy |
| --- | --- | --- |
| `both-modified` | Both sides edited content | `mine`, `theirs`, or `content` |
| `delete-modify` | You deleted; they edited (`ours` empty) | `delete` to keep deletion, or `theirs` to restore their version |
| `modify-delete` | You edited; they deleted (`theirs` empty) | `delete` to accept their deletion, or `mine` to keep your version |

The MCP tool requests `source=ytext` so `ours` reflects live Y.Text when the document is loaded server-side (what the human sees in the editor).

<RequestExample>
```json
conflicts({ "kind": "list" })
```
</RequestExample>

<RequestExample>
```json
conflicts({ "kind": "content", "file": "notes/sso.md" })
```
</RequestExample>

### `resolve_conflict`

<Warning>
Destructive: modifies the working tree and may create a git commit when all conflicts are cleared.
</Warning>

<ParamField body="file" type="string" required>
Relative path with extension (e.g. `notes/sso.md`).
</ParamField>

<ParamField body="strategy" type='"mine" | "theirs" | "content" | "delete"' required>
Resolution strategy. Inspect `shape` from `conflicts({ kind: "content" })` before choosing.
</ParamField>

<ParamField body="content" type="string">
Required and non-empty when `strategy` is `"content"`. Ignored otherwise.
</ParamField>

| Strategy | Git operations |
| --- | --- |
| `mine` | `git checkout --ours -- <file>` then `git add` |
| `theirs` | `git checkout --theirs -- <file>` then `git add` |
| `content` | Write exact bytes to disk, then `git add` |
| `delete` | `git rm <file>` |

When the last tracked conflict is resolved, `ConflictStore` runs `git commit --no-edit` to finish the merge. On commit failure the store may re-add unmerged files; re-call `conflicts({ kind: "list" })` to confirm post-state.

<RequestExample>
```json
resolve_conflict({ "file": "notes/sso.md", "strategy": "theirs" })
```
</RequestExample>

<RequestExample>
```json
resolve_conflict({
  "file": "notes/sso.md",
  "strategy": "content",
  "content": "# Merged title\n\nCombined body text."
})
```
</RequestExample>

```mermaid
sequenceDiagram
  participant Agent
  participant MCP as MCP server
  participant API as /api/sync/*
  participant Store as ConflictStore
  participant Git

  Agent->>MCP: conflicts({ kind: "list" })
  MCP->>API: GET /api/sync/conflicts
  API-->>Agent: list of conflicted files

  Agent->>MCP: conflicts({ kind: "content", file })
  MCP->>API: GET /api/sync/conflict-content?source=ytext
  API-->>Agent: base, ours, theirs, shape

  Agent->>MCP: resolve_conflict({ file, strategy })
  MCP->>API: POST /api/sync/resolve-conflict
  API->>Store: resolveConflict()
  Store->>Git: checkout/add/rm + commit
  API-->>Agent: { ok: true, file }
```

Proactively detect conflicts with `conflicts({ kind: "list" })` or `exec("cat …")`, which includes `lifecycle` on document reads.

## Common failure modes

| Symptom | Cause | Recovery |
| --- | --- | --- |
| Sync paused, `conflictCount > 0` | Unresolved merge conflict | Resolve in editor or via `resolve_conflict` |
| `pausedReason: dirty-tree` | Uncommitted local edits would be overwritten | Commit or discard changes, then trigger sync |
| `state: auth-error` | Expired or revoked GitHub token | `ok auth login` or reconnect from sync indicator |
| Push toggle disabled | Account lacks write access | Request collaborator access; sync preference stays preserved in memory |
| Sync auto-disabled | Protected branch rejected push | Push to an unprotected branch or use a PR workflow |
| `identityUnresolved: true` | Missing `user.name` / `user.email` | Set Git identity when prompted |
| `ok clone` auth failure | Missing or insufficient token scopes | `ok auth login` or `ok auth pat` with `repo` scope |
| Diverged history after force-push | Remote rewritten | Coordinate with team; consider timeline recovery |
| Detached HEAD | Checked out specific commit | Return to a branch (`main`, etc.) |

Network blips set `state` to `offline`; the engine retries automatically. Trigger a manual sync from the indicator if a pause persists.

## HTTP routes (server-backed)

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/sync/status` | Engine state, ahead/behind, `conflictCount` |
| `POST` | `/api/sync/trigger` | Body `{ op?: "sync" \| "pull" \| "push" }` — returns `202` |
| `GET` | `/api/sync/conflicts` | List tracked conflicts |
| `GET` | `/api/sync/conflict-content?file=&source=ytext` | Merge stages for one file |
| `POST` | `/api/sync/resolve-conflict` | Body `{ file, strategy, content? }` |

CLI `ok sync` / `ok pull` / `ok push` and MCP conflict tools all depend on these routes when the collaboration server is running.

## Related pages

<CardGroup>
<Card title="Auth reference" href="/auth-reference">
GitHub OAuth device flow, PAT storage, `gh` CLI credential reuse, and token scopes for clone and sync.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `ok` subcommands, global flags, and server management commands.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Full catalog of seventeen MCP tools, conflict guards, and preview envelopes.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`autoSync.enabled`, `autoSync.default`, and config precedence across scopes.
</Card>
<Card title="Team sharing" href="/team-sharing">
Publish projects to GitHub and construct share URLs with `ok share`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Diagnose server lock state, repair stale configs, and recover from sync failures.
</Card>
</CardGroup>

---

## 11. Team sharing

> Publish projects to GitHub, construct share URLs, and manage read-only doc sharing via `ok share` and `ok sharing` commands.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/11-team-sharing.md
- Generated: 2026-06-25T22:43:55.457Z

### Source Files

- `docs/content/features/share.mdx`
- `packages/cli/src/commands/share/publish.ts`
- `packages/cli/src/commands/sharing/share.ts`
- `packages/server/src/share/publish.ts`
- `packages/server/src/share/construct-url.ts`
- `packages/server/src/mcp/tools/share-link.ts`
- `packages/cli/src/commands/auth/login.ts`

---
title: "Team sharing"
description: "Publish projects to GitHub, construct share URLs, and manage read-only doc sharing via `ok share` and `ok sharing` commands."
---

Open Knowledge team sharing spans two surfaces: **share links** (`https://openknowledge.ai/d/…`) that pin a doc or folder to a GitHub branch for read-only handoff, and **config sharing mode** (`ok config-sharing`) that controls whether `.ok/` artifacts ride in git with content or stay machine-local via `.git/info/exclude`. Publishing a no-remote project to GitHub runs through `ok share publish` (or the editor Publish wizard backed by the same HTTP routes); constructing share URLs requires a running collaboration server and a pushed `github.com` `origin`.

## Share link model

A share link encodes a GitHub blob URL (doc) or tree URL (folder) for the project's current branch. The marketing URL wraps that substrate:

```
https://openknowledge.ai/d/{base64url-payload}
```

The payload is a version byte (`0x01`) followed by the UTF-8 GitHub URL. Recipients land on a splash page, then open the target in the desktop app or clone with `ok clone <owner/repo> -b <branch>`.

```mermaid
sequenceDiagram
  participant Editor as Editor / MCP agent
  participant Server as Collaboration server
  participant Git as Local git mirror
  participant Splash as openknowledge.ai splash

  Editor->>Server: POST /api/share/construct-url
  Server->>Git: read HEAD branch, origin, refs/remotes/origin/*
  Git-->>Server: owner/repo, branch, branch on origin
  Server-->>Editor: shareUrl + sharedUrl + branch
  Editor-->>Recipient: clipboard / message with shareUrl
  Recipient->>Splash: open shareUrl
  Splash-->>Recipient: clone or Open in OpenKnowledge
```

<Note>
Share links are **read-only against the working tree**. Constructing a URL does not commit, push, or `git fetch`. The pinned branch must already exist on `origin` (check `refs/remotes/origin/<branch>` or `packed-refs`).
</Note>

### Preconditions

| Requirement | Failure code |
| --- | --- |
| `origin` remote configured | `no-remote` |
| Named branch checked out (not detached HEAD) | `detached-head` |
| Current branch mirrored on `origin` | `branch-not-on-origin` |
| `origin` host is `github.com` (HTTPS, SSH, or `git://`) | `non-github-remote` |
| Path has no `..`, `.git`, backslashes, or control chars | `invalid-path` |

## Publish a project to GitHub

Use publishing when the project has no `origin` (the editor Share button opens the same wizard). The flow lists eligible owners, validates the repo name, creates the repository via GitHub's API, sets `origin`, and pushes the current HEAD.

<Steps>
<Step title="Authenticate">

Run `ok auth login` (device flow) or ensure `gh` CLI credentials are available. Publish and owner-list commands resolve tokens via `gh` first, then the OK token store.

```bash
ok auth login
```

</Step>
<Step title="List eligible owners">

```bash
ok share owners
ok share owners --json
```

Returns your user account plus orgs where you have `can_create_repository` or admin role.

</Step>
<Step title="Check repository name">

```bash
ok share name-check --owner my-org --name my-wiki
ok share name-check --owner my-org --name my-wiki --json
```

Repo names must be 1–100 chars, match `^[A-Za-z0-9._-]+$`, and not start with `.` or `-`. Owner logins must be 1–39 chars, match `^[A-Za-z0-9-]+$`, and not start or end with `-`.

</Step>
<Step title="Publish">

```bash
ok share publish \
  --owner my-org \
  --name my-wiki \
  --visibility private \
  --project-dir /path/to/project
```

On success, stdout reports the clone URL. With `--json`, the terminal line is `{ "type": "publish", "ownerLogin", "repoName", "cloneUrl", "defaultBranch" }`.

The publish flow also: scaffolds OK content if needed, runs `git init` when no `.git` exists, creates an initial commit when HEAD is missing, and on name conflict retries by fetching an existing repo you own before failing.

</Step>
<Step title="Verify">

```bash
git remote -v
git branch -vv
```

Confirm `origin` points at the new GitHub repo and the default branch was pushed.

</Step>
</Steps>

### Publish error codes

| Code | Typical cause |
| --- | --- |
| `auth-required` | No token; run `ok auth login` |
| `name-conflict` | Repo name taken and not accessible to your account |
| `saml-sso` | Org requires SAML SSO authorization |
| `push-failed` | Network, auth, or remote already exists with wrong URL |
| `init-failed` | Could not scaffold content, init git, or create initial commit |
| `network` | Other GitHub API or transport failure |

<Warning>
If the repo was created but the push failed (auth timeout, network blip), retry only the push — the GitHub-side repo is not recreated. The editor wizard exposes a **Retry push** affordance for this case.
</Warning>

## Construct a share URL

### Editor

Click **Share** in the toolbar to copy a `https://openknowledge.ai/d/…` URL for the focused doc or folder. If there is no `origin`, the Publish wizard runs first; after a successful push, the share URL is copied automatically.

### MCP `share_link`

Requires a running Hocuspocus server (`ok start`). Agents build links but **do not publish** — when `no-remote` is returned, direct the user to the Share wizard or `ok share publish`.

<ParamField body="path" type="string" required>
Content-dir-relative target. Doc paths are extension-less (`.md`/`.mdx` stripped on disk probe). Folder paths name a directory. `""` is the content-root sentinel (folder-only).
</ParamField>

<ParamField body="kind" type="'doc' | 'folder'">
Omit to auto-probe (`.mdx` → `.md` → directory). **Required** when `path` is `""`.
</ParamField>

<ParamField body="cwd" type="string">
Optional project root override.
</ParamField>

<ResponseField name="shareUrl" type="string">
Marketing URL: `https://openknowledge.ai/d/{encoded}`.
</ResponseField>

<ResponseField name="sharedUrl" type="string">
Raw GitHub blob or tree URL pinned to `branch`.
</ResponseField>

<ResponseField name="branch" type="string">
Branch encoded in the link (current HEAD).
</ResponseField>

<ResponseField name="resolvedKind" type="'doc' | 'folder'">
Kind the target resolved to after disk probe.
</ResponseField>

<RequestExample>

```json
{ "path": "guides/onboarding", "kind": "doc" }
```

</RequestExample>

<ResponseExample>

```json
{
  "ok": true,
  "shareUrl": "https://openknowledge.ai/d/AbCdEf...",
  "sharedUrl": "https://github.com/my-org/my-wiki/blob/main/guides/onboarding.md",
  "branch": "main",
  "resolvedKind": "doc"
}
```

</ResponseExample>

### HTTP API

:::endpoint POST /api/share/construct-url
Build a share URL from git context. Body is a discriminated union on `kind`.

**Doc request**

```json
{ "kind": "doc", "docPath": "guides/onboarding.md" }
```

**Folder request**

```json
{ "kind": "folder", "folderPath": "guides" }
```

Empty `folderPath` shares the content root (mapped to `tree/<branch>/<content.dir>` when `content.dir` is a subdirectory).

**Success (200)**

```json
{
  "ok": true,
  "shareUrl": "https://openknowledge.ai/d/...",
  "sharedUrl": "https://github.com/owner/repo/blob/main/guides/onboarding.md",
  "branch": "main"
}
```

**Failure (200, `ok: false`)**

```json
{ "ok": false, "error": "branch-not-on-origin", "branch": "feature-x" }
```

GET returns `405`. Malformed bodies return `400`.
:::

| Route | Method | Purpose |
| --- | --- | --- |
| `/api/share/publish/owners` | GET | List publish-eligible owners (spawns `ok share owners`) |
| `/api/share/publish/name-check` | GET | `?owner=&name=` availability check |
| `/api/share/publish` | POST | Create repo, set `origin`, initial push |

<ParamField body="owner" type="string" required>
GitHub user or org login.
</ParamField>

<ParamField body="name" type="string" required>
Repository name.
</ParamField>

<ParamField body="visibility" type="'public' | 'private'" required>
Repository visibility.
</ParamField>

<ParamField body="description" type="string">
Optional repository description.
</ParamField>

## Config sharing mode (`ok config-sharing`)

Separate from share links, **config sharing mode** decides whether Open Knowledge artifacts are committed with project content or hidden per-clone in `.git/info/exclude`.

| Mode | `.git/info/exclude` | Teammates see |
| --- | --- | --- |
| `shared` | OK paths **removed** from exclude | `.ok/`, editor MCP configs, project skills, `.okignore`, `.claude/launch.json` when committed |
| `local-only` | OK paths **added** to exclude | Only markdown content (OK config stays on your machine) |

Set the mode at init with `--shared` or `--local-only`, or toggle later:

<CodeGroup>
```bash title="Enable shared mode"
ok config-sharing share
```

```bash title="Switch to local-only"
ok config-sharing unshare
```

```bash title="Inspect current mode"
ok config-sharing status
```
</CodeGroup>

All subcommands accept `--project <dir>` and `--json`.

<Check>
After `ok config-sharing share`, commit the newly visible OK files so teammates receive the same MCP wiring and project skills.
</Check>

### Unshare refusal

`ok config-sharing unshare` refuses when OK artifacts are already tracked upstream. Untrack with `git rm --cached` on the listed paths, then re-run. Switching to local-only affects teammates on the next pull if those files were previously committed.

### Status output

`ok config-sharing status` reports:

- Current mode: `shared`, `local-only`, or `no-git`
- Paths currently in `.git/info/exclude`
- OK paths tracked upstream despite exclude intent

## Receiving a share link

Recipients open the splash page showing target name, `owner/repo`, and branch.

<Tabs>
<Tab title="Desktop (macOS)">

Click **Open in OpenKnowledge**. The app:

1. Matches `owner/repo` against recent projects — silent open on hit.
2. Otherwise prompts clone vs. locate existing folder.
3. On clone, runs `git clone -b <share-branch>` then navigates to the doc, folder, or project root.

If the repo is open on a different branch, a branch-switch dialog appears when the shared target exists on only one branch.

</Tab>
<Tab title="CLI (macOS and Linux)">

Copy from the splash:

```bash
npm install -g @inkeep/open-knowledge
ok clone owner/repo -b share-branch
```

Branch-pinned clone falls back to the default branch if the feature branch was deleted.

</Tab>
</Tabs>

<Info>
Read-only recipients (no push permission) skip the auto-sync prompt. Sync stays disabled until write access is granted.
</Info>

## Troubleshooting

| Symptom | Fix |
| --- | --- |
| `no-remote` on share | Run Publish wizard, `ok share publish`, or `gh repo create` + `git push -u origin <branch>` |
| `branch-not-on-origin` | `git push -u origin <branch>`; if already pushed, `git fetch origin` to refresh local remote refs |
| `detached-head` | `git checkout <branch>` |
| `non-github-remote` | Share links are GitHub-only in v1; migrate `origin` to `github.com` |
| `auth-required` on publish | `ok auth login` or configure `gh auth login` |
| `saml-sso` | Authorize the OAuth app for the org at the SSO URL GitHub returns |
| `name-conflict` | Pick a different `--name` or use an existing repo you own |
| MCP `share_link` server error | Start the server: `ok start` |
| `config-sharing unshare` refused | `git rm --cached` tracked OK paths, then re-run |

```bash
# Diagnose git + server health
ok diagnose health
ok config-sharing status --json
```

## Related pages

<CardGroup>
<Card title="Auth reference" href="/auth-reference">
GitHub OAuth device flow, PAT storage, and token scopes for publish and clone.
</Card>
<Card title="GitHub sync and conflicts" href="/github-sync-and-conflicts">
Auto-sync, push/pull, and merge conflict resolution after sharing.
</Card>
<Card title="Initialize a project" href="/initialize-project">
`ok init` sharing mode prompt and first-time scaffold.
</Card>
<Card title="HTTP API reference" href="/http-api-reference">
Full `/api/share/*` and editor route inventory.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
`share_link` tool contract and routed-write boundaries.
</Card>
</CardGroup>

---

## 12. Semantic search setup

> Enable embeddings-based MCP search ranking with `ok embeddings set-key`, project-local `search.semantic.*` config, and content-egress constraints.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/12-semantic-search-setup.md
- Generated: 2026-06-25T22:45:15.695Z

### Source Files

- `packages/cli/src/commands/embeddings/index.ts`
- `packages/server/src/mcp/tools/search.ts`
- `packages/core/src/config/schema.ts`
- `docs/content/reference/configuration.mdx`
- `packages/server/src/api-search-semantic.test.ts`
- `packages/cli/scripts/build-config-schema.mjs`

---
title: "Semantic search setup"
description: "Enable embeddings-based MCP search ranking with `ok embeddings set-key`, project-local `search.semantic.*` config, and content-egress constraints."
---

Semantic search adds an **embeddings-based ranking signal** to workspace search. When enabled, the MCP `search` tool fuses a vector similarity score with the existing lexical engine (title boost, body BM25, recency) so conceptually related pages surface even when they share no keywords — for example, a query about "auth retries" can rank a page titled "Credential Rotation" above unrelated hits.

The feature is **off by default** and **additive**. With it disabled, search is pure lexical ranking. When enabled but not ready (no API key, provider error, or index still warming), search **silently degrades to lexical** — it never blocks agent workflows or returns errors.

<Callout type="warn" title="Content egress">
When semantic search is **enabled** and an API key is set, the **search query** and **matching page content** are sent to the configured embeddings provider (OpenAI by default). Only content already in your searchable corpus is embedded — paths excluded by `.okignore` / `.gitignore` are never sent. Embedding is **lazy**: nothing leaves your machine until a search explicitly opts in with `semantic: true`. The API key is stored only in `~/.ok/secrets.yml` (mode `0600`) or the `OK_EMBEDDINGS_API_KEY` environment variable — never in `config.yml`, logs, or telemetry.
</Callout>

## Prerequisites

- A running OpenKnowledge project with the collaboration server (`ok start`).
- An OpenAI-compatible embeddings API key (OpenAI, Azure OpenAI, or a self-hosted endpoint that implements the same `/embeddings` contract).
- Project-local config scope — semantic settings live in `.ok/local/config.yml` (gitignored, per machine). Keys set in committed `.ok/config.yml` are **ignored** for egress safety.

## Enable semantic search

<Steps>

<Step title="Store the embeddings API key">

Run `ok embeddings set-key` from any directory. The command reads the key from a hidden prompt (TTY) or stdin (piped input) and writes it to `~/.ok/secrets.yml` under the `OPENAI_API_KEY` field.

```bash
ok embeddings set-key
```

<RequestExample>

```bash
echo "sk-your-key" | ok embeddings set-key
```

</RequestExample>

For CI or scripted runs, export `OK_EMBEDDINGS_API_KEY` instead. The file store takes precedence when both are present.

</Step>

<Step title="Enable semantic search for this project">

Turn on semantic search in **project-local** config. The CLI is the fastest path from a terminal:

```bash
cd /path/to/your/project
ok embeddings enable
```

This writes `search.semantic.enabled: true` to `.ok/local/config.yml`. You can also toggle **Settings → This project → Search** in OK Desktop; the UI shows an egress-confirmation prompt before enabling.

</Step>

<Step title="Start the server and verify capability">

```bash
ok start
ok embeddings status
```

<ResponseExample>

```text
Semantic search
  project:     /path/to/your/project

  This machine (all projects):
    API key:    set — ~/.ok/secrets.yml

  This project:
    enabled:    yes
    capability: AVAILABLE
    coverage:   42 / 42 pages embedded
    provider:   https://api.openai.com/v1
    model:      text-embedding-3-small
    dimensions: native (1536)
```

</ResponseExample>

`capability: AVAILABLE` means both `enabled` and a key are present. If the server is not running, coverage shows as unavailable until you start it and the index embeds.

Use `ok embeddings status --json` for machine-readable output, or `GET /api/semantic-status` while the server is up.

</Step>

<Step title="Confirm MCP search uses vectors">

From a connected agent, run a `search` call with a conceptually related query. The MCP `search` tool opts in to semantic ranking by default (`semantic` omitted or `true`).

<ResponseExample>

```json
{
  "query": "auth retries",
  "intent": "full_text",
  "resultCount": 3,
  "results": [
    {
      "kind": "page",
      "path": "guides/credential-rotation",
      "score": 8.42,
      "signals": {
        "lexical": 0,
        "fullText": 0,
        "recency": 0.12,
        "vector": 0.71
      }
    }
  ],
  "semantic": {
    "capable": true,
    "applied": true,
    "coverage": { "embedded": 42, "total": 42 }
  }
}
```

</ResponseExample>

A non-zero `signals.vector` on a hit confirms the embeddings signal contributed. The text response includes a one-line semantic note (for example, `Semantic: on — vector signal contributed (42/42 pages embedded).`).

</Step>

</Steps>

## Configuration keys

All `search.semantic.*` keys are **project-local** scope — they belong in `.ok/local/config.yml`, not committed `.ok/config.yml`. Wrong-scope keys are ignored; `ok config validate` reports them.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `search.semantic.enabled` | boolean | `false` | Master toggle. Must be `true` for any embeddings egress. |
| `search.semantic.baseUrl` | string | `https://api.openai.com/v1` | OpenAI-compatible embeddings API base URL. |
| `search.semantic.model` | string | `text-embedding-3-small` | Model id served at `baseUrl`. Changing it invalidates the on-disk cache. |
| `search.semantic.dimensions` | number | native (1536) | Optional output vector size. Smaller values shrink `.ok/local/embeddings/` at some quality cost. |
| `search.semantic.similarityFloor` | number | `0` (off) | Optional cosine-similarity cutoff. Docs below the floor are excluded from vector candidacy. Most setups should leave this unset. |

<ParamField body="search.semantic.enabled" type="boolean" required={false}>
Project-local master switch. When `true` and a key is present, semantic ranking is available to searches that pass `semantic: true`. Default `false`.
</ParamField>

<ParamField body="search.semantic.baseUrl" type="string" required={false}>
Base URL for an OpenAI-compatible `/embeddings` endpoint. Use for Azure OpenAI, self-hosted models, or other providers. The API key is **not** stored here.
</ParamField>

<ParamField body="search.semantic.model" type="string" required={false}>
Embeddings model identifier. Must match a model your provider serves. The vector cache is keyed by provider + model + dimensions; changing any of these triggers re-embedding.
</ParamField>

<ParamField body="search.semantic.dimensions" type="number" required={false}>
Optional reduced output dimensionality (for example `512` or `1024` with `text-embedding-3-small`). Omit to use the model's native size (1536 for the default model).
</ParamField>

<ParamField body="search.semantic.similarityFloor" type="number" required={false}>
Hard cosine-similarity floor in `[0, 1]`. Vector-only candidates below this value are dropped before ranking. Default behavior (`0`) is rank-based — the closest pages are returned regardless of absolute score.
</ParamField>

Example project-local config for a non-OpenAI provider:

```yaml
search:
  semantic:
    enabled: true
    baseUrl: https://your-azure-endpoint.openai.azure.com/openai/deployments/embeddings
    model: text-embedding-3-small
    dimensions: 1024
```

Published JSON Schema for project-local keys is generated at build time (`config.project-local.schema.json`).

## CLI reference

| Command | Purpose |
| --- | --- |
| `ok embeddings set-key` | Store the embeddings API key in `~/.ok/secrets.yml` (`0600`). |
| `ok embeddings clear-key` | Remove the stored key from all backends. |
| `ok embeddings enable` | Set `search.semantic.enabled: true` in project-local config. |
| `ok embeddings disable` | Set `search.semantic.enabled: false` in project-local config. |
| `ok embeddings status` | Report key presence, enabled state, capability, coverage, and provider settings. |

Global flags `--cwd <path>` and `--json` apply to `enable`, `disable`, and `status`.

## How ranking works

```mermaid
flowchart LR
  A[Search request] --> B{semantic: true?}
  B -->|no| C[Lexical only]
  B -->|yes| D{enabled + key?}
  D -->|no| C
  D -->|yes| E{intent full_text<br/>query len ≥ 3?}
  E -->|no| C
  E -->|yes| F[Embed query]
  F --> G[Fuse vector + lexical via RRF]
  G --> H[Ranked results + semantic block]
  C --> H
```

Semantic ranking applies only when **all** of the following are true:

1. `search.semantic.enabled` is `true` in project-local config.
2. An API key is available (`~/.ok/secrets.yml` or `OK_EMBEDDINGS_API_KEY`).
3. The request passes `semantic: true` (MCP `search` defaults to `true`; HTTP callers must set it explicitly).
4. `intent` is `full_text` (not `omnibar` or `autocomplete`).
5. The trimmed query length is at least **3** characters.

The cmd-K omnibar stays **purely lexical** on per-keystroke queries. A deliberate "search by meaning" submit from the omnibar sends `intent: full_text` with `semantic: true` and does fuse vectors.

### Indexing and cache

- The first semantic search triggers a **background embed** of markdown pages in the searchable corpus.
- Vectors are cached incrementally under `.ok/local/embeddings/`. Only changed documents re-embed.
- The cache key includes provider URL, model, dimensions, and chunking config — changing any invalidates and rebuilds.
- Embedding batches run in the background; subsequent searches report `coverage.embedded / coverage.total` so agents know when vectors are still filling in.

### What is never embedded

| Content | Lexical search | Embedded for semantic |
| --- | --- | --- |
| Pages under `.okignore` / `.gitignore` exclusions | Excluded from index | Never sent |
| Dot-path segments (`.cursor/`, `.claude/`, etc.) | Searchable with penalty | Never embedded |
| Non-markdown files | Name/path only | Not embedded |

Hidden dot-path files can still appear in lexical results but never carry `signals.vector`.

## MCP `search` behavior

The MCP `search` tool routes through `POST /api/search` on the collaboration server. Key parameters:

<ParamField body="semantic" type="boolean" required={false}>
Per-call override. Set `false` to force pure-lexical ranking even when semantic search is enabled. Omit (or `true`) to use semantic when available. MCP defaults to `true`.
</ParamField>

<ParamField body="intent" type="string" required={false}>
`full_text` (default) includes body content and is required for semantic fusion. `omnibar` is title/path/folder only and never applies vectors.
</ParamField>

<ResponseField name="semantic" type="object">
Present when semantic search is enabled for the workspace. Fields: `capable` (key loaded and service ready), `applied` (at least one result carried a vector signal this call), `coverage` (`embedded` / `total` page counts).
</ResponseField>

<ResponseField name="signals.vector" type="number">
Cosine similarity for this hit, present only when semantic ranking contributed for that document.
</ResponseField>

Pair `search` with `exec` `grep` for exhaustive literal-string coverage across all file types. Semantic search improves **ranking** for markdown pages; it does not replace grep for code or config files.

## HTTP endpoints

:::endpoint GET /api/semantic-status
Returns embedding index status for the running server. Used by `ok embeddings status` (live coverage) and the editor settings pane.

<ResponseExample>

```json
{
  "enabled": true,
  "keyPresent": true,
  "keySource": "file",
  "keyHint": "-key",
  "ready": true,
  "capable": true,
  "embedded": 42,
  "total": 42
}
```

</ResponseExample>
:::

:::endpoint POST /api/search
Workspace search. Pass `semantic: true` with `intent: "full_text"` to opt into vector fusion. The MCP tool sets `source: "mcp"` automatically.

<RequestExample>

```json
{
  "query": "session token refresh",
  "intent": "full_text",
  "semantic": true,
  "limit": 20
}
```

</RequestExample>
:::

The editor can set keys via `POST /api/local-op/embeddings/set-key` and `POST /api/local-op/embeddings/clear-key` without shell access.

## Troubleshooting

<AccordionGroup>

<Accordion title="Search stays lexical after enabling">
Check `ok embeddings status`. Common causes:
- No API key — run `ok embeddings set-key`.
- `enabled: no` — run `ok embeddings enable` or toggle Settings → Search.
- `enabled: true` in committed `.ok/config.yml` instead of `.ok/local/config.yml` — move it to project-local scope.
- Query shorter than 3 characters, or `intent: "omnibar"`.
- Per-call `semantic: false` override.
</Accordion>

<Accordion title="coverage.embedded is less than total">
Normal during warm-up. The first semantic search kicks off background embedding. Retry after a few seconds; the `semantic` block reports progress. Changed docs re-embed incrementally on subsequent searches.
</Accordion>

<Accordion title="capable: false with a key set">
The embedder failed to initialize (invalid key, network error, dimension mismatch). Search degrades to lexical. Check server logs. A dimension mismatch error suggests setting `search.semantic.dimensions` to match the provider's output.
</Accordion>

<Accordion title="Disable semantic search completely">
Run `ok embeddings disable` in the project folder, or toggle off in Settings. Optionally `ok embeddings clear-key` to remove the machine-wide key. Existing cache files under `.ok/local/embeddings/` remain on disk but are unused.
</Accordion>

</AccordionGroup>

## Related pages

<Cards>
  <Card title="Configuration reference" href="/configuration-reference">
    Full config schema, precedence order, and environment variables including `OK_EMBEDDINGS_API_KEY`.
  </Card>
  <Card title="MCP tools reference" href="/mcp-tools-reference">
    `search` parameters, response shape, and the two-tier `search` + `exec` grep model.
  </Card>
  <Card title="Project scaffold" href="/project-scaffold">
    `.ok/local/` layout, project-local scope, and `.okignore` exclusion semantics.
  </Card>
  <Card title="Collaboration server" href="/collaboration-server">
    Server lifecycle, `ok start`, and which MCP reads require Hocuspocus.
  </Card>
</Cards>

---

## 13. Folders and templates

> Folder properties in `.ok/frontmatter.yml`, leaf-to-root template resolution, and MCP `write({ document: { template } })` instantiation semantics.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/13-folders-and-templates.md
- Generated: 2026-06-25T22:45:04.353Z

### Source Files

- `docs/content/advanced/folders-and-templates.mdx`
- `packages/core/src/templates/template-format.ts`
- `packages/server/src/mcp/tools/write.ts`
- `packages/server/src/mcp/tools/verb-schemas.ts`
- `packages/core/src/frontmatter/tags.ts`
- `docs/content/features/ignore-patterns.mdx`

---
title: "Folders and templates"
description: "Folder properties in `.ok/frontmatter.yml`, leaf-to-root template resolution, and MCP `write({ document: { template } })` instantiation semantics."
---

Open Knowledge stores folder metadata in `<folder>/.ok/frontmatter.yml` and reusable document starters in `<folder>/.ok/templates/<name>.md`. Template resolution walks from the document's parent folder up to the content root; the nearest definition wins on filename collisions. Agents instantiate templates through MCP `write({ document: { path, template } })`, which strips the reserved `template:` identity block, applies `{{date}}` and `{{user}}` substitution, and writes via the collaboration server.

## Folder properties

Folder properties describe the folder itself. They do not inherit into child folders and do not flow into documents inside the folder.

| Aspect | Behavior |
| --- | --- |
| On-disk path | `<content-root>/<folder>/.ok/frontmatter.yml` (empty `folder` = content root) |
| Schema | Open-shape YAML — any top-level keys |
| Conventional keys | `title`, `description`, `tags` (surfaced in navigation, search, folder overview, and `exec` listings) |
| Inheritance | None — each folder reads only its own file |
| Patch semantics | Merge-patch: set a key to update it, set a key to `null` to delete it, omit keys to leave them unchanged |
| Empty result | Clearing all keys deletes `frontmatter.yml` and removes an empty `.ok/` directory |

A folder gets a `.ok/` directory when you add properties or its first template. Folders with no special configuration stay plain directories.

<ParamField body="path" type="string" required>
Content-root-relative folder path. Example: `meetings` or `research/sources`.
</ParamField>

<ParamField body="frontmatter" type="object">
Open-shape property map. Conventional keys: `title`, `description`, `tags`.
</ParamField>

<RequestExample>

```json
write({
  folder: {
    path: "meetings",
    frontmatter: {
      title: "Meetings",
      description: "Standups, planning sessions, and retrospectives",
      tags: ["meetings"]
    }
  }
})
```

</RequestExample>

<ResponseExample>

```text
Created folder meetings.
Set folder properties (meetings/.ok/frontmatter.yml).
```

</ResponseExample>

<Note>
Use `write({ folder })` to create a new folder. To change an existing folder's properties, use `edit({ folder: { path, frontmatter } })`. A `409` on create means the folder already exists.
</Note>

### Read folder metadata

The editor folder overview and MCP `exec` listings expose a folder's own `title`, `description`, and `tags` when present. `GET /api/folder-config?path=<folder>` returns the same metadata plus `templates_available`.

```bash
exec("ls meetings/")
```

Enriched directory output includes `title`, `description`, `tags`, and `templates_available` when the folder has properties or templates.

## Template files

A template is a markdown file at `<folder>/.ok/templates/<name>.md`. The filename (without `.md`) is the template id agents pass to `write({ document: { template } })`.

| Constraint | Rule |
| --- | --- |
| Name pattern | `^[A-Za-z0-9_-]+$` — letters, digits, underscore, hyphen only |
| File extension | `.md` only (non-`.md` files in `templates/` are ignored) |
| Identity metadata | `title` (required at write time), optional `description` and `tags` |
| Starter content | Markdown body plus optional starting document properties |

### On-disk format

Templates use a single frontmatter block. The reserved `template:` key holds picker identity (name, description, tags shown in menus). Every other top-level key becomes starting properties on documents created from the template.

```md title="meetings/.ok/templates/standup.md"
---
template:
  title: Daily standup
  description: Standup notes scaffold
type: meeting-note
status: draft
tags: [standup]
---

# Standup — {{date}}

Recorded by {{user}}

## Yesterday

## Today

## Blockers
```

At instantiation, `parseTemplateFile` peels the `template:` block and returns `starterContent` — the remaining frontmatter keys plus the markdown body. Legacy templates with two stacked `---` blocks still parse; saving rewrites them to the single-block form.

<Warning>
Do not declare a top-level `template:` key inside the `content` field when creating a template via MCP. That key is reserved and rejected at write time with `TEMPLATE_RESERVED_KEY`.
</Warning>

### Create or update a template

<ParamField body="path" type="string" required>
Template path as `<folder>/<name>`. Example: `meetings/standup`.
</ParamField>

<ParamField body="content" type="string" required>
Starter markdown. A leading `---…---` block sets starting document properties; the markdown below is the body.
</ParamField>

<ParamField body="frontmatter.title" type="string" required>
Human-readable label shown in the New File dialog and agent template menus.
</ParamField>

<ParamField body="frontmatter.description" type="string">
Optional one-line summary beside the title in pickers.
</ParamField>

<ParamField body="frontmatter.tags" type="string[]">
Optional tags stored under `template:` on disk.
</ParamField>

<RequestExample>

```json
write({
  template: {
    path: "meetings/standup",
    content: "# Standup — {{date}}\n\nRecorded by {{user}}\n",
    frontmatter: {
      title: "Daily standup",
      description: "Standup notes scaffold"
    }
  }
})
```

</RequestExample>

Project-wide templates live at `.ok/templates/<name>.md` (empty folder segment = content root). Folder-scoped templates live under that folder's `.ok/templates/`.

## Leaf-to-root template resolution

When creating a document, Open Knowledge resolves templates against the document's **parent folder**, not the document path itself. For `posts/2026/launch`, resolution starts at `posts/2026/`.

```mermaid
flowchart TB
  subgraph resolve["resolveTemplatesAvailable(parentFolder)"]
    L["1. Collect local templates from parent folder"]
    W["2. Walk ancestors leaf → root as inherited"]
    R["3. Collect project-root templates as inherited"]
    C["4. Closest folder wins on name collision"]
  end
  Doc["write document path: posts/2026/launch"] --> Parent["parentFolder = posts/2026"]
  Parent --> L --> W --> R --> C
  C --> Menu["templates_available menu"]
```

| Scope | Source | Visibility |
| --- | --- | --- |
| `local` | Templates in the parent folder's `.ok/templates/` | Available to documents created in that folder and its descendants |
| `inherited` | Templates from an ancestor folder or the content root | Surfaced to descendant folders, marked `inherited` |

Resolution rules:

- **Closest wins.** If `meetings/` and `meetings/prep-notes/` both define `prep-notes`, a document under `meetings/prep-notes/` uses the local copy.
- **Siblings are invisible.** Templates in `research/` do not appear when creating a document in `meetings/`.
- **Descendants do not bubble up.** A template defined only in `meetings/prep-notes/` is not listed for `meetings/` itself.
- **Project root is inherited everywhere.** Templates at `.ok/templates/` apply under every folder unless overridden locally.
- **First match sticks.** Once a name is collected from the nearest folder, ancestor definitions with the same filename are skipped.

Each resolved entry carries `name`, `title`, `description`, `path`, `source_folder`, and `scope`. Agents discover the menu through `exec("ls <folder>/")`, which includes `templates_available`.

## MCP `write({ document: { template } })` instantiation

Template-based document creation is a server-routed write. The collaboration server must be running (`ok start`).

<ParamField body="path" type="string" required>
Document path without extension. Slashes encode the target folder; missing parents are created. Example: `posts/launch`.
</ParamField>

<ParamField body="template" type="string" required>
Template name (filename without `.md`). Resolved leaf-to-root against the parent folder of `path`. Mutually exclusive with `content`.
</ParamField>

<ParamField body="frontmatter" type="object">
Optional document properties applied **after** instantiation via a separate frontmatter patch.
</ParamField>

<ParamField body="position" type="string">
Ignored when `template` is set — instantiation always uses `replace`.
</ParamField>

### Instantiation pipeline

1. **Resolve parent folder** — `parentFolderOf(docName)` extracts the directory portion of `path`.
2. **Match template** — `resolveTemplatesAvailable` builds the menu; the first entry whose `name` equals `template` wins.
3. **Read and peel** — `instantiateDoc` strips the `template:` identity block; remaining frontmatter and body become the draft document.
4. **Substitute** — `{{date}}` → UTC `YYYY-MM-DD`; `{{user}}` → connected agent display name (empty string when unknown).
5. **Write** — `POST /api/agent-write-md` with `position: "replace"`.
6. **Optional frontmatter patch** — If `frontmatter` is also supplied, `POST /api/frontmatter-patch` merges properties onto the new document.

<RequestExample>

```json
write({
  document: {
    path: "posts/launch",
    template: "blog-post"
  }
})
```

</RequestExample>

<ResponseExample>

```text
Written successfully (instantiated from template "blog-post").
```

</ResponseExample>

### Error cases

| Error | Cause |
| --- | --- |
| `either content or template must be provided` | Both fields omitted |
| `TEMPLATE_AND_CONTENT_BOTH_SET` | Both `template` and `content` supplied |
| `template "<name>" not found for folder "<parent>"` | Name not in the resolved menu; error lists available templates with scope |
| `failed to read template at <path>` | Template file missing or unreadable on disk |
| `document created from template but frontmatter failed` | Body written; subsequent frontmatter patch failed |
| Hocuspocus not running | Server required for document writes |

<Tip>
When choosing a template, agents should read `title` and `description` from `templates_available` — the `name` field is the id passed to `write`, but the title is the human-facing label.
</Tip>

### `template` versus `content`

| Input | Behavior |
| --- | --- |
| `content` | Literal markdown body; optional `frontmatter` composes inline YAML (forces `replace`) |
| `template` | Loads starter content from disk; identity metadata never copied to the document |
| Both | Hard error — use `template` then `edit` for post-create changes |

Batch writes via `write({ documents: [...] })` follow the same per-entry rules.

## Substitution tokens

Only two placeholders are allowed in template bodies:

| Token | Replacement |
| --- | --- |
| `{{date}}` | Today's date in UTC as `YYYY-MM-DD` |
| `{{user}}` | Display name of the person or agent creating the document |

Any other `{{...}}` token is rejected when saving a template (`TEMPLATE_UNKNOWN_VARIABLE`). Unknown tokens left in an on-disk file are left unchanged at instantiation (the allowlist replaces only recognized tokens).

Placeholders may appear in both the starter frontmatter and the markdown body. A template with `title: {{date}}` produces a document whose `title` property is the substituted date.

## Storage layout

:::files
<content-root>/
├── .ok/
│   ├── frontmatter.yml          # content-root folder properties
│   └── templates/
│       └── daily-note.md        # project-wide template
├── meetings/
│   ├── .ok/
│   │   ├── frontmatter.yml      # meetings/ folder properties
│   │   └── templates/
│   │       └── standup.md       # folder-local template
│   └── 2026-01-15-standup.md    # document (own properties only)
└── research/
    └── sources.md
:::

Paths are relative to the **content root** configured by `content.dir` in `.ok/config.yml` (default `.`, the project root). The editor folder overview, Settings → Project templates, and MCP `write` / `edit` with `folder` or `template` targets all write to these locations.

<Info>
Folder properties and templates are not affected by `.okignore`. Ignore patterns hide content from search and agent context but do not remove `.ok/` metadata directories from the filesystem.
</Info>

## Related pages

<CardGroup>
  <Card title="Project scaffold" href="/project-scaffold">
    `.ok/` directory layout, config scopes, and `content.dir` semantics.
  </Card>
  <Card title="Editor workflows" href="/editor-workflows">
    Folder overview UI, Properties panel, and the New File template picker.
  </Card>
  <Card title="MCP tools reference" href="/mcp-tools-reference">
    Full `write` and `edit` schemas for `document`, `folder`, and `template` targets.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `content.dir` and published config schema paths.
  </Card>
</CardGroup>

---

## 14. CLI reference

> All `ok` subcommands, global flags (`--cwd`, `--log-level`), server management (`start`, `stop`, `ps`, `status`, `clean`), and single-file dispatch behavior.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/14-cli-reference.md
- Generated: 2026-06-25T22:45:35.224Z

### Source Files

- `packages/cli/src/cli.ts`
- `packages/cli/src/commands/start.ts`
- `packages/cli/src/commands/stop.ts`
- `packages/cli/src/commands/status.ts`
- `packages/cli/src/commands/ps.ts`
- `packages/cli/src/commands/config.ts`
- `docs/content/reference/cli.mdx`

---
title: "CLI reference"
description: "All `ok` subcommands, global flags (`--cwd`, `--log-level`), server management (`start`, `stop`, `ps`, `status`, `clean`), and single-file dispatch behavior."
---

The `@inkeep/open-knowledge` npm package installs two equivalent binaries — `ok` and `open-knowledge` — both backed by `packages/cli/src/cli.ts`. Every invocation runs through a Commander program with global flags, optional project-root anchoring, and a pre-parse single-file dispatch path that intercepts bare markdown paths before subcommand routing.

## Global flags

These flags apply before any subcommand runs.

<ParamField body="--cwd" type="string">
  Sets the working directory via `process.chdir()` before config load and command execution.
</ParamField>

<ParamField body="--log-level" type="string" default="info">
  Sets `LOG_LEVEL` and `OK_CONSOLE_LEVEL`. Accepted values: `silent`, `error`, `warn`, `info`, `debug`, `trace`.
</ParamField>

<ParamField body="--no-color" type="boolean">
  Disables color output by setting `NO_COLOR=1`.
</ParamField>

<ParamField body="--color" type="boolean">
  Forces color output by setting `FORCE_COLOR=1`.
</ParamField>

<ParamField body="--version / -V" type="boolean">
  Prints the version notice and exits.
</ParamField>

<ParamField body="--help / -h" type="boolean">
  Prints help and exits. Also suppresses single-file dispatch (see below).
</ParamField>

### Project anchoring

For subcommands `start`, `stop`, `status`, `clean`, `ui`, `mcp`, and `preview`, the CLI walks up from the current directory to find an enclosing Open Knowledge project root (a directory containing `.ok/`). When the invocation cwd is inside a project but not at its root, the CLI `chdir`s to the project root and prints:

```
[ok] Using Open Knowledge project at <root>
```

The original invocation cwd is preserved for path-relative operations such as `ok stop <directory>`.

## Default dispatch (`ok` with no subcommand)

When argv contains no recognized subcommand and single-file dispatch does not match, the default action is:

1. **Desktop available** (macOS with `Open Knowledge.app` installed): launch the desktop app.
2. **Otherwise**: run `ok start` for the current directory.

```bash
ok                  # desktop app or `ok start`
ok --cwd ~/my-wiki  # same, but from ~/my-wiki
```

`ok start --mode app` forces desktop launch without booting a server. `ok start --mode browser` forces the browser/server path even when the desktop app is installed.

## Single-file dispatch

Before Commander parses argv, the CLI scans tokens for a markdown file target. This runs only when `--help`, `-h`, `--version`, or `-V` are absent.

### Detection rules

A token is treated as a file target when either:

- It has a `.md` or `.mdx` extension, or
- It resolves to an existing path on disk (relative to `--cwd` or the current directory).

The first matching operand wins. Known subcommand names are excluded unless the `open` escape hatch applies.

| Invocation | Behavior |
| --- | --- |
| `ok notes.md` | Open `notes.md` |
| `ok ./specs/foo/SPEC.md` | Open that file |
| `ok open ./start.md` | Open `start.md` when `start` would otherwise match the subcommand |
| `ok start` | Runs the `start` subcommand (not single-file dispatch) |

### Open modes

`prepareSingleFileOpen()` classifies the target into two modes:

**Project mode** — the file sits inside an existing Open Knowledge project's content directory. The CLI delegates to `ok open <docName>` with the resolved project root. Opens via desktop deep link (`openknowledge://open?project=…&doc=…`) when the desktop app is installed; otherwise opens the running UI in the browser.

**Ephemeral mode** — the file is outside any project. The CLI:

1. Creates a throwaway temp project under `ok-ephemeral-*` in the system temp directory.
2. Boots a single-file server with `--single-file` (git and MCP disabled).
3. Opens the browser at `http://<host>:<port>/#/<docName>`.
4. Holds the session until Ctrl-C, then tears down the server and temp directory.

Ephemeral sessions lack version history, MCP tools, and "Open with AI" integrations.

<Warning>
  Ephemeral mode requires UI assets bundled with the npm package. If assets are missing, reinstall `@inkeep/open-knowledge` or build the app in a monorepo checkout.
</Warning>

## Server management

Each Open Knowledge project runs its own collaboration server and optional UI sibling. Lock files under `.ok/` track process state.

```mermaid
stateDiagram-v2
  [*] --> missing: no lock file
  missing --> alive: ok start
  alive --> dead-pid: process exits
  dead-pid --> missing: ok clean
  alive --> missing: ok stop
  corrupt --> missing: ok clean
  alive --> foreign-host: lock from another host
```

### `ok start`

Start the Hocuspocus/Yjs collaboration server for the current project. Requires `.ok/` unless `--single-file` is used (internal/desktop path).

| Flag | Description |
| --- | --- |
| `-p, --port <port>` | Collaboration server port |
| `--ui-port <port>` | Pin the UI sibling port; if a server already runs, connect to it instead of exiting |
| `-H, --host <host>` | Server bind host (default from `HOST` env or package default) |
| `--open` | Open the editor URL in the default browser after start |
| `--mode <browser\|app>` | Force browser server boot or desktop app launch |
| `--serve-content-assets` | Serve content assets from this server |
| `--react-shell-dist-dir <path>` | Serve React shell from path (suppresses UI sibling spawn) |
| `--single-file <path>` | Ephemeral single-file mode (git + MCP off) |
| `--project-dir <dir>` | Throwaway project root for `--single-file` |

On start, the CLI automatically runs repair sweeps for MCP configs, `launch.json`, and bundled editor skills (unless ephemeral). A UI sibling (`ok ui`) is auto-spawned when no live UI lock exists. Idle shutdown releases the server lock after 30 minutes with no UI activity.

Lock collision errors distinguish desktop (`interactive`), MCP-spawned (`mcp-spawned`), and generic server locks. Git availability errors exit with code `78`.

### `ok stop`

Stop server and UI processes for a target.

| Target | Behavior |
| --- | --- |
| *(no argument)* | Stop processes for the current project; if none found, fall back to `ok ps` output |
| `<port>` | Stop the project whose server or UI lock matches the port |
| `<pid>` | Stop the project whose server or UI lock matches the PID |
| `<directory>` | Stop processes for the project at that path (spaces allowed) |
| `all` | Stop every running server across all discovered lock directories |

Sends `SIGTERM` to alive or foreign-host processes. Exit code `1` if any kill fails.

### `ok status`

Show live state of server and UI lock files for the current project.

```
server  alive  pid=12345 port=3921 started=2026-06-25T10:00:00.000Z
ui      alive  pid=12346 port=3922 started=2026-06-25T10:00:01.000Z
```

| State | Meaning | Remediation |
| --- | --- | --- |
| `not running` | No lock file | Run `ok start` |
| `alive` | Process responding on this host | — |
| `stale (dead pid=…)` | Lock exists but PID is dead | Run `ok clean` |
| `lock file corrupt` | Unparseable lock JSON | Run `ok clean` |
| `foreign host (…)` | Lock owned by another machine | Inspect remotely or wait |

Pass `--json` for structured output.

### `ok ps`

List all running Open Knowledge servers on the machine.

| Flag / argument | Description |
| --- | --- |
| `all` (positional) | Include stale `dead-pid` entries |
| `--all` | Same as positional `all` |
| `--json` | Emit structured JSON (always includes all statuses) |

Default view shows `running`, `desktop`, `foreign`, and `ui-orphan` entries. Columns include directory, API/UI ports, CPU/memory usage, status, PID, start time, and binary path.

### `ok clean`

Prune stale or corrupt lock files for the current project. Never touches live locks (`alive` or `foreign-host`). Removes locks in `dead-pid` or `corrupt` state only.

## Project lifecycle

### `ok init`

Scaffold `.ok/` in the current directory, initialize git, install skills, and register MCP entries for detected editors.

| Flag | Description |
| --- | --- |
| `--mcp` / `--no-mcp` | Register MCP server for editors (default: on) |
| `--dev-mcp` | Register a local dev MCP entry (`node` + `packages/cli/dist/cli.mjs`) |
| `--scope <user\|project\|both>` | MCP config write scope |
| `--shared` | Commit OK config alongside content (default for fresh repos) |
| `--local-only` | Keep OK config out of git via `.git/info/exclude` |

### `ok seed`

Scaffold a starter pack into the project.

| Flag | Description |
| --- | --- |
| `[path]` | Project directory (default: cwd) |
| `-p, --pack <id>` | Starter pack ID (default: Karpathy three-layer pack) |
| `-r, --root <path>` | Subfolder to scaffold into |
| `--list-packs` | List available packs and exit |
| `-y, --yes` | Skip confirmation |
| `--dry-run` | Print plan without writing |

### `ok clone <url> [dir]`

Clone a git repository and open it. Accepts `owner/repo` shorthand or full GitHub URLs.

| Flag | Description |
| --- | --- |
| `-b, --branch <branch>` | Branch to check out (falls back to default if missing) |
| `--json` | JSONL progress events |

Non-JSON mode clones, `chdir`s to the target, and runs `ok start`.

### `ok preview`

Read-only report of what content the file watcher will track, based on `.okignore` and `content.dir`.

## Editor and UI

### `ok ui`

Serve the React editor UI as a standalone process.

| Flag | Description |
| --- | --- |
| `-p, --port <port>` | UI port (`$PORT` env or kernel-allocated fallback if busy) |
| `-H, --host <host>` | Bind host (default: dual loopback `[::1]` + `127.0.0.1`) |

### `ok open <doc>`

Open a document in the desktop app or browser.

| Flag | Description |
| --- | --- |
| `--folder` | Treat `<doc>` as a folder route (browser only; requires running UI) |
| `--project <dir>` | Project root (default: cwd) |

`<doc>` must be a relative path with no `..`, leading `/`, or backslashes.

## Agent integration

### `ok mcp`

Start the MCP stdio server for the project knowledge base. AI editors spawn this command to expose knowledge-base tools.

| Flag | Description |
| --- | --- |
| `-p, --port <port>` | Proxy stdio to an existing HTTP MCP port (skips bundle proxy) |
| `--no-bundle-proxy` | Run the npm MCP server in-process instead of proxying to the macOS desktop bundle |

### `ok repair-skills`

Force a refresh of bundled `SKILL.md` files for installed AI editors (project-local and user-global). Runs automatically during `ok start`.

### `ok install-skill`

Build `openknowledge.skill` for Claude Desktop upload. Not needed for Claude Code — `ok init` covers that.

| Flag | Description |
| --- | --- |
| `--out <path>` | Output path (default: `~/Downloads/openknowledge.skill`) |
| `--no-open` | Build without OS file-association handoff |
| `--force` | Bypass install-state gate |

## Git sync

| Command | Description |
| --- | --- |
| `ok sync` | Commit, pull, and push to the remote |
| `ok pull` | Pull changes from the remote |
| `ok push` | Push commits to the remote |

All three accept `--json` for JSONL progress events.

## GitHub authentication

Parent command: `ok auth`.

| Subcommand | Description |
| --- | --- |
| `login` | GitHub OAuth device flow (`--host`, `--json`) |
| `status` | Show authentication status |
| `repos` | List accessible repositories |
| `signout` | Remove stored credentials |
| `pat` | Store a Personal Access Token |
| `git-credential` | Git credential helper (lookup protocol) |

## Team sharing

### `ok share`

GitHub publish flow helpers (requires authentication).

| Subcommand | Description |
| --- | --- |
| `owners` | List GitHub owners eligible to host a new repository |
| `name-check` | Check if `owner/name` is available |
| `publish` | Publish a no-remote project to GitHub (`--owner`, `--name`, `--visibility`, `--project-dir`, `--host`, `--json`) |

### `ok config-sharing`

Manage whether `.ok/` artifacts are committed or kept local-only per clone.

| Subcommand | Description |
| --- | --- |
| `share` | Switch to shared mode (remove OK paths from `.git/info/exclude`) |
| `unshare` | Switch to local-only mode (add OK paths to `.git/info/exclude`) |
| `status` | Print current sharing mode and excluded paths |

All accept `--project <dir>` and `--json`.

## Semantic search

Parent command: `ok embeddings`.

| Subcommand | Description |
| --- | --- |
| `set-key` | Store OpenAI embeddings API key in `~/.ok/secrets.yml` |
| `clear-key` | Remove stored key |
| `enable` | Turn semantic search on for this project (project-local config) |
| `disable` | Turn semantic search off |
| `status` | Show key presence, enabled state, coverage, provider (`--cwd`, `--json`) |

## Configuration

Parent command: `ok config`.

| Subcommand | Description |
| --- | --- |
| `validate` | Validate merged config (defaults → user → project) |
| `migrate` | Remove deprecated fields (`--scope project\|user\|both`, `--dry-run`) |

## Diagnostics

### `ok diagnose`

| Subcommand | Description |
| --- | --- |
| `health` | Environment health checks: `git`, `bun`, `config-yaml`, `content-dir`, `server-lock`, `shadow-repo`, `shadow-health`, `macos-codesig` (`--json`, `--verbose`, `--check <name>`, `--quiet`) |
| `process <pid>` | Capture diagnostic bundle for a running process (`--cpu-profile`, `--output`, `--no-inspector`) |
| `bundle` | Support bundle zip (`--pid`, `--out`, `--yes`, `--redact`) |

### `ok bug-report`

Generate a diagnostic bundle for bug reporting. `--reveal` / `--no-reveal` control Finder reveal (default: reveal).

## Environment variables

| Variable | Effect |
| --- | --- |
| `HOST` | Default server bind host |
| `PORT` | Default collaboration server port |
| `LOG_LEVEL` / `OK_CONSOLE_LEVEL` | Console log verbosity |
| `OK_RECLAIM_DISABLE` | Disable repair sweeps during `ok start` |
| `OK_BUNDLE_PROXY` / `OK_MCP_SPAWN_TIMEOUT_MS` | MCP bundle proxy and spawn timeout |
| `OK_MCP_AUTOSTART` | MCP shim auto-start behavior |

## Exit codes

| Code | Cause |
| --- | --- |
| `0` | Success |
| `1` | General failure (missing file, lock collision, stop/clean partial failure) |
| `2` | Invalid CLI arguments (e.g. `--mode=app` with `--open`) |
| `78` | Git not available or too old during `ok start` |

## Common workflows

<Steps>
  <Step title="Initialize and start">
    Run `ok init` in your content directory, then `ok start --open` to boot the server and open the editor.
  </Step>
  <Step title="Check server state">
    Run `ok status` for the current project or `ok ps` to see all running instances.
  </Step>
  <Step title="Stop or recover">
    Run `ok stop` to shut down the current project server. If locks are stale after a crash, run `ok clean` then `ok start` again.
  </Step>
  <Step title="Open a loose markdown file">
    Run `ok path/to/notes.md` for an ephemeral browser session, or `ok open <doc>` inside an initialized project.
  </Step>
</Steps>

<RequestExample>

```bash
# Start with browser open
ok start --open

# List all servers
ok ps

# Stop by port
ok stop 3921

# Open a file outside any project
ok ~/drafts/ideas.md
```

</RequestExample>

<ResponseExample>

```text
DIRECTORY           PORTS (API/UI)  CPU/MEM (API | UI)  STATUS    PID     STARTED  BINARY
~/my-wiki           3921 / 3922     0.3% / 1.2% | …    running   12345   2m ago   /usr/local/bin/ok

To stop a server: ok stop <port|pid|directory|all>
```

</ResponseExample>

## Related pages

<CardGroup>
  <Card title="Quickstart" href="/quickstart">
    Run `ok init`, `ok start --open`, and verify MCP tools respond from a connected agent.
  </Card>
  <Card title="Collaboration server" href="/collaboration-server">
    Hocuspocus/Yjs server lifecycle, per-project locks, and MCP routing boundaries.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    YAML config keys, precedence order, and environment-only variables.
  </Card>
  <Card title="MCP tools reference" href="/mcp-tools-reference">
    Seventeen MCP tools, input nesting, preview envelopes, and conflict guards.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Diagnose lock state, run health checks, and recover from crash leftovers with `ok clean`.
  </Card>
</CardGroup>

---

## 15. MCP tools reference

> Seventeen MCP tools (`exec`, `search`, `links`, `write`, `edit`, `delete`, `move`, `history`, `checkpoint`, `workflow`, and others), input nesting conventions, preview envelopes, and conflict guards.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/15-mcp-tools-reference.md
- Generated: 2026-06-25T22:46:57.624Z

### Source Files

- `packages/server/src/mcp/tools/index.ts`
- `packages/server/src/mcp/tools/verb-schemas.ts`
- `packages/server/src/mcp/tools/write.ts`
- `packages/server/src/mcp/tools/edit.ts`
- `packages/server/src/mcp/tools/workflow.ts`
- `packages/server/src/mcp/tools/palette.ts`
- `docs/content/reference/mcp.mdx`

---
title: "MCP tools reference"
description: "Seventeen MCP tools (`exec`, `search`, `links`, `write`, `edit`, `delete`, `move`, `history`, `checkpoint`, `workflow`, and others), input nesting conventions, preview envelopes, and conflict guards."
---

The Open Knowledge MCP server registers **17 tools** in `packages/server/src/mcp/tools/index.ts`. Agents call them over MCP after `ok init` wires the server into Claude Code, Cursor, or Codex. Two reads work without the Hocuspocus collaboration server (`exec`, `preview_url`); `config` and `palette` also operate on disk alone. Every other read and all writes route through the server started by `ok start`.

```mermaid
flowchart LR
  subgraph agent["Agent host"]
    MCP["MCP client"]
  end
  subgraph ok["Open Knowledge"]
    MCPServer["MCP server\npackages/server"]
    Hocus["Hocuspocus server\nok start"]
    Disk["Content dir + .ok/"]
    UILock["ui.lock"]
  end
  MCP --> MCPServer
  MCPServer -->|"exec, config, palette"| Disk
  MCPServer -->|"preview_url"| UILock
  MCPServer -->|"search, links, write, …"| Hocus
  Hocus --> Disk
```

<Note>
Every tool accepts an optional `cwd` — an absolute host path to the project root. Required when the MCP server is registered globally (`npx @inkeep/open-knowledge mcp`); optional when anchored to a single project. See [Wire agent editors](/wire-agent-editors).
</Note>

## Tool catalog

| Tool | Server | Role |
| --- | --- | --- |
| `exec` | No | Read-only shell over the content directory (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut`) with per-file enrichment |
| `search` | Yes | Ranked workspace search (title boost + BM25 + recency; optional semantic fusion) |
| `links` | Yes | Wiki-link graph (`backlinks`, `forward`, `dead`, `orphans`, `hubs`, `suggest`) |
| `history` | Yes | Document version timeline or folder activity timeline |
| `config` | No | Read effective merged config (full tree or dotted `key`) |
| `palette` | No | Authoring palette: markdown-native forms, `html preview` starters, theme tokens |
| `preview_url` | No* | Resolve a browser-openable preview URL from `ui.lock` |
| `share_link` | Yes | Build a GitHub-substrate share URL for a doc or folder |
| `write` | Yes | Create or overwrite `document`, `folder`, `template`, or `asset`; batch via `documents` |
| `edit` | Yes | Body find/replace or frontmatter merge-patch on `document`, `folder`, or `template` |
| `delete` | Yes | Delete `document` (one path or array), `folder`, `template`, or `asset` |
| `move` | Yes | Move/rename `document`, `folder`, `asset`, or `template`; rewrites affected links |
| `checkpoint` | Yes | Project-wide version snapshot; returns a 40-char `version` SHA |
| `restore_version` | Yes | Restore one document to a historical `version` |
| `conflicts` | Yes | List or inspect GitHub-sync merge conflicts |
| `resolve_conflict` | Yes | Resolve a conflict (`mine` / `theirs` / `content` / `delete`) and commit |
| `workflow` | No | Procedural guides (`ingest`, `research`, `consolidate`, `discover`, `wiki`) |

\* `preview_url` reads `ui.lock` directly but may auto-start the OK server when none is running.

<Warning>
When the Hocuspocus server is down, server-routed tools return: `Error: Hocuspocus server is not running. Start it with ok start, then retry.` Use `exec("grep …")` as the server-free search fallback.
</Warning>

## Input nesting conventions

Polymorphic write verbs (`write`, `edit`, `delete`) require **exactly one target key** per call. Nest the target's fields under that key — never flatten them to the top level.

<RequestExample>

```json
write({ "document": { "path": "meetings/standup", "content": "# Standup\n..." } })
```

</RequestExample>

<RequestExample>

```json
edit({ "document": { "path": "meetings/standup", "find": "TODO", "replace": "DONE" } })
```

</RequestExample>

Violations return a teaching error from `exactlyOneTargetError`:

- **Zero targets** — `Name exactly one of document, folder, … — the one thing you are addressing.`
- **Multiple targets** — `You named document and folder — name exactly ONE target.`

`move` is the exception: flat `from` / `to` paths (or nested `template: { from, to }`), with auto-detection of document vs folder vs asset. `conflicts`, `links`, and `workflow` dispatch on `kind` instead of a target key.

### Write targets

| Target | Tools | Notes |
| --- | --- | --- |
| `document` | `write`, `edit`, `delete` | Extension-less path (`meetings/standup`). `delete` accepts a string, array, or `{ path }`. |
| `folder` | `write`, `edit`, `delete` | `write` creates; `edit` patches `.ok/frontmatter.yml`. |
| `template` | `write`, `edit`, `delete` | Path = `<folder>/<name>` (letters, digits, `_`, `-` only). Stored at `<folder>/.ok/templates/<name>.md`. |
| `asset` | `write`, `delete` | Path includes extension (`images/diagram.png`). Upload via base64 `content` or local `source`. |
| `documents` | `write` only | Batch array; mutually exclusive with single targets. Each entry may carry its own `summary`. |

### Frontmatter merge-patch

`FrontmatterArg` is a JSON Merge Patch: set a key to update it, pass `null` to delete, omit keys to leave unchanged. A nested object **replaces** the subtree at that key.

<ParamField body="frontmatter" type="object">
Merge-patch map for document, folder, or template metadata. Use with `edit({ document: { path, frontmatter } })` for existing docs; `write` with `position: "replace"` for full rewrites.
</ParamField>

### Position and template instantiation

<ParamField body="position" type="enum" default="replace (new docs only)">
`replace` overwrites the whole body (only mode that persists frontmatter). `append` / `prepend` add content; frontmatter blocks in the payload are ignored on those modes.
</ParamField>

<ParamField body="template" type="string">
On `write({ document: { path, template } })`, resolves against the parent folder's template cascade (leaf-to-root walk). Mutually exclusive with `content`; forces `position: "replace"`. Only `{{date}}` and `{{user}}` substitution tokens are allowed.
</ParamField>

## Output shape and preview envelope

Responses mirror input nesting: `write({ folder })` returns `{ folder: { ok, path } }`; `edit({ document })` returns `{ document: { … } }`; a batch returns `{ documents: [ … ] }`.

Every tool that touches a previewable surface shares a **uniform preview envelope** at the top level of `structuredContent`:

<ResponseField name="previewUrl" type="string | null">
Route-only URL (`/#/<doc>`, no host:port). Identifies which doc to preview — not openable as-is.
</ResponseField>

<ResponseField name="previewUrlSource" type="string">
Provenance of `previewUrl` (currently `lock` from `ui.lock`).
</ResponseField>

<ResponseField name="warning" type="object">
Present on write tools when no browser is attached. Contains `action` (`attach-preview-once` or `start-ui`), `message`, `previewUrl`, and `autoOpen`.
</ResponseField>

Call `preview_url` to resolve the full browser-reachable URL (`http://localhost:<port>/#/<doc>`). `preview_url` also returns top-level `autoOpen`, reflecting `appearance.preview.autoOpen`.

<Info>
`autoOpen: true` (default) — agents may navigate the preview using host capabilities. `autoOpen: false` — the user manages their own preview window; surface the URL only on request.
</Info>

### Advisory warnings

`write` and `edit` may attach `structuredContent.document.warnings` — discriminated by `kind`:

| Kind | Meaning |
| --- | --- |
| `content-divergence` | Converged Y.Text did not byte-match composed content — re-read with `exec("cat <path>")` |
| `disk-edit-reconciled` | An out-of-band disk edit was folded in before your write landed |
| `mermaid-parse-error` | Write succeeded but a Mermaid fence will not render — fix and re-edit |

## Read tools

### `exec`

Runs one allowlisted command (or pipe) against the project content directory. Not a full shell — `&&`, `;`, redirections, and writes are rejected.

<ParamField body="command" type="string" required>
Bash-like read command. Examples: `cat articles/auth.md`, `grep -rn oauth articles/ | head -5`.
</ParamField>

Returns raw stdout plus `enrichedPaths` per referenced wiki file: frontmatter, backlink/forward-link counts, shadow-repo history, and route-only `previewUrl`. Soft cap: 500 lines / ~64 KB per response copy.

<Warning>
When the project has `.ok/`, prefer `exec` over native editor `Read`/`Grep`/`Glob` for `.md`/`.mdx` — native tools skip enrichment the MCP layer provides.
</Warning>

### `search`

<ParamField body="query" type="string" required>
Free-form query tokenized across title, path, and (with `full_text`) body.
</ParamField>

<ParamField body="intent" type="enum" default="full_text">
`omnibar` — title/path/folders only. `full_text` — includes markdown body.
</ParamField>

<ParamField body="semantic" type="boolean">
Set `false` to force pure-lexical ranking even when semantic search is enabled. Omit to use semantic when available (content egress to embeddings provider).
</ParamField>

<ParamField body="limit" type="number" default="20">
Max rows; maximum 100.
</ParamField>

Each result row carries `kind` (`page` | `folder` | `file`), `score`, `signals` breakdown, optional `snippet`, and route-only `previewUrl`. Cold start may return `ready: false` with empty results — retry after 2–3 seconds.

### `links`

<ParamField body="kind" type="string | string[]" required>
One of `backlinks`, `forward`, `dead`, `orphans`, `hubs`, `suggest` — or an array to fetch several views in one call. Results nest under each kind's key.
</ParamField>

<ParamField body="document" type="string">
Required for `backlinks`, `forward`, and `suggest`.
</ParamField>

Passing multiple kinds merges into a single payload; per-kind failures land in an `errors` map.

### `history`

Pass exactly one of `document` or `folder`.

<ParamField body="document" type="string">
Extension-less doc path. Each entry's `version` is a 40-char SHA for `restore_version`.
</ParamField>

<ParamField body="folder" type="string">
Folder timeline over `.ok/` artifacts (templates + frontmatter). Use `""` for project root.
</ParamField>

Entry kinds: `checkpoint`, `wip`, `upstream`. Optional filters: `branch`, `limit` (max 200), `offset`, `kind`, `author`, `excludeAuthor`.

### `config`

<ParamField body="key" type="string">
Dotted config key (e.g. `appearance.theme`). Omit for the full merged config (defaults → user → project).
</ParamField>

Returns `{ value, exists, key? }`. Removed keys (`server.*`, `mcp.*`, `github.*`) return `exists: false`.

### `palette`

Returns three sections: `components` (markdown-native authoring forms), `embedPatterns` (themed `html preview` starters), and `tokens` (CSS custom properties for preview iframes).

<ParamField body="components" type="string[]">
Optional canonical ids (max 32, case-sensitive) to expand full JSX prop schemas into `componentDetails`. Unmatched ids appear in `notFound`.
</ParamField>

### `preview_url`

<ParamField body="document" type="string">
Extension-less doc path. Mutually exclusive with `folder`.
</ParamField>

<ParamField body="folder" type="string">
Folder route (`…/#/<folder>/`). Mutually exclusive with `document`.
</ParamField>

<ParamField body="armPaneTarget" type="boolean">
When true with a target, arms a TTL-bounded (~30s) pane target under `.ok/local/` for Claude Code Desktop `preview_start`.
</ParamField>

<ResponseField name="url" type="string | null">
Full browser-reachable URL, or `null` when no UI is running.
</ResponseField>

<ResponseField name="baseUrl" type="string | null">
UI origin (e.g. `http://localhost:5173`).
</ResponseField>

<ResponseField name="running" type="boolean">
Whether a UI is bound for the project.
</ResponseField>

### `share_link`

Builds a read-only `https://openknowledge.ai/d/...` URL pinned to the current branch. Never publishes or pushes.

<ParamField body="path" type="string" required>
Content-dir-relative target. Extension-less for docs; directory path for folders. `""` is the content-root sentinel (requires `kind: "folder"`).
</ParamField>

<ParamField body="kind" type="enum">
`doc` or `folder`. Auto-probed from disk when omitted (`.mdx` → `.md` → directory).
</ParamField>

Preconditions: named branch, `github.com` origin, branch pushed to origin.

## Write tools

### `write`

Four native CRUD targets plus batch:

<CodeGroup>

```json title="Create a document"
{ "document": { "path": "meetings/standup", "content": "# Standup\nNotes here." } }
```

```json title="Instantiate from template"
{ "document": { "path": "fishing-log/2026-06-25", "template": "trip-log" } }
```

```json title="Batch write"
{ "documents": [
    { "path": "a", "content": "# A" },
    { "path": "b", "content": "# B", "summary": "Added section B" }
  ]}
```

</CodeGroup>

Existing docs require explicit `position`. Creating a doc with empty content errors. `extension` (`.md` | `.mdx`) applies only on create.

### `edit`

Body edit and frontmatter patch are **mutually exclusive** per call.

<ParamField body="find" type="string">
Exact body text to find. Requires `replace`. Frontmatter-intersecting finds are rejected.
</ParamField>

<ParamField body="occurrence" type="number" default="1">
Which match to edit (1 = first).
</ParamField>

Folders accept frontmatter only (no body). Templates use the same body/frontmatter split with path `<folder>/<name>`.

### `delete`

Irreversible. Call `links({ kind: "backlinks", document })` before deleting docs to see inbound references. Inbound links become redlinks.

### `move`

<ParamField body="from" type="string" required>
Current path. Doc: extension-less. Folder: relative path. Asset: path with extension.
</ParamField>

<ParamField body="to" type="string" required>
New path, same shape as `from`.
</ParamField>

Rewrites wiki-links and supported inline Markdown links across affected docs. Returns `previewUrls` map and `previousPreviewUrl` for deleted paths. Errors: 400 (invalid/excluded), 404 (missing), 409 (destination exists, with `colliding[]`).

### Edit summaries

`write`, `edit`, `move`, and `checkpoint` accept an optional `summary` (≤80 chars, transport cap 200). Summaries appear on the document timeline and persist to git history. Avoid secrets and PII.

## Version tools

### `checkpoint`

Saves a project-wide restore point via `POST /api/save-version`.

<ResponseField name="version" type="string">
40-character commit SHA. Same field name used by `history` entries and `restore_version`.
</ResponseField>

`previewUrl` is always `null` — checkpoints are not scoped to one doc.

### `restore_version`

<ParamField body="document" type="string" required>
Document to restore (extension-less path).
</ParamField>

<ParamField body="version" type="string" required>
40-char SHA from `history` or `checkpoint`.
</ParamField>

Append-only: creates a new version with old content. May return `warnings` with kind `content-divergence` when restored Y.Text does not byte-match target bytes.

## Conflict guards

GitHub-sync merge conflicts set `lifecycle.status = "conflict"` on affected documents. **All mutating operations refuse conflicted docs** with HTTP 409 (`urn:ok:error:doc-in-conflict`):

- `write`, `edit` (`/api/agent-write-md`, `/api/agent-patch`)
- `delete` (`/api/delete-path`)
- `move` (`/api/rename-path`)
- `restore_version`
- Agent undo

The error detail instructs: call `conflicts({ kind: "content" })` + `resolve_conflict` before retrying.

<Steps>
<Step title="Detect conflicts">

```json
conflicts({ "kind": "list" })
```

Returns `{ list: [{ file, detectedAt, … }] }` — empty when none.

</Step>
<Step title="Inspect merge stages">

```json
conflicts({ "kind": "content", "file": "notes/sso.md" })
```

Returns `{ content: { file, base, ours, theirs, shape, lifecycleStatus } }`. `file` must include the `.md`/`.mdx` extension. `shape` is `both-modified`, `delete-modify`, or `modify-delete`.

</Step>
<Step title="Resolve and commit">

```json
resolve_conflict({ "file": "notes/sso.md", "strategy": "content", "content": "…merged bytes…" })
```

Strategies: `mine` (`git checkout --ours`), `theirs` (`git checkout --theirs`), `content` (exact bytes), `delete` (`git rm`).

</Step>
</Steps>

<Warning>
`resolve_conflict` modifies the working tree and creates a git commit. Re-call `conflicts({ kind: "list" })` after a 500 to confirm post-state — the resolve API is best-effort and non-atomic.
</Warning>

## `workflow`

Returns numbered procedural plans (instructional text, not data). Dispatches on `kind`:

| Kind | Required param | Purpose |
| --- | --- | --- |
| `ingest` | `source` | Capture external source verbatim into the KB |
| `research` | `topic` | Gather sources and write provisional findings |
| `consolidate` | `topic` | Promote research to a canonical article |
| `discover` | — | Extract conventions from an existing repo |
| `wiki` | — | Generate a source-grounded codebase wiki into `wiki/` |

`previewUrl` is always `null` — workflow output is prose, not a previewable document.

## Structured output channel

All tools return MCP `content[0].text` plus optional `structuredContent`. `textPlusStructured` mirrors the visible body into `structuredContent.text` as a Claude Desktop client workaround. Programmatic consumers should prefer `content[0].text` for the human-readable channel and `structuredContent` for typed fields.

## Related pages

<CardGroup>
<Card title="Collaboration server" href="/collaboration-server">
Hocuspocus lifecycle, server-free vs server-routed tools, and protocol boundaries.
</Card>
<Card title="Wire agent editors" href="/wire-agent-editors">
MCP registration, bundled skills, and `exec` verification prompts.
</Card>
<Card title="GitHub sync and conflicts" href="/github-sync-and-conflicts">
Clone, auto-sync, and end-to-end conflict resolution workflows.
</Card>
<Card title="Semantic search setup" href="/semantic-search-setup">
Enable embeddings-based `search` ranking and content-egress constraints.
</Card>
<Card title="Folders and templates" href="/folders-and-templates">
Template cascade and `write({ document: { template } })` semantics.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
YAML config keys including `appearance.preview.autoOpen`.
</Card>
<Card title="HTTP API reference" href="/http-api-reference">
Server routes (`/api/agent-write-md`, `/api/search`, `/api/sync/*`) behind MCP tools.
</Card>
<Card title="Karpathy LLM wiki" href="/karpathy-llm-wiki">
Three-layer workflow using `workflow` kinds `ingest`, `research`, and `consolidate`.
</Card>
</CardGroup>

---

## 16. Configuration reference

> YAML config keys in `.ok/config.yml`, `~/.ok/global.yml`, and `.ok/local/config.yml`; precedence order; published JSON Schema paths; and environment-only variables.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/16-configuration-reference.md
- Generated: 2026-06-25T22:47:08.001Z

### Source Files

- `packages/core/src/config/schema.ts`
- `packages/core/src/config/merge-layered.ts`
- `packages/core/src/config/validate-patch-scopes.ts`
- `packages/cli/scripts/build-config-schema.mjs`
- `packages/cli/src/commands/config.ts`
- `docs/content/reference/configuration.mdx`
- `.env.example`

---
title: "Configuration reference"
description: "YAML config keys in `.ok/config.yml`, `~/.ok/global.yml`, and `.ok/local/config.yml`; precedence order; published JSON Schema paths; and environment-only variables."
---

Open Knowledge stores typed YAML configuration in three scope-specific files under `.ok/` and `~/.ok/`. At runtime the editor and collaboration server merge layers with `mergeLayered` (scope-aware leaf resolution), validate writes with `validatePatchScopes`, and publish JSON Schema artifacts from `ConfigSchema` in `@inkeep/open-knowledge-core`. All config keys are human-only (`agentSettable: false`); agents cannot patch config through MCP tools.

## Config files

| Scope | Path | Git | Purpose |
| --- | --- | --- | --- |
| **User** | `~/.ok/global.yml` | No | Personal preferences across every project on this machine |
| **Project** | `.ok/config.yml` | Yes | Team-shared project defaults committed to the repo |
| **Project-local** | `.ok/local/config.yml` | No (gitignored) | Per-machine, per-project settings that must not sync |

All three files are optional. Missing files fall back to built-in defaults from `ConfigSchema.parse({})`.

<Note>
Embeddings API keys are **not** config keys. Store them with `ok embeddings set-key` in `~/.ok/secrets.yml` (mode `0600`), or set `OK_EMBEDDINGS_API_KEY` for scripted runs.
</Note>

## Precedence

Effective values resolve in two stages: **layer merge** for YAML config, then **environment and CLI overrides** for settings that have no `config.yml` key.

```text
Built-in defaults
  → ~/.ok/global.yml          (user)
  → .ok/config.yml            (project)
  → .ok/local/config.yml      (project-local)
  → environment variables     (env-only settings)
  → CLI flags                 (highest)
```

Within the YAML layers, `mergeLayered(user, project, projectLocal)` applies **scope-aware short-circuiting** at each leaf field:

| Scope | Reads from | Fallback when absent |
| --- | --- | --- |
| `user` | `~/.ok/global.yml` only | No fallback to lower layers |
| `project` | `.ok/config.yml` | `~/.ok/global.yml` |
| `project-local` | `.ok/local/config.yml` | `.ok/config.yml`, then `~/.ok/global.yml` |

Arrays replace wholesale at the highest defined layer; they are not concatenated. Setting a leaf to `null` in project-local clears that value. Keys placed in the wrong-scope file are **not used** for the effective merged config, even if YAML parsing succeeds.

<Warning>
`ok config validate` merges **user + project** YAML and checks schema validity and removed keys. It does not load project-local config. The editor and running server use all three layers via `mergeLayered`.
</Warning>

### Auto-sync resolution

Git auto-sync uses a dedicated two-step lookup outside the generic merge table:

1. If `autoSync.enabled` in `.ok/local/config.yml` is `true` or `false`, that value wins.
2. Otherwise, if project config is valid, `autoSync.default` in `.ok/config.yml` applies (`true` enables, `false` disables, `null` leaves onboarding to prompt).
3. If project-local config is invalid, the server logs a warning and falls back to the project default; if project config is also invalid, auto-sync defaults to **disabled**.

## JSON Schema artifacts

Schemas are generated at build time by `packages/cli/scripts/build-config-schema.mjs` from `ConfigSchema` and the field registry (`CONFIG_SCHEMA_MAJOR` is `0`, path segment `v0`).

### Published paths

| Artifact | Path in `@inkeep/open-knowledge` package |
| --- | --- |
| Full merged schema | `dist/config-schema.json` |
| Project scope | `dist/schemas/v0/config.project.schema.json` |
| User scope | `dist/schemas/v0/config.user.schema.json` |
| Project-local scope | `dist/schemas/v0/config.project-local.schema.json` |

Back-compat aliases also ship at the `dist/` root: `config.project.schema.json`, `config.user.schema.json`, `config.project-local.schema.json`.

### IDE `$schema` URLs

New config files receive a `yaml-language-server: $schema=` magic comment pointing at unpkg:

```yaml
# yaml-language-server: $schema=https://unpkg.com/@inkeep/open-knowledge@latest/dist/schemas/v0/config.project.schema.json
```

Per-scope URLs:

| Scope | Schema filename |
| --- | --- |
| Project | `config.project.schema.json` |
| User | `config.user.schema.json` |
| Project-local | `config.project-local.schema.json` |

Scope-pruned schemas include only fields whose `scope` metadata matches that file. The full `config-schema.json` includes every key for documentation and validation tooling.

## Config keys

The **Scope** column is authoritative: writes to the wrong file return `SCOPE_VIOLATION` from the Settings pane, `writeConfigPatch`, or `bindConfigDoc.patch`.

### `content`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `content.dir` | string | `"."` | project | Content root relative to the project directory (the folder containing `.ok/`). Exclude paths with `.okignore`. |

### `appearance`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `appearance.theme` | `"light"` \| `"dark"` \| `"system"` | (unset) | user | Editor color theme. |
| `appearance.preview.autoOpen` | boolean | `true` | user | When `true`, the agent opens or refreshes the live preview after each MCP edit. Set `false` to manage preview yourself. |
| `appearance.sidebar.showHiddenFiles` | boolean | `false` | project-local | Show dot-prefixed entries (`.ok/`, `.okignore`) in the file tree. |

### `editor`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `editor.wordWrap` | boolean | `true` | user | Soft-wrap long lines in source (CodeMirror) mode. |

### `autoSync`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `autoSync.enabled` | `boolean` \| `null` | `null` | project-local | Per-machine git auto-sync toggle. `null` means unanswered; onboarding prompts on first remote-detected open. |
| `autoSync.default` | `boolean` \| `null` | `null` | project | Committed default for a machine's first `autoSync.enabled`: `true` on, `false` off, `null` ask. A per-machine `autoSync.enabled` choice overrides this. |

### `terminal`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `terminal.enabled` | `boolean` \| `null` | `null` | project-local | Opt-out for the in-app terminal (real OS shell at full user privilege). Terminal is on by default; set `false` to disable for this project on this machine. Never shared via git, clone, or sync. |

### `telemetry`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `telemetry.localSink.enabled` | boolean | `true` | project | Write local diagnostic spans and logs under `.ok/local/` for `ok diagnose bundle`. Set `false` for sensitive workspaces. |
| `telemetry.localSink.spans.maxBytes` | number | `52428800` (~50 MB) | project | Max spans file size before rotation. |
| `telemetry.localSink.logs.maxBytes` | number | `26214400` (~25 MB) | project | Max logs file size before rotation. |
| `telemetry.localSink.attributeDenylist` | `string[]` | 8 credential keys | project | Attribute keys redacted to `[REDACTED]` before local span/log writes. Built-in denylist includes `authorization`, `auth.token`, `auth.bearer`, `cookie`, `set-cookie`, `x-api-key`, `password`, `secret`. |

### `search.semantic`

| Key | Type | Default | Scope | Description |
| --- | --- | --- | --- | --- |
| `search.semantic.enabled` | boolean | `false` | project-local | Add embeddings-based ranking to MCP `search`, fused with lexical results. **Content egress** when enabled and a key is set. |
| `search.semantic.baseUrl` | string | `"https://api.openai.com/v1"` | project-local | OpenAI-compatible embeddings API base URL. Override for Azure, self-hosted, or other providers. |
| `search.semantic.model` | string | `"text-embedding-3-small"` | project-local | Embeddings model id served at `baseUrl`. Changing it re-embeds the corpus. |
| `search.semantic.dimensions` | number | (native) | project-local | Optional output vector size. Omit for native dimensions (1536 for `text-embedding-3-small`). |
| `search.semantic.similarityFloor` | number (0–1) | (unset) | project-local | Optional hard cosine-similarity cutoff. Off by default; retrieval is rank-based. Set only when you know your provider's cosine scale. |

<Info>
Folder metadata (`title`, `description`, templates) lives in per-folder `.ok/frontmatter.yml` and `.ok/templates/`, not in `config.yml`. Path exclusions live in `.okignore`, not config.
</Info>

## Editing and validating config

<Steps>
<Step title="Choose an edit surface">

- **Settings pane** (Cmd/Ctrl-,): validates live against the scope-appropriate schema; blocks `SCOPE_VIOLATION` writes.
- **IDE**: edit the YAML file directly; use the `$schema` magic comment for autocomplete.
- **CLI**: `ok config validate` and `ok config migrate`.

</Step>
<Step title="Validate merged config">

```bash
ok config validate
```

On success, prints merged sources (user and project paths, or `defaults only`). On failure, prints `SCHEMA_INVALID` or `REMOVED_KEY` errors with file:line:column when available.

</Step>
<Step title="Remove deprecated keys">

```bash
ok config migrate
ok config migrate --scope project
ok config migrate --dry-run
```

Strips obsolete keys from `config.yml` and/or `global.yml`, including silently dropped legacy keys (`sync.*`, `persistence.*`, `server.port`) plus every entry in the `REMOVED_KEYS` registry.

</Step>
</Steps>

<AccordionGroup>
<Accordion title="Removed config keys and replacements">

| Removed path | Use instead |
| --- | --- |
| `content.include` | `content.dir` + `.okignore` |
| `content.exclude` | `.okignore` at project root |
| `folders` | `<folder>/.ok/frontmatter.yml` and `<folder>/.ok/templates/` |
| `appearance.editorModeDefault` | Editor mode button (always WYSIWYG on open) |
| `appearance.sidebar.showAllFiles` | `appearance.sidebar.showHiddenFiles` |
| `server.host` | `--host` flag or `HOST` env |
| `server.port` | `--port` flag or `PORT` env |
| `mcp.autoStart` | `OK_MCP_AUTOSTART=0` |
| `github.oauthAppClientId` | `OPEN_KNOWLEDGE_GITHUB_CLIENT_ID` |
| `preview.baseUrl` | `ok ui` / running UI process |
| `upload.maxBytes`, `mcp.tools.*.maxResults` | Hardcoded in core; not configurable |

Run `ok config migrate` to strip these automatically.

</Accordion>
</AccordionGroup>

## Environment-only variables

These settings have no `config.yml` key and no Settings toggle. CLI flags beat environment variables where both apply.

### Common

| Variable | Effect |
| --- | --- |
| `HOST` | Collaboration server bind host. `--host` wins. Default: `localhost`. |
| `PORT` | Server bind port when `--port` / `--ui-port` are absent. |
| `OK_MCP_AUTOSTART` | Set to `0` to disable MCP shim auto-starting `ok start` when no server lock exists. |
| `OK_LOG_LEVEL` | Log verbosity (`info`, `debug`, `trace`). Falls back to `LOG_LEVEL`. |
| `OK_MCP_SPAWN_TIMEOUT_MS` | Milliseconds to wait for MCP auto-spawned server startup (positive integer; default internal timeout is 5000 ms when unset). |

### Advanced / operator

| Variable | Effect |
| --- | --- |
| `OPEN_KNOWLEDGE_GITHUB_CLIENT_ID` | Override the bundled GitHub OAuth App client ID. |
| `OK_EMBEDDINGS_API_KEY` | Fallback embeddings API key when none is in `~/.ok/secrets.yml`. Prefer `ok embeddings set-key`. |
| `OK_SHOWALL_MAX_ENTRIES` | Max file-tree entries per directory level before truncation (default `50000`). |
| `OK_SEARCH_MAX_ENTRIES` | Max entries in the search corpus name-only tier before deepest-tail paths drop (default `50000`). |
| `OK_BRIDGE_TOLERANCE_TELEMETRY` | Set to `1` to record bridge-tolerance events in `.ok/local/tolerance-telemetry.jsonl` (opt-in, local-only, records doc paths in cleartext). |
| `OTEL_SDK_DISABLED` | Inverted gate for OTLP push telemetry. Set to `false` to **enable** push (off by default). |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint (default `http://localhost:4318`). |
| `OTEL_EXPORTER_OTLP_TIMEOUT` | OTLP export timeout in milliseconds. |

### Contributor / dev-only

These appear in `.env.example` for the monorepo dev server; they do not affect end-user `ok` CLI runtime the same way:

| Variable | Effect |
| --- | --- |
| `VITE_PORT` | Vite dev server port (default `5173`). Does not affect `ok start` / `ok ui` port resolution. |
| `VITE_OTEL_ENABLED` | Frontend OpenTelemetry instrumentation in the Vite app. |

## Semantic search quick reference

Semantic search is off by default and configured entirely in project-local config plus secrets:

1. `ok embeddings set-key` — store provider key in `~/.ok/secrets.yml`.
2. Set `search.semantic.enabled: true` in `.ok/local/config.yml`.
3. Optionally override `search.semantic.baseUrl`, `model`, `dimensions`, or `similarityFloor`.

When enabled with a key set, search queries and matching document content are sent to the configured embeddings provider. Search degrades silently to lexical ranking if the key is missing, the provider errors, or you are offline.

## Related pages

<CardGroup>
<Card title="Project scaffold" href="/project-scaffold">
`.ok/` layout, the three config scopes, `.okignore`, and `content.dir` semantics.
</Card>
<Card title="Semantic search setup" href="/semantic-search-setup">
Enable embeddings ranking, store API keys, and understand content-egress constraints.
</Card>
<Card title="CLI reference" href="/cli-reference">
`ok config validate`, `ok config migrate`, and global flags that override env config.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
`ok diagnose health`, `ok diagnose bundle`, and recovering from stale config or server state.
</Card>
</CardGroup>

---

## 17. HTTP API reference

> Editor and agent HTTP routes on the Hocuspocus server (`/api/agent-write-md`, `/api/search`, `/api/documents`, `/api/share/*`, `/api/local-op/*`) used by the web app and desktop shell.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/17-http-api-reference.md
- Generated: 2026-06-25T22:48:01.925Z

### Source Files

- `packages/server/src/api-extension.ts`
- `packages/server/src/api-config.test.ts`
- `packages/server/src/api-agent-write-summary.test.ts`
- `packages/server/src/api-search.test.ts`
- `packages/server/src/share/publish.ts`
- `packages/server/src/share/construct-url.ts`
- `packages/server/src/api-extension.ok-init.test.ts`

---
title: "HTTP API reference"
description: "Editor and agent HTTP routes on the Hocuspocus server (`/api/agent-write-md`, `/api/search`, `/api/documents`, `/api/share/*`, `/api/local-op/*`) used by the web app and desktop shell."
---

The collaboration server registers a Hocuspocus extension from `createApiExtension` (`packages/server/src/api-extension.ts`). Its `onRequest` hook (priority 100) dispatches every `/api/*` path before static file serving. Request and response bodies are validated against Zod schemas exported from `@inkeep/open-knowledge-core` (`packages/core/src/schemas/api/`). The web editor, desktop shell, and desktop bridge call these routes on the loopback host bound by `ok start`.

```mermaid
flowchart LR
  subgraph clients [Clients]
    Web["Web editor"]
    Desktop["Desktop shell"]
    Bridge["Desktop bridge"]
  end
  subgraph server [Hocuspocus server]
    Gate["Origin + loopback gates"]
    Dispatch["Route table"]
    Yjs["Yjs / agent sessions"]
    FS["Content filesystem"]
    CLI["ok CLI subprocess"]
  end
  Web --> Gate
  Desktop --> Gate
  Bridge --> Gate
  Gate --> Dispatch
  Dispatch --> Yjs
  Dispatch --> FS
  Dispatch --> CLI
```

## Base URL and transport

| Property | Value |
| --- | --- |
| Host | Loopback only: `localhost`, `127.x.x.x`, or `[::1]` with optional port |
| Scheme | `http://` (WebSocket collab is separate at `ws://<host>/collab`) |
| Bootstrap | `GET /api/config` returns `collabUrl`, `port`, and optional `paneTarget` |
| CORS | `OPTIONS` preflight supported; `Access-Control-Allow-Origin` echoes a permitted loopback `Origin` |
| Client headers | Optional `x-ok-client-protocol`, `x-ok-client-runtime`, `x-ok-client-kind` (see `CLIENT_VERSION_HEADER` in `@inkeep/open-knowledge-core`) |

<Note>
`GET /api/config` builds `collabUrl` as `ws://<Host>/collab`. When the `Host` header is absent, `collabUrl` is `null` by design.
</Note>

## Security gates

All `/api/*` requests pass an origin gate: if `Origin` is present, the hostname must be `localhost`, `::1`, `[::1]`, or `127.*.*.*` (`packages/server/src/api-origin.ts`).

**Mutating routes** (writes, uploads, sync triggers, git checkout, and every `/api/local-op/*` path) additionally require:

- Peer address on loopback (`127.*`, `::ffff:127.*`, or `::1`)
- `Host` header matching `isAllowedWorkspaceHostHeader` (localhost or 127.x only)

In **ephemeral single-file mode**, every `/api/*` request is loopback-gated.

`/api/local-op/*` handlers also call `checkLocalOpSecurity`: loopback peer plus a loopback `Origin` when one is sent (`packages/server/src/local-op-security.ts`).

## Response formats

**Success** — `application/json` body validated against the route’s success schema.

**Error** — `application/problem+json` with RFC 7807-style fields:

<ResponseField name="type" type="string">
URN problem type, e.g. `urn:ok:error:invalid-request`, `urn:ok:error:loopback-required`, `urn:ok:error:not-found`. Full enum in `ProblemTypeSchema`.
</ResponseField>

<ResponseField name="title" type="string">
Human-readable summary.
</ResponseField>

<ResponseField name="status" type="number">
HTTP status code (400–599).
</ResponseField>

<ResponseField name="detail" type="string">
Optional extra context.
</ResponseField>

**Streaming** — Some routes return `application/x-ndjson` (clone, auth login, document list `showAll` stream). Terminal error lines use `{ type: "error", problem: { ... } }`.

## Route inventory

Paths are exact unless noted. Methods not listed return `405` with `Allow` when the handler enforces method.

### Bootstrap and workspace

| Method | Path | Purpose |
| --- | --- | --- |
| `GET`, `HEAD` | `/api/config` | Editor bootstrap: collab URL, port, pane target, single-file flag |
| `DELETE` | `/api/config` | One-shot consume of armed `paneTarget` (loopback + host gate) |
| `GET` | `/api/server-info` | Server instance ID, branch, disk-ack state |
| `GET` | `/api/workspace` | Resolved content directory path (loopback) |
| `GET` | `/api/principal` | Git-configured principal (loopback) |

### Documents and listing

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/document` | Read one open or on-disk document by `docName` |
| `GET` | `/api/documents` | List documents, assets, folders under optional `dir` |
| `GET` | `/api/pages` | Flat page index for sidebar |
| `GET` | `/api/asset` | Serve binary asset by content-relative `path` |
| `GET` | `/api/asset-text` | Serve text asset content |

### Agent writes (server-routed)

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/api/agent-write-md` | Primary markdown write into Yjs + disk |
| `POST` | `/api/agent-write` | Legacy plain-text write |
| `POST` | `/api/agent-patch` | Find/replace patch on document body |
| `POST` | `/api/frontmatter-patch` | Patch YAML frontmatter keys |
| `POST` | `/api/agent-undo` | Undo agent edit stack |
| `GET` | `/api/agent-activity` | List agent activity for a document |
| `GET` | `/api/agent-burst-diff` | Burst diff for agent stack item |

### Search and tags

| Method | Path | Purpose |
| --- | --- | --- |
| `GET`, `POST` | `/api/search` | Workspace search (omnibar, full-text, autocomplete) |
| `GET` | `/api/semantic-status` | Embeddings index status |
| `GET` | `/api/suggest-links` | Link suggestions for a document |
| `GET` | `/api/tags` | Tag index with counts |
| `GET` | `/api/tags/:name` | Documents carrying a tag (URL-encoded slashes) |

### Link graph

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/backlinks` | Incoming links for `docName` |
| `GET` | `/api/backlink-counts` | Counts for comma-separated `docNames` |
| `GET` | `/api/forward-links` | Outgoing links from `docName` |
| `GET` | `/api/link-graph` | N-degree neighborhood (`degrees`, optional `docName`) |
| `GET` | `/api/dead-links` | Broken links (optional `sourceDocName` filters) |
| `GET` | `/api/orphans` | Orphan pages (`mode`: `both`, `incoming`, `outgoing`) |
| `GET` | `/api/hubs` | High in-degree pages (`limit`) |

### File operations

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/api/create-page` | Create markdown page |
| `POST` | `/api/create-folder` | Create folder |
| `POST` | `/api/duplicate-path` | Duplicate file or folder |
| `POST` | `/api/rename-path` | Rename/move path |
| `POST` | `/api/delete-path` | Delete path |
| `POST` | `/api/trash/cleanup` | Permanently remove trashed items |
| `POST` | `/api/upload` | Stream upload asset (multipart) |

### History and versions

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/history` | List versions for `docName` |
| `GET` | `/api/history/:sha` | Fetch one version by SHA |
| `POST` | `/api/save-version` | Checkpoint current document |
| `POST` | `/api/rollback` | Roll back to a version |

### Share and git

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/api/share/construct-url` | Build `openknowledge.ai/d/…` share URL |
| `GET` | `/api/share/publish/owners` | List GitHub owners for publish UI |
| `GET` | `/api/share/publish/name-check` | Check repo name availability |
| `POST` | `/api/share/publish` | Create GitHub repo and push project |
| `GET` | `/api/git/branch-info` | Local branch metadata |
| `POST` | `/api/git/checkout` | Checkout branch |

### Sync

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/sync/status` | Sync engine status |
| `POST` | `/api/sync/trigger` | Trigger pull/push |
| `GET` | `/api/sync/conflicts` | List merge conflicts |
| `GET` | `/api/sync/conflict-content` | Conflict file content |
| `POST` | `/api/sync/resolve-conflict` | Resolve a conflict |

### Local operations (`/api/local-op/*`)

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/api/local-op/clone` | Clone repo, start server (NDJSON stream) |
| `POST` | `/api/local-op/ok-init` | Scaffold `.ok/` on a git worktree |
| `POST` | `/api/local-op/auth/login` | GitHub device-flow login (NDJSON) |
| `POST` | `/api/local-op/auth/status` | Auth status JSON |
| `POST` | `/api/local-op/auth/repos` | List accessible repos |
| `POST` | `/api/local-op/auth/signout` | Sign out |
| `POST` | `/api/local-op/auth/set-identity` | Set git identity |
| `POST` | `/api/local-op/embeddings/set-key` | Store embeddings API key |
| `POST` | `/api/local-op/embeddings/clear-key` | Clear embeddings key |

### Templates, folders, seeds, desktop helpers

| Method | Path | Purpose |
| --- | --- | --- |
| `GET`, `PUT`, `POST`, `DELETE` | `/api/template` | CRUD/move project templates |
| `GET` | `/api/templates` | List templates |
| `GET`, `PUT` | `/api/folder-config` | Folder frontmatter config |
| `GET` | `/api/page-headings` | Heading outline for a page |
| `POST` | `/api/install-skill` | Install agent skill |
| `GET` | `/api/skill/install-state` | Skill install snapshot |
| `GET` | `/api/seed/packs` | List starter packs |
| `GET` | `/api/seed/plan` | Plan seed application |
| `POST` | `/api/seed/apply` | Apply starter pack |
| `GET` | `/api/installed-agents` | Detect installed agent CLIs |
| `POST` | `/api/spawn-cursor` | Launch Cursor on project |
| `POST` | `/api/handoff` | Open-with-AI handoff dispatch |
| `POST` | `/api/client-logs` | Ingest renderer console logs |
| `GET` | `/api/rescue` | List rescue/timeline entries |
| `GET` | `/api/metrics/reconciliation` | Reconciliation metrics |
| `GET` | `/api/metrics/parse-health` | Parse health metrics |
| `GET` | `/api/metrics/agent-presence` | Live agent presence map |

<Info>
When `enableTestRoutes` is set, the server also exposes `/api/test-reset`, `/api/test-flush-git`, `/api/test-rescan-backlinks`, and `/api/test-rescan-files` for integration tests only.
</Info>

---

## `GET /api/config`

Desktop and web clients poll this route on startup.

:::endpoint GET /api/config
Returns the ok-ui-shaped bootstrap payload with same-origin `collabUrl`, bound port, and optional armed pane target.
:::

<ParamField body="(none)" type="—">
No query parameters. Uses request `Host` header to build `collabUrl`.
</ParamField>

<ResponseField name="collabUrl" type="string | null">
WebSocket URL `ws://<host>/collab`, or `null` when `Host` is missing.
</ResponseField>

<ResponseField name="previewUrl" type="null">
Always `null` on the collab server (preview URLs come from MCP `get_preview_url`).
</ResponseField>

<ResponseField name="port" type="number">
Bound server port from `.ok/local/server.lock`, or `0` when unconfigured.
</ResponseField>

<ResponseField name="paneTarget" type="string | null">
One-shot hash route (e.g. `#/folder/doc`) armed by CLI; cleared by `DELETE /api/config`.
</ResponseField>

<ResponseField name="singleFile" type="boolean">
`true` in ephemeral single-file sessions (`ok notes.md`).
</ResponseField>

<RequestExample>
```bash
curl -s http://127.0.0.1:7777/api/config
```
</RequestExample>

<ResponseExample>
```json
{
  "collabUrl": "ws://127.0.0.1:7777/collab",
  "previewUrl": null,
  "port": 7777,
  "paneTarget": null,
  "singleFile": false
}
```
</ResponseExample>

---

## Agent write routes

Agent HTTP writes mirror MCP `write` / `edit` but route through the collab server so Yjs, disk persistence, git flush, and contributor attribution stay consistent.

### `POST /api/agent-write-md`

:::endpoint POST /api/agent-write-md
Apply markdown to a document via Yjs, flush to disk and git, and return subscriber counts plus optional warnings.
:::

<ParamField body="docName" type="string" required>
Content-relative document name (no extension). Rejects reserved system/config names.
</ParamField>

<ParamField body="markdown" type="string" required>
Markdown body to write.
</ParamField>

<ParamField body="position" type="append | prepend | replace">
Default `append`. `replace` rewrites the full body.
</ParamField>

<ParamField body="extension" type=".md | .mdx | …">
Optional doc extension when creating a new file.
</ParamField>

<ParamField body="agentId" type="string" required>
Stable agent session identifier.
</ParamField>

<ParamField body="agentName" type="string" required>
Display name for presence and attribution.
</ParamField>

<ParamField body="summary" type="string">
Optional edit summary (max 80 chars; longer values truncated with `truncatedFrom` in response).
</ParamField>

<ResponseField name="timestamp" type="string">
ISO-8601 write timestamp.
</ResponseField>

<ResponseField name="subscriberCount" type="number">
Yjs subscribers on the document channel.
</ResponseField>

<ResponseField name="systemSubscriberCount" type="number">
Subscribers on the system observer channel.
</ResponseField>

<ResponseField name="summary" type="object">
`{ value, truncatedFrom?, hint? }` when a summary was provided.
</ResponseField>

<ResponseField name="warning" type="object">
Optional `content-divergence` or `disk-edit-reconciled` advisory.
</ResponseField>

<Warning>
Frontmatter cannot be changed via body find/replace on `/api/agent-patch`. Use `/api/frontmatter-patch` or `position: "replace"` on `/api/agent-write-md` for YAML block edits.
</Warning>

<RequestExample>
```bash
curl -s -X POST http://127.0.0.1:7777/api/agent-write-md \
  -H 'Content-Type: application/json' \
  -d '{
    "docName": "guides/quickstart",
    "markdown": "# Quickstart\n\nUpdated intro.\n",
    "position": "replace",
    "agentId": "cursor-1",
    "agentName": "Cursor",
    "summary": "Refresh quickstart intro"
  }'
```
</RequestExample>

Related legacy/surgical endpoints:

| Method | Path | Role |
| --- | --- | --- |
| `POST` | `/api/agent-write` | Legacy plain `content` write (parity with write-md for summaries) |
| `POST` | `/api/agent-patch` | Body `find` / `replace` surgical edit |
| `POST` | `/api/frontmatter-patch` | Patch frontmatter map with typed values |

---

## `GET` / `POST /api/search`

:::endpoint GET /api/search
Query the workspace search corpus with URL query parameters.
:::

:::endpoint POST /api/search
Same search engine with a JSON body (used by shared HTTP clients and MCP-backed flows).
:::

<ParamField body="query" type="string">
Search string. Max 200 characters; longer queries return `400`.
</ParamField>

<ParamField body="intent" type="autocomplete | full_text | omnibar">
Controls ranking tiers. `omnibar` returns page and folder entity matches; `full_text` includes body snippets.
</ParamField>

<ParamField body="ranking" type="navigation | relevance">
Reorders the same candidate set: `navigation` favors name matches; `relevance` favors body term density.
</ParamField>

<ParamField body="scopes" type="array">
Subset of `page`, `folder`, `content`, `file`. Also accepted as comma-separated `scope` query param on GET.
</ParamField>

<ParamField body="limit" type="number">
Maximum results (non-negative integer).
</ParamField>

<ParamField body="semantic" type="boolean">
Opt-in semantic reranking when embeddings are configured.
</ParamField>

<ParamField body="source" type="omnibar | mcp | http">
Telemetry source tag.
</ParamField>

<ResponseField name="results" type="array">
Entries with `kind`, `path`, `title`, `score`, `signals`, optional `snippet`.
</ResponseField>

<ResponseField name="elapsedMs" type="number">
Server-side search duration.
</ResponseField>

<ResponseField name="semantic" type="object">
When requested: `capable`, `applied`, `coverage: { embedded, total }`.
</ResponseField>

<ResponseField name="ready" type="boolean">
`false` while the file index boot gate is still warming (partial corpus).
</ResponseField>

<ResponseField name="truncated" type="boolean">
`true` when the name-only file tier hit `OK_SEARCH_MAX_ENTRIES`.
</ResponseField>

<RequestExample>
```bash
curl -s 'http://127.0.0.1:7777/api/search?query=arch&intent=omnibar'
```
</RequestExample>

<ResponseExample>
```json
{
  "query": "arch",
  "intent": "omnibar",
  "results": [
    { "kind": "folder", "path": "architecture", "title": "architecture", "score": 1, "signals": {} },
    { "kind": "page", "path": "architecture/overview", "title": "System Overview", "score": 0.9, "signals": {} }
  ],
  "elapsedMs": 12
}
```
</ResponseExample>

POST body limit is ~1 MiB; oversize bodies return `413` (`urn:ok:error:payload-too-large`). Malformed JSON returns `400`.

Pair with `GET /api/semantic-status` for embeddings key presence and index readiness.

---

## `GET /api/documents`

:::endpoint GET /api/documents
List documents, assets, folders, and non-markdown files under the content root.
:::

<ParamField body="dir" type="string">
Optional subdirectory filter (content-relative). Invalid paths return `400`.
</ParamField>

<ParamField body="showAll" type="boolean">
When `true`, include assets and non-markdown files subject to content filters.
</ParamField>

<ParamField body="depth" type="1">
With `showAll=true`, `depth=1` limits recursion to one level.
</ParamField>

<ResponseField name="documents" type="array">
`DocumentListEntry` objects: `kind` is `document`, `asset`, `folder`, or `file` with kind-specific fields.
</ResponseField>

<ResponseField name="truncated" type="boolean">
Present when the show-all walk hits the entry cap.
</ResponseField>

When `showAll=true` and the client sends `Accept: application/x-ndjson`, the handler streams one JSON object per line, ending with `{ "type": "complete", "truncated", "count" }`.

<RequestExample>
```bash
curl -s 'http://127.0.0.1:7777/api/documents?dir=guides'
```
</RequestExample>

For a single loaded document’s Yjs text, use `GET /api/document?docName=<name>`. A missing on-disk file returns `404` without creating a phantom Y.Doc.

---

## Share routes (`/api/share/*`)

Share endpoints read git remotes and invoke `ok share` subprocesses. Publish and owners routes require loopback security like other `/api/local-op/*` gates.

### `POST /api/share/construct-url`

:::endpoint POST /api/share/construct-url
Build an encoded share URL and underlying GitHub blob/tree link for a doc or folder.
:::

<ParamField body="kind" type="doc | folder" required>
Target type.
</ParamField>

<ParamField body="docPath" type="string">
Required when `kind` is `doc`. Content-relative path with extension.
</ParamField>

<ParamField body="folderPath" type="string">
Required when `kind` is `folder`. Empty string means content root.
</ParamField>

<ResponseField name="ok" type="boolean">
`true` on success.
</ResponseField>

<ResponseField name="shareUrl" type="string">
`https://openknowledge.ai/d/<encoded>` share link.
</ResponseField>

<ResponseField name="sharedUrl" type="string">
Underlying `github.com/.../blob/...` or `tree/...` URL.
</ResponseField>

<ResponseField name="branch" type="string">
Current git branch used for the link.
</ResponseField>

Error codes (HTTP 200 with `ok: false`): `no-remote`, `detached-head`, `branch-not-on-origin`, `non-github-remote`, `invalid-path`.

<RequestExample>
```bash
curl -s -X POST http://127.0.0.1:7777/api/share/construct-url \
  -H 'Content-Type: application/json' \
  -d '{ "kind": "doc", "docPath": "docs/guide.md" }'
```
</RequestExample>

### Publish flow

| Method | Path | Input | Output |
| --- | --- | --- | --- |
| `GET` | `/api/share/publish/owners` | — | `{ ok, owners: [{ login, kind, avatarUrl? }] }` or `{ ok: false, error }` |
| `GET` | `/api/share/publish/name-check` | `owner`, `name` query params | `{ ok, available }` |
| `POST` | `/api/share/publish` | `{ owner, name, visibility, description? }` | `{ ok, ownerLogin, repoName, cloneUrl, defaultBranch }` or `{ ok: false, error }` |

Publish errors include `name-conflict`, `saml-sso`, `auth-required`, `push-failed`, `init-failed`, `network`, `no-project`. Successful publish triggers a background `syncEngine.refreshRemote()`.

---

## Local operations (`/api/local-op/*`)

Desktop shell routes that spawn `ok` CLI subprocesses or touch secrets on disk. All require loopback connection and permitted origin.

### `POST /api/local-op/ok-init`

:::endpoint POST /api/local-op/ok-init
Initialize `.ok/` scaffolding on an absolute path inside the user home directory.
:::

<ParamField body="projectPath" type="string" required>
Absolute filesystem path to a git working tree.
</ParamField>

<ResponseField name="ok" type="boolean">
`true` when initialized or already initialized.
</ResponseField>

<ResponseField name="projectPath" type="string">
Canonical realpath on success.
</ResponseField>

<ResponseField name="reason" type="not-a-git-worktree | init-failed">
Present when `ok` is `false`.
</ResponseField>

- Non-absolute `projectPath` → `400` (`urn:ok:error:invalid-request`)
- Path outside home → `400` (`urn:ok:error:dir-outside-home`)
- Concurrent init → `429` with `Retry-After: 2`
- Idempotent: re-calling on an initialized project returns `{ ok: true }` without rewriting `config.yml`

<RequestExample>
```bash
curl -s -X POST http://127.0.0.1:7777/api/local-op/ok-init \
  -H 'Content-Type: application/json' \
  -d '{ "projectPath": "/Users/me/projects/my-wiki" }'
```
</RequestExample>

### `POST /api/local-op/clone`

<ParamField body="url" type="string" required>
Git remote URL (`https://`, `ssh://`, `git://`, or SCP-style `git@host:repo`). Blocks `file://`, `javascript:`, etc.
</ParamField>

<ParamField body="dir" type="string" required>
Destination directory within home (`~` expanded).
</ParamField>

<ParamField body="branch" type="string">
Optional branch name (validated).
</ParamField>

Returns `200` with `Content-Type: application/x-ndjson`. Progress events are JSON lines; completion line includes `{ type: "complete", port, dir }` after starting the project server. Timeout: 10 minutes. Concurrent clone → `429` (`Retry-After: 30`).

### Auth and embeddings

| Method | Path | Behavior |
| --- | --- | --- |
| `POST` | `/api/local-op/auth/login` | Device-flow NDJSON stream; optional `{ host }` (default `github.com`) |
| `POST` | `/api/local-op/auth/status` | `{ authenticated: boolean }` |
| `POST` | `/api/local-op/auth/repos` | Lists repos accessible to stored credentials |
| `POST` | `/api/local-op/auth/signout` | Clears stored auth |
| `POST` | `/api/local-op/auth/set-identity` | `{ name, email }` git identity |
| `POST` | `/api/local-op/embeddings/set-key` | `{ key }` → `{ keyPresent: true }` |
| `POST` | `/api/local-op/embeddings/clear-key` | → `{ keyPresent: false }` |

---

## Verify routes locally

<Steps>
<Step title="Start the collab server">
Run `ok start` (or `ok start --open`) in an initialized project. Note the bound loopback port from CLI output or `GET /api/config`.
</Step>
<Step title="Check bootstrap">
```bash
curl -s http://127.0.0.1:<port>/api/config | jq .
```
Confirm `collabUrl` and `port` match expectations.
</Step>
<Step title="Exercise search and listing">
```bash
curl -s 'http://127.0.0.1:<port>/api/search?query=readme&intent=omnibar' | jq '.results[:3]'
curl -s 'http://127.0.0.1:<port>/api/documents' | jq '.documents | length'
```
</Step>
<Step title="Confirm loopback gate">
A mutating request from a non-loopback peer or with `Host: evil.example.com` should return `403` (`urn:ok:error:loopback-required` or `urn:ok:error:host-not-allowed`).
</Step>
</Steps>

<AccordionGroup>
<Accordion title="Common error URNs">
| URN | Typical cause |
| --- | --- |
| `urn:ok:error:invalid-request` | Schema validation failure, bad `docName`, query too long |
| `urn:ok:error:loopback-required` | Remote peer on a mutating or local-op route |
| `urn:ok:error:host-not-allowed` | `Host` header not localhost/127.x |
| `urn:ok:error:invalid-origin` | `Origin` not on loopback |
| `urn:ok:error:doc-not-found` | Document missing on read/patch |
| `urn:ok:error:reserved-doc-name` | Write to system/config doc |
| `urn:ok:error:concurrent-operation` | Another clone/auth/publish/init in flight |
| `urn:ok:error:payload-too-large` | Search POST body &gt; ~1 MiB |
</Accordion>
<Accordion title="MCP vs HTTP writes">
MCP `write` and `edit` tools call these same HTTP routes when a collab server is running. Server-free MCP reads (`exec`, `get_preview_url`) do not use this route table — see the collaboration server lifecycle for the split.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Collaboration server" href="/collaboration-server">
Hocuspocus lifecycle, per-project locks, and when MCP routes writes through HTTP.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Seventeen MCP tools that delegate to these HTTP endpoints for mutations.
</Card>
<Card title="Team sharing" href="/team-sharing">
`ok share` CLI commands backing `/api/share/*` routes.
</Card>
<Card title="Semantic search setup" href="/semantic-search-setup">
Configure embeddings keys via `/api/local-op/embeddings/*` and `search.semantic.*` config.
</Card>
<Card title="Auth reference" href="/auth-reference">
GitHub OAuth device flow and token storage used by `/api/local-op/auth/*`.
</Card>
</CardGroup>

---

## 18. Auth reference

> GitHub OAuth device flow (`ok auth login`), PAT storage, `gh` CLI credential reuse, git credential helper, and token scopes for clone, sync, and share operations.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/18-auth-reference.md
- Generated: 2026-06-25T22:47:38.317Z

### Source Files

- `packages/cli/src/commands/auth/index.ts`
- `packages/cli/src/commands/auth/login.ts`
- `packages/cli/src/commands/auth/pat.ts`
- `packages/cli/src/commands/auth/git-credential.ts`
- `packages/cli/src/commands/auth/status.ts`
- `packages/server/src/github-permissions.ts`
- `packages/cli/src/commands/clone.ts`

---
title: "Auth reference"
description: "GitHub OAuth device flow (`ok auth login`), PAT storage, `gh` CLI credential reuse, git credential helper, and token scopes for clone, sync, and share operations."
---

Open Knowledge authenticates against **GitHub** and **GitHub Enterprise Server (GHE)** only. Credentials power `git` operations (clone, pull, push, sync) and GitHub REST API calls (repo listing, publish, push-permission probes). The CLI resolves credentials in a fixed priority order, stores OAuth tokens when you sign in through Open Knowledge, and wires them into `git` through a credential helper.

## Credential tiers

Open Knowledge classifies the active credential source as tier **A**, **B**, **C**, or **none**. Resolution is consistent across `ok auth status`, `ok clone`, and server-side sync.

| Tier | Source | Git credential helper | Disconnect in app |
| --- | --- | --- | --- |
| **A** | `gh` CLI (`gh auth token`) | `credential.helper=!gh auth git-credential` | Not applicable — managed by `gh` |
| **B** | Token stored by Open Knowledge (`gitProtocol: https`) | `credential.helper=!open-knowledge auth git-credential` | `ok auth signout` or Settings → Disconnect |
| **C** | Token stored with `gitProtocol: ssh` (status classification only) | Same helper as tier B when used for HTTPS remotes | Same as tier B |
| **none** | No credential | No helper injected | — |

Tier **A** always wins when `gh` is installed and returns a non-empty token. A stored Open Knowledge token is ignored until `gh` stops providing one.

<Callout type="info">
  On the desktop app, Settings → Account shows **Connected via gh CLI** for tier A and exposes **Disconnect** only for tiers B and C. Disconnecting clears Open Knowledge's stored token, not `gh`'s session.
</Callout>

## Sign in with device flow

`ok auth login` runs the GitHub OAuth **device authorization flow**. It is the primary sign-in path when `gh` is unavailable or you want Open Knowledge to own the token.

<Steps>

<Step title="Run login">

```bash
ok auth login
```

For GHE, pass the hostname:

```bash
ok auth login --host github.example.com
```

</Step>

<Step title="Authorize in the browser">

The CLI prints a verification URL and one-time code:

```
Open: https://github.com/login/device
Enter code: XXXX-XXXX
```

Complete authorization in the browser before the code expires.

</Step>

<Step title="Verify">

```bash
ok auth status
```

Expected output when authenticated:

```
✓ Logged in as <login> on github.com
```

</Step>

</Steps>

### JSONL mode

Pass `--json` for machine-readable progress events (used by the editor and desktop app via `/api/local-op/auth/login`):

<RequestExample>

```bash
ok auth login --json
```

</RequestExample>

<ResponseExample>

```jsonl
{"type":"verification","user_code":"ABCD-1234","verification_uri":"https://github.com/login/device","expires_in":899}
{"type":"complete","host":"github.com","login":"octocat"}
```

</ResponseExample>

### OAuth scopes

Device flow requests these scopes by default:

| Scope | Purpose |
| --- | --- |
| `repo` | Clone, pull, and push private repositories; create repos during publish |
| `read:user` | Resolve the authenticated login after sign-in |
| `user:email` | Attach name and email metadata to the stored credential |

### OAuth client ID

The bundled OAuth app client ID is used unless overridden:

<ParamField body="OPEN_KNOWLEDGE_GITHUB_CLIENT_ID" type="string">
  Optional environment variable. When unset, Open Knowledge uses the default client ID from `@inkeep/open-knowledge-core`.
</ParamField>

## Store a personal access token

Use `ok auth pat` when device flow is unavailable, when you need a token with explicit scopes, or when clone fails with a **scope mismatch**.

<Steps>

<Step title="Create a PAT on GitHub">

Create a classic PAT or fine-grained token with at least the **`repo`** scope (full control of private repositories). For publish flows that create repositories, `repo` is required.

</Step>

<Step title="Store it in Open Knowledge">

```bash
ok auth pat
```

The command prompts securely, validates the token against `GET /user`, then stores it with `gitProtocol: https`.

</Step>

<Step title="Confirm">

```bash
ok auth status --json
```

</Step>

</Steps>

`ok auth pat` supports `--host` for GHE and `--json` for scripting.

## Reuse `gh` CLI credentials

When `gh` is on `PATH` and `gh auth token` returns a token, Open Knowledge treats that as **tier A** and delegates all `git` HTTPS authentication to:

```
credential.helper=!gh auth git-credential
```

`detectGh` probes `gh` and common install paths (`/opt/homebrew/bin/gh`, `/usr/local/bin/gh`, `/usr/bin/gh`, and others) with a 5-second timeout.

If you already use `gh auth login`, you typically do not need `ok auth login` for clone or sync on the same machine.

## Token storage

Stored credentials (tiers B and C) persist per GitHub host.

| Backend | Location | When used |
| --- | --- | --- |
| **keyring** (preferred) | OS keychain, service `open-knowledge`, account = host | Default on macOS and supported Linux desktops |
| **file** (fallback) | `~/.ok/auth.yml` (mode `0600`, directory mode `0700`) | Headless Linux, keychain timeout (>2s init), or keychain unavailable |

On first read, a file-stored credential is **migrated** into the keychain when possible; the plaintext copy is removed.

Each entry stores:

<ResponseField name="login" type="string">
  GitHub username from `GET /user`.
</ResponseField>

<ResponseField name="token" type="string">
  OAuth or PAT bearer token used as the git HTTPS password.
</ResponseField>

<ResponseField name="gitProtocol" type="string">
  `https` (default after login or PAT) or `ssh` (affects tier classification).
</ResponseField>

<ResponseField name="name" type="string">
  Display name from the GitHub profile, when available.
</ResponseField>

<ResponseField name="email" type="string">
  Primary email from the GitHub profile, when available.
</ResponseField>

`ok auth signout --host <host>` clears the credential from **both** backends when present.

## Git credential helper

Open Knowledge implements the [git credential protocol](https://git-scm.com/docs/gitcredentials) as a hidden subcommand:

```bash
open-knowledge auth git-credential get
```

`git` invokes this helper (via `credential.helper=!open-knowledge auth git-credential`) during HTTPS clone, pull, and push. The helper:

1. Reads `host=` (and related fields) from stdin.
2. Looks up a stored token for that host.
3. Writes `username=<login>` and `password=<token>` to stdout, or exits `1` when absent.

### `gh` token relay

When the collaboration server runs `git` with a tier-A `gh` token, it sets environment variables consumed by the helper:

| Variable | Role |
| --- | --- |
| `OK_GH_TOKEN` | Bearer token to relay |
| `OK_GH_TOKEN_HOST` | Host the token applies to |

If both are set and `OK_GH_TOKEN_HOST` matches the requested host, the helper returns `username=x-access-token` and the relayed password — without reading the keychain. This lets sync reuse `gh` credentials even though the persistent helper path is `open-knowledge auth git-credential`.

### Clone and sync wiring

| Operation | Credential resolution |
| --- | --- |
| `ok clone` | `resolveAuth` → tier A/B helper, or skip auth for **public** `github.com` HTTPS repos |
| `ok sync` / `ok push` / `ok pull` | Uses the project's existing `git` remote and system/git config; server auto-sync injects `buildSyncCredentialArgs` (`credential.helper=!open-knowledge auth git-credential`) |
| Collaboration server sync engine | Same helper plus optional `OK_GH_TOKEN` relay from `createGhTokenSource` (60s TTL cache) |

Clone sets `GIT_TERMINAL_PROMPT=0` so `git` never blocks on interactive username/password prompts.

## Auth commands

| Command | Description |
| --- | --- |
| `ok auth login` | Device flow sign-in; stores token in keychain/file |
| `ok auth pat` | Prompt for and store a PAT |
| `ok auth status` | Show login and tier; exits `1` when unauthenticated |
| `ok auth repos` | List repositories visible to the resolved token |
| `ok auth signout` | Remove stored credentials for `--host` |
| `ok auth git-credential get` | Git credential helper (not for direct use) |

Shared flags:

<ParamField body="--host" type="string" default="github.com">
  GitHub.com or GHE hostname. Non-GitHub hosts (GitLab, Bitbucket, etc.) are rejected.
</ParamField>

<ParamField body="--json" type="boolean" default="false">
  Emit JSON or JSONL instead of human-readable stderr.
</ParamField>

### Status JSON shape

```json
{
  "type": "status",
  "host": "github.com",
  "backend": "keyring",
  "authenticated": true,
  "tier": "B",
  "login": "octocat",
  "name": "The Octocat",
  "email": "octocat@github.com"
}
```

## Scopes and operations

### Clone (`ok clone`)

| Repo visibility | Auth required? | Minimum scope |
| --- | --- | --- |
| Public on `github.com` | No — anonymous HTTPS clone | — |
| Private or non-`github.com` public | Yes | `repo` (HTTPS) or SSH key (SSH URL) |

On auth failure, `ok clone` prints actionable recovery:

- **No credential / 401 / unknown auth** → `ok auth login`, then re-run clone
- **Scope mismatch** (`insufficient scopes`, `missing scope`, `required scope`) → create PAT with `repo`, run `ok auth pat`, re-run clone
- **403 with access** → check repository permissions for the signed-in account
- **SSH auth failure** → fix SSH keys or switch to HTTPS URL

### Sync (`ok sync`, auto-sync)

Sync performs `git pull` and `git push` against `origin`. It needs the same HTTPS credentials as manual git, effectively requiring **`repo`** scope for private remotes.

The sync engine surfaces typed error codes including `auth-401`, `auth-403`, `auth-scope-mismatch`, and `auth-no-credential`. When credentials expire or lack scope, sync enters **`auth-error`** state until you reconnect.

Push-permission probes (`checkPushPermission`) call `GET /repos/{owner}/{repo}` and read `permissions.push`. Token resolution order: **`gh` token → token store → anonymous (denied)**.

### Share and publish

Two distinct surfaces use GitHub auth:

| Surface | Commands | Auth use |
| --- | --- | --- |
| **Publish to GitHub** | `ok share owners`, `ok share name-check`, `ok share publish` | REST API to create repo + `git push` for initial upload |
| **Config sharing mode** | `ok config-sharing share`, `unshare`, `status` | Local `.git/info/exclude` only — **no GitHub token** |

Publish resolves tokens via the same order as `ok auth repos`: **`gh` first, then stored token**. Without a token, publish returns `auth-required`.

`ok share publish` requires `--owner`, `--name`, `--visibility`, and `--project-dir`. It creates the repository (user or org), sets `origin`, and pushes.

## GitHub Enterprise Server

Pass `--host <ghe-hostname>` to auth commands. API calls use `https://<host>/api/v3`. Device flow and PAT validation respect the host-specific base URL.

`validateGitHubHost` rejects known non-GitHub forge hostnames at the CLI layer.

## Desktop and editor integration

The desktop utility process passes `detectGh` and `makeLazyProbeTokenStore()` into the collaboration server so sync and permission probes share the same credential model as the CLI.

Editor flows:

- **Settings → Account → Connect GitHub** spawns `ok auth login --json` through the local-op HTTP API.
- **Disconnect** spawns `ok auth signout`.
- **Clone / publish wizards** call `ok auth status` and `ok auth repos` subprocesses.

Tier A accounts show as managed by `gh`; disconnect is not offered because Open Knowledge does not store a separate token.

## Troubleshooting

<AccordionGroup>

<Accordion title="Not logged in during clone">

```bash
ok auth login
ok clone owner/repo
```

If `gh` is authenticated, check `gh auth status` — tier A should satisfy private clones without `ok auth login`.

</Accordion>

<Accordion title="Token invalid after sign-in">

```bash
ok auth status
```

If status reports `token invalid`, sign out and sign in again:

```bash
ok auth signout
ok auth login
```

</Accordion>

<Accordion title="Scope mismatch on push or clone">

Create a PAT with **`repo`** scope at [github.com/settings/tokens](https://github.com/settings/tokens), then:

```bash
ok auth pat
```

Re-run the failing command. Scope-mismatch recovery intentionally suggests `ok auth pat`, not another device-flow login.

</Accordion>

<Accordion title="Keychain fallback to file storage">

If you see `[auth] token storage: file (~/.ok/auth.yml)`, the OS keychain was unavailable or slow to initialize. Credentials still work; permissions on `~/.ok/auth.yml` are restricted to your user.

</Accordion>

<Accordion title="Sync paused with auth-error">

Reconnect via Settings → Account or `ok auth login`. Confirm `ok auth status` succeeds, then trigger sync with `ok sync` or the editor sync control.

</Accordion>

</AccordionGroup>

## Related pages

<CardGroup cols={2}>

<Card title="GitHub sync and conflicts" href="/github-sync-and-conflicts">
  Clone with `ok clone`, enable auto-sync, and resolve merge conflicts.
</Card>

<Card title="Team sharing" href="/team-sharing">
  Publish projects with `ok share publish` and manage config sharing with `ok config-sharing`.
</Card>

<Card title="CLI reference" href="/cli-reference">
  Full `ok` subcommand listing including `auth`, `clone`, `sync`, and `share`.
</Card>

<Card title="Troubleshooting" href="/troubleshooting">
  Diagnose server locks, repair skills, and recover from stale state with `ok clean`.
</Card>

</CardGroup>

---

## 19. Karpathy LLM wiki workflow

> Source-grounded knowledge base pattern with `research/` and `articles/` folders, starter packs, and MCP `workflow` kinds `ingest`, `research`, and `consolidate`.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/19-karpathy-llm-wiki-workflow.md
- Generated: 2026-06-25T22:59:07.878Z

### Source Files

- `docs/content/workflows/karpathy-llm-wiki.mdx`
- `packages/server/src/mcp/tools/workflow.ts`
- `packages/server/src/mcp/tools/ingest-body.ts`
- `packages/server/src/mcp/tools/research-body.ts`
- `packages/server/src/mcp/tools/consolidate-body.ts`
- `packages/cli/src/commands/seed.ts`
- `docs/content/features/agent-activity.mdx`

---
title: "Karpathy LLM wiki workflow"
description: "Source-grounded knowledge base pattern with `research/` and `articles/` folders, starter packs, and MCP `workflow` kinds `ingest`, `research`, and `consolidate`."
---

Open Knowledge implements Andrej Karpathy's LLM-curated wiki pattern as the default `knowledge-base` starter pack (`ok seed`, pack id `knowledge-base`), scaffolds three content folders under the project `content.dir`, and wires the MCP `workflow` tool's `ingest`, `research`, and `consolidate` kinds to procedural guides in `packages/server/src/mcp/tools/`. The pack splits Karpathy's single `wiki/` layer into `research/` (provisional) and `articles/` (canonical) so promotion is an explicit `consolidate` step rather than accidental drift.

## Pattern overview

Karpathy's gist describes three persistent layers plus operational metadata:

| Layer | Role | Karpathy name | Open Knowledge name |
| --- | --- | --- | --- |
| Raw sources | Verbatim external material; read, never edited | `raw/` | `external-sources/` |
| Wiki pages | LLM-synthesized summaries and cross-links | `wiki/` (single layer) | `research/` + `articles/` (split) |
| Audit trail | Append-only record of ingest, query, and lint activity | `log.md` | `log.md` at pack root |
| Schema | Tells the agent how folders behave | `CLAUDE.md` / `AGENTS.md` | `<folder>/.ok/frontmatter.yml` + per-folder templates |

Open Knowledge adds an explicit promotion gate: `research/` articles carry `status: provisional`; `articles/` carry `status: canonical` and a `supersedes:` chain back to the research they replace.

```mermaid
flowchart TB
  subgraph ingest_layer["external-sources/"]
    ES["Raw wrappers + binaries<br/>tags: source, immutable, layer-ingest"]
  end
  subgraph research_layer["research/"]
    R["Provisional synthesis<br/>status: provisional<br/>sources: frontmatter list"]
  end
  subgraph canonical_layer["articles/"]
    A["Canonical decisions<br/>status: canonical<br/>supersedes: research paths"]
  end
  LOG["log.md — append-only audit"]
  WF["MCP workflow tool"]
  WF -->|kind: ingest| ES
  WF -->|kind: research| R
  WF -->|kind: consolidate| A
  ES -->|cite local paths| R
  R -->|promote after decision| A
  ES & R & A --> LOG
```

<Note>
The knowledge base is **closed-loop**: downstream docs cite local paths under `external-sources/`, not bare web URLs. Agent-initiated fetches for grounding claims follow the same `ingest` discipline as user-shared URLs.
</Note>

## Starter pack layout

`ok seed` (default pack `knowledge-base`) creates three folders, three templates, folder frontmatter, and `log.md`. Default subfolder suggestion is `brain/`; pass `--root` to nest the pack or leave blank for project root.

:::files
your-project/
├── external-sources/
│   └── .ok/
│       ├── frontmatter.yml
│       └── templates/clip.md
├── research/
│   └── .ok/
│       ├── frontmatter.yml
│       └── templates/research-log.md
├── articles/
│   └── .ok/
│       ├── frontmatter.yml
│       └── templates/article.md
└── log.md
:::

| Folder | Template | `status` | Produced by |
| --- | --- | --- | --- |
| `external-sources/` | `clip` | — (immutable source) | `workflow({ kind: "ingest" })` |
| `research/` | `research-log` | `provisional` | `workflow({ kind: "research" })` |
| `articles/` | `article` | `canonical` | `workflow({ kind: "consolidate" })` |

Each folder's `.ok/frontmatter.yml` carries a `description` the agent reads on every `exec("ls …")` listing — distributed schema instead of a single root `AGENTS.md`. The `open-knowledge-pack-knowledge-base` skill (installed by `ok seed --pack knowledge-base`) holds workflow rules so template bodies stay structural only.

## MCP `workflow` tool

The `workflow` tool returns numbered procedural guides (instructional text, not data). The agent executes the guide using `exec`, `write`, `edit`, `links`, and other MCP tools. `previewUrl` is always `null` for workflow responses.

<ParamField body="kind" type="enum" required>
`ingest` | `research` | `consolidate` | `discover` | `wiki`. Karpathy's three-layer pipeline uses the first three.
</ParamField>

<ParamField body="source" type="string">
Required when `kind` is `ingest`. URL, local file path, or identifier to capture verbatim.
</ParamField>

<ParamField body="topic" type="string">
Required when `kind` is `research` or `consolidate`. Question, topic, or anchor URL.
</ParamField>

<ParamField body="cwd" type="string">
Optional project root override. Defaults to the connected project's `content.dir` from `.ok/config.yml`.
</ParamField>

### `ingest` — preserve raw sources

`workflow({ kind: "ingest", source: "https://…" })` returns a guide that:

- Sanity-checks scope, duplicate sources, and intent (preserve vs analyze)
- Classifies binary vs text sources; downloads binaries to `external-sources/<slug>.<ext>` with `sha256` and `bytes` metadata
- Writes a markdown wrapper with `preservation: binary` or `preservation: text-extracted`, `source_url`, and tags `source`, `immutable`, `layer-ingest`
- Refuses analysis, summarization, and silent chaining into `research`
- On sha256 mismatch for an existing slug, appends dated siblings (`<slug>.YYYY-MM-DD.<ext>`) with `supersedes:` — the layer is append-only by convention

Binary wrappers embed assets via `![[file.ext]]`; text wrappers preserve extracted content verbatim in the body.

### `research` — provisional synthesis

`workflow({ kind: "research", topic: "…" })` returns a guide that:

- Creates checkpoint tasks before any external fetch
- Scans existing coverage (`grep`, `ls`, `cat`) and routes to Path A (new article), Path B (chat-only answer), or Path C (update existing)
- Stops at a scoping gate in supervised mode until the user confirms a research rubric
- Calls `ingest` per source before analyzing — persist-as-you-go is mandatory
- Writes under `research/<slug>.md` with `status: provisional` and a `sources:` frontmatter list
- Validates with `links({ kind: "dead" })` and cross-links neighbor docs
- Files valuable Q&A back as short pages (Karpathy's query step)

Default article structure: Question, Context, Findings (with inline source links), Trade-offs, Open questions, Tentative recommendation, Further reading.

### `consolidate` — promote to canonical

`workflow({ kind: "consolidate", topic: "…" })` returns a guide that:

- **STOP gate:** confirms a real team decision before any write; returns early if still provisional
- Loads research articles and their `sources:` chain
- Writes under `articles/<slug>.md` with `status: canonical` and `supersedes: [research-path.md]`
- Updates superseded research with `superseded_by:` — research is never deleted
- Uses direct voice (decisions stated as decisions, not options)

Canonical structure: Summary, Context, Decision, Rationale, Trade-offs, Alternatives considered, Implementation notes, Further reading.

## End-to-end procedure

<Steps>
<Step title="Initialize the project">

Run `ok init` to scaffold `.ok/`, git, skills, and MCP registration. See [Initialize a project](/initialize-project).

</Step>

<Step title="Seed the knowledge-base pack">

```bash
ok seed --pack knowledge-base --yes
# or nest under a subfolder:
ok seed --pack knowledge-base --root brain --yes
```

Verify folders with `exec("ls external-sources research articles")` or the editor sidebar. Seeding is idempotent — existing entries are skipped.

</Step>

<Step title="Wire an MCP agent">

Confirm `workflow` appears in the agent's tool list alongside `exec`, `write`, `edit`, and `links`. See [Wire agent editors](/wire-agent-editors).

</Step>

<Step title="Ingest sources">

For each external URL or file:

```
workflow({ kind: "ingest", source: "https://example.com/spec" })
```

Expect wrappers under `external-sources/` with `source_url`, `date_fetched`, and preservation metadata. Five sources → five files.

</Step>

<Step title="Research the topic">

```
workflow({ kind: "research", topic: "agent-framework evaluation" })
```

Confirm `research/<slug>.md` exists with `status: provisional`, populated `sources:`, and inline citations to `external-sources/` paths.

</Step>

<Step title="Query and file answers">

Ask the agent questions against the research doc. Valuable answers become new `research/` pages or short Q&A files — not chat-only history.

</Step>

<Step title="Consolidate after a decision">

Only when the team has decided:

```
workflow({ kind: "consolidate", topic: "agent-framework evaluation" })
```

Confirm `articles/<slug>.md` with `status: canonical`, `supersedes:` chain, and `superseded_by:` on the research doc.

</Step>
</Steps>

## Promotion rhythm

| Trigger | Tool | Output layer |
| --- | --- | --- |
| Source arrives (URL, PDF, transcript) | `ingest` | `external-sources/` |
| Agent fetched the web to ground a claim | `ingest` | `external-sources/` |
| Synthesizing 2+ sources into findings | `research` | `research/` (`provisional`) |
| Team decided; position is canonical | `consolidate` | `articles/` (`canonical`) |
| Scratch note or runbook | `write` | Any folder (outside the three-kind pipeline) |
| Useful one-off query answer | `write` to `research/` | Provisional page, not chat |

<Warning>
**Consolidate too early.** Canonical articles that precede real decisions require constant rewrites. Keep uncertainty in `research/` until the decision is firm.

**Ingest your own thoughts.** `ingest` is for external sources preserved verbatim. Reflections belong in `research/` or a separate notes folder.

**Bare web URLs in body text.** An inline `https://…` link inside a knowledge-base doc is a TODO — run `ingest` first, then cite `./external-sources/<slug>.md`.
</Warning>

## Log discipline

`log.md` at the pack root is append-only. After any turn that creates, edits, or restructures content, append one dated entry covering:

- `ingest`, `research`, or `consolidate` runs
- Direct `write` / `edit` / `move` / `delete` outside the three workflow kinds
- Folder restructures and `.ok/config.yml` changes

Reference touched docs as markdown links (`[doc](./path/doc.md)`) so entries register in `links({ kind: "backlinks" })`.

## Composing features

| Concern | Surface |
| --- | --- |
| Agent-readable folder rules | `<folder>/.ok/frontmatter.yml` on `exec("ls")` |
| Template instantiation | `write({ document: { path, template: "research-log" } })` |
| Edit attribution | Agent activity panel + shadow-repo timeline |
| Link graph hygiene | `links` (`dead`, `orphans`, `backlinks`) |
| Closed-loop citations | `sources:` frontmatter + inline `./external-sources/…` links |
| Crash-safe long research | Persist-as-you-go: ingest per source, edit article section-by-section |

## Cadence

| When | Action |
| --- | --- |
| As sources arrive | `ingest` (~30 s per source) |
| Weekly | `research` pass on recent ingests; flag contradictions |
| Per decision | `consolidate` to `articles/` |
| Monthly | `links({ kind: ["dead", "orphans"] })` lint pass on `articles/` |

End each ingest session with one synthesis query; file the answer in `research/` so the vault compounds.

## Karpathy mapping reference

| Karpathy operation | Open Knowledge equivalent |
| --- | --- |
| Ingest (touch many wiki pages) | `ingest` → optional neighbor link updates (Step 7 in ingest guide) |
| Query (search + synthesize + file back) | Agent `search` + `exec` + `research`; file Q&A as new pages |
| Lint (contradictions, orphans, stale claims) | `links` tool + periodic agent prompts |
| Index (`index.md`) | Dynamic via `exec("ls")` enrichment + sidebar; optional hand-written `index.md` |
| Schema (`CLAUDE.md`) | Per-folder `.ok/frontmatter.yml` + `knowledge-base` pack skill |

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Run `ok init`, `ok start --open`, and confirm MCP tools respond before seeding the pack.
</Card>
<Card title="Initialize a project" href="/initialize-project">
Scaffold `.ok/`, git, skills, and editor MCP registration.
</Card>
<Card title="Folders and templates" href="/folders-and-templates">
Folder frontmatter cascade and `write({ document: { template } })` resolution.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Full `workflow` input schema and the other sixteen MCP tools.
</Card>
<Card title="CLI reference" href="/cli-reference">
`ok seed` flags (`--pack`, `--root`, `--dry-run`, `--list-packs`).
</Card>
<Card title="Entity vault workflow" href="/entity-vault">
Alternative starter pack for people, companies, and meeting timelines instead of source-grounded research.
</Card>
</CardGroup>

---

## 20. Entity vault workflow

> GBrain-compatible entity dossiers with compiled truth and append-only timelines, scaffolded via starter packs and maintained through MCP write and checkpoint tools.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/20-entity-vault-workflow.md
- Generated: 2026-06-25T22:48:20.529Z

### Source Files

- `docs/content/workflows/entity-vault.mdx`
- `packages/server/src/mcp/tools/workflow.ts`
- `packages/server/src/mcp/tools/discover-body.ts`
- `packages/server/src/mcp/tools/checkpoint.ts`
- `packages/server/src/mcp/tools/history.ts`
- `packages/cli/src/commands/seed.ts`
- `packages/core/src/templates/template-format.ts`

---
title: "Entity vault workflow"
description: "GBrain-compatible entity dossiers with compiled truth and append-only timelines, scaffolded via starter packs and maintained through MCP write and checkpoint tools."
---

The `entity-vault` starter pack scaffolds a GBrain-compatible Markdown vault of typed entity dossiers—`people/`, `companies/`, `meetings/`, `concepts/`, `originals/`, and `media/`—each with folder frontmatter, `.ok/templates/`, and a bundled pack skill. Open Knowledge is the cockpit layer: agents create and update dossiers through MCP `write` and `edit`, humans review in the editor, and `checkpoint` plus `history` capture project-wide restore points over git-backed attribution.

## Dossier model

Every durable dossier in `people/`, `companies/`, and `concepts/` splits into two zones separated by a `--- timeline ---` sentinel:

| Zone | Location | Mutation rule |
| --- | --- | --- |
| Compiled truth | Above `--- timeline ---` | Rewritable synthesis; update when evidence changes your current understanding |
| Timeline | Below `--- timeline ---` | Append-only dated bullets; never edit existing entries |

Timeline bullets use a parseable shape agents and external indexers can rely on:

```markdown
- **YYYY-MM-DD** | source | @author — evidence. Confidence: direct note.
```

Route new facts deliberately: change **compiled truth** when the synthesis shifts; append a timeline bullet when the entry is raw evidence. Meeting notes in `meetings/` and originals in `originals/` stay verbatim—agents extract entities from them into dossiers rather than rewriting the source.

<Note>
The pack id is `entity-vault` (display name **Personal CRM**). There is no `gbrain` pack alias—interop is a Markdown shape contract, not a bundled engine.
</Note>

## Open Knowledge vs optional GBrain

| Layer | Open Knowledge | Garry Tan's `gbrain` (optional) |
| --- | --- | --- |
| Markdown files | Creates, edits, reviews, templates, folder guidance | Imports/syncs as source material |
| Human correction | WYSIWYG/source editor, activity attribution, version checkpoints | Sees corrections after import/sync |
| Agent writes | MCP `write`, `edit`, `links`, `checkpoint`, `search`, `exec` | Separate GBrain MCP/skills if installed |
| Search/retrieval | Project search and link-graph tooling | DB-backed embeddings, hybrid retrieval, graph automation |
| Interop contract | Plain Markdown + Git | `gbrain import` / `gbrain sync --repo` |

Open Knowledge does not replace `gbrain`. The entity vault ships the Markdown half; `gbrain` is an optional indexing and automation layer over the same files.

## Scaffold the vault

<Steps>
<Step title="Initialize the project">

Run `ok init` if the project is not already initialized. See [Initialize a project](/initialize-project).

</Step>
<Step title="Seed the entity-vault pack">

<CodeGroup>

```bash Seed into default vault/ subfolder
ok seed --pack entity-vault
```

```bash Seed into a custom subfolder
ok seed --pack entity-vault --root my-vault -y
```

```bash Preview without writing
ok seed --pack entity-vault --dry-run
```

</CodeGroup>

The pack's `defaultSubfolder` is `vault/`. Omit `--root` on a TTY to accept the suggested subfolder; pass `-y` in non-interactive runs.

</Step>
<Step title="Verify layout and skills">

Confirm folders, root files, and templates landed. `ok seed` installs `open-knowledge-pack-entity-vault` next to the platform skill for each wired editor (Claude Code, Cursor, Codex).

</Step>
</Steps>

:::files
your-project/
└── vault/                          # defaultSubfolder: vault
    ├── USER.md                     # user profile for briefings
    ├── SOUL.md                     # agent persona / values
    ├── ACCESS_POLICY.md            # 4-tier privacy model
    ├── HEARTBEAT.md                # operational cadence
    ├── log.md                      # append-only work log
    ├── people/
    │   └── .ok/
    │       ├── frontmatter.yml
    │       └── templates/person.md
    ├── companies/
    ├── meetings/
    ├── concepts/
    ├── originals/
    └── media/
:::

Each typed folder carries `.ok/frontmatter.yml` (visible in `exec` listings) and a starter template under `.ok/templates/`. Template resolution walks up the folder tree; see [Folders and templates](/folders-and-templates).

## Document shape

A person dossier instantiated from the `person` template follows this structure:

```markdown
---
type: person
title: Jane Founder
created: 2026-05-12
author: mike
tags: [person]
---

## Compiled truth

Co-founder and CEO of [[companies/jane-co|Jane Co]]. Met through
[[people/alex-seed-investor|Alex Seed Investor]].

--- timeline ---

## Timeline

- **2026-05-12** | [[meetings/2026-05-12-jane-founder-coffee|coffee meeting]] | @mike — Jane described Jane Co's agent-runtime observability wedge. Confidence: direct note.
```

Compatibility rules:

- Document frontmatter carries `title:` and `type:` (`person`, `company`, `concept`, `meeting`, `original`, `transcript`).
- Prefer path-qualified wikilinks when entity identity matters: `[[people/jane-founder|Jane Founder]]`, `[[companies/jane-co|Jane Co]]`.
- Meeting filenames follow `YYYY-MM-DD-<slug>.md`; frontmatter includes `date` and `attendees:` aligned with `people/` dossier slugs.
- Keep `--- timeline ---` as the explicit separator between compiled truth and timeline.

## Create and update dossiers

MCP document writes require a running Hocuspocus server (`ok start`). Instantiate from a folder template:

<RequestExample>

```json
write({
  document: {
    path: "people/jane-founder",
    template: "person",
    summary: "Stub Jane Founder dossier"
  }
})
```

</RequestExample>

<ParamField body="document.path" type="string" required>
Relative path without extension. Parent folder determines which templates are available.
</ParamField>

<ParamField body="document.template" type="string">
Template name from the parent folder's `templates_available` menu (e.g. `person`, `company`, `meeting`). Mutually exclusive with `content`. Substitutes `{{date}}` and `{{user}}` at instantiation.
</ParamField>

<ParamField body="document.position" type="string">
`replace` (default for new docs), `append`, or `prepend`. Required when the document already exists.
</ParamField>

<ParamField body="summary" type="string">
Optional one-line label (≤80 chars) persisted to git history and visible on the document timeline. Avoid secrets or PII.
</ParamField>

For targeted changes on existing dossiers, use `edit({ document: { path, find, replace } })` to rewrite compiled truth or `write({ document: { path, content, position: "append" } })` to append timeline bullets without replacing the file.

<Warning>
`edit` body find/replace operates on document body only. To patch frontmatter keys, use `edit({ document: { path, frontmatter } })`.
</Warning>

## Meeting → dossier extraction loop

<Steps>
<Step title="Capture the meeting">

```json
write({
  document: {
    path: "meetings/2026-05-12-jane-founder-coffee",
    template: "meeting"
  }
})
```

Fill raw notes with path-qualified links to mentioned people, companies, and concepts.

</Step>
<Step title="Extract entities">

Prompt your MCP-capable agent:

```txt
From meetings/2026-05-12-jane-founder-coffee.md, create or update the
referenced person, company, and concept dossiers using the entity-vault
templates. Append dated timeline bullets. Do not rewrite existing timeline
entries or the meeting note.
```

The pack skill routes: stub missing dossiers, append timeline bullets citing the meeting link, update compiled truth only when synthesis changes.

</Step>
<Step title="Review and checkpoint">

Review agent edits in the editor. Before a large batch of dossier updates, save a restore point:

<RequestExample>

```json
checkpoint({ summary: "Post-meeting dossier extraction" })
```

</RequestExample>

<ResponseExample>

```json
{
  "version": "a1b2c3d4e5f6789012345678901234567890abcd",
  "previewUrl": null
}
```

</ResponseExample>

List checkpoints and per-document history with `history({ document: "people/jane-founder", kind: "checkpoint" })`. Restore a single document with `restore_version({ document, version })` using the 40-char SHA from `history`.

</Step>
<Step title="Commit">

Commit Markdown changes to git. The correction loop depends on inspectable, diffable files—not hidden model context.

</Step>
</Steps>

## Link graph maintenance

Entity vaults depend on path-qualified links between dossiers. Use MCP `links` for audits (requires the collaboration server):

| `links` kind | Use in vault maintenance |
| --- | --- |
| `dead` | Find broken wikilinks; fix or remove (monthly cadence in `HEARTBEAT.md`) |
| `orphans` | Surface disconnected dossiers for adoption or intentional standalone marking |
| `hubs` | Identify highly linked concept or person pages |
| `suggest` | Detect prose mentions missing link syntax |

Run combined audits: `links({ kind: ["dead", "orphans"] })`.

## Optional GBrain interop

If you also run [Garry Tan's gbrain](https://github.com/garrytan/gbrain), point it at the same vault after Open Knowledge writes files:

```bash
gbrain import ~/your-ok-vault --no-embed
gbrain embed --stale
gbrain sync --repo ~/your-ok-vault
```

Recommended model:

- Use `gbrain import ... --no-embed` for the first bulk load.
- Run `gbrain embed --stale` after import or no-embed sync.
- Commit OK edits, then `gbrain sync --repo` for incremental refresh.
- Keep OK as the human inspection and correction surface; keep `gbrain` as the indexing and automation engine.

Root files `USER.md`, `SOUL.md`, `ACCESS_POLICY.md`, and `HEARTBEAT.md` align with GBrain-style agent workflows but are useful even without `gbrain` installed.

## Operational cadence

| Cadence | Action |
| --- | --- |
| After each meeting | Drop raw notes into `meetings/<date>-<slug>.md`; link mentioned entities |
| End of day | Agent extracts entities; append timeline bullets to referenced dossiers |
| Weekly | `links({ kind: "dead" })`; triage orphans vs new entities vs typos |
| Monthly | Audit stale dossiers, empty timelines, compiled truth conflicting with recent evidence |
| Before large agent batches | `checkpoint({ summary: "…" })` for a project-wide restore point |
| With `gbrain` | Commit OK edits, then `gbrain sync --repo` and `gbrain embed --stale` |

Append one dated entry to `log.md` per agent turn that creates or restructures vault content. Reference touched docs as markdown links so backlinks surface in `links({ kind: "backlinks" })`.

## Troubleshooting

<AccordionGroup>
<Accordion title="write fails: Hocuspocus server is not running">

Document creation and replacement route through the collaboration server. Start it with `ok start` before MCP writes. Server-free reads (`exec`, `search` in some modes) still work; see [Collaboration server](/collaboration-server).

</Accordion>
<Accordion title="template not found for folder">

Templates resolve by walking up from the document's parent folder. Run `exec("ls people")` to see `templates_available`. Ensure seed completed and the path matches the folder where the template lives.

</Accordion>
<Accordion title="Agent rewrote timeline entries">

Reinforce the pack skill rule: timeline is append-only. Use `history` + `restore_version` to roll back a dossier, or hand-correct in the editor. Checkpoints capture project-wide state before large extractions.

</Accordion>
<Accordion title="Dead links after stubbing entities">

Run `links({ kind: "dead" })`. Triage into new dossier stubs (agent `write` with template), typo fixes (`edit`), or link removal. The monthly heartbeat in `HEARTBEAT.md` expects this audit.

</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Karpathy LLM wiki workflow" href="/karpathy-llm-wiki">
Source-grounded counterpart when you need ingest/research/consolidate over external sources instead of entity dossiers.
</Card>
<Card title="Folders and templates" href="/folders-and-templates">
Folder frontmatter, template resolution, and `write({ document: { template } })` instantiation semantics.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Full signatures for `write`, `edit`, `checkpoint`, `history`, `links`, and `restore_version`.
</Card>
<Card title="Wire agent editors" href="/wire-agent-editors">
Connect MCP-capable agents and verify the `exec` tool against your seeded vault.
</Card>
<Card title="Editor workflows" href="/editor-workflows">
Review agent dossier edits, timeline panel, and human correction in the web editor.
</Card>
</CardGroup>

---

## 21. Troubleshooting

> Diagnose server lock state, run `ok diagnose health` and `ok diagnose bundle`, file bug reports, repair stale skills/MCP configs, and recover from crash leftovers with `ok clean`.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/21-troubleshooting.md
- Generated: 2026-06-25T22:49:23.808Z

### Source Files

- `packages/cli/src/commands/diagnose.ts`
- `packages/cli/src/commands/diagnose-health.ts`
- `packages/cli/src/commands/bug-report.ts`
- `packages/cli/src/commands/clean.ts`
- `packages/cli/src/commands/repair-skills.ts`
- `packages/cli/src/commands/status.ts`
- `packages/cli/src/commands/lock-state.ts`

---
title: "Troubleshooting"
description: "Diagnose server lock state, run `ok diagnose health` and `ok diagnose bundle`, file bug reports, repair stale skills/MCP configs, and recover from crash leftovers with `ok clean`."
---

The `ok` CLI exposes a layered diagnostics stack: lock inspection (`ok status`), environment probes (`ok diagnose health`), project-local support bundles (`ok diagnose bundle`), global bug-report zips (`ok bug-report`), and crash recovery (`ok clean`). Skills and MCP editor entries are refreshed automatically on `ok start`; `ok repair-skills` forces an explicit skill sweep when agents stop seeing bundled guidance.

## Diagnosis workflow

<Steps>
<Step title="Check lock state">

From the project root, inspect server and UI lockfiles:

```bash
ok status
```

Use `--json` for structured output. Each lock resolves to one of five states (see [Lock states](#lock-states)).

</Step>

<Step title="Run health checks">

Probe prerequisites, config, content directory, shadow repo, and macOS signing:

```bash
ok diagnose health
```

Add `--verbose` for per-check detail. Target a single check with `--check server-lock`. Exit code is `0` when no checks fail; warnings do not fail the command.

</Step>

<Step title="Recover stale locks">

If `ok status` reports `stale` or `corrupt`, prune only dead or unparseable lockfiles:

```bash
ok clean
```

`ok clean` never removes locks held by live processes.

</Step>

<Step title="Restart and re-wire">

Stop live servers if needed, then start again (MCP config and skill repair sweeps run automatically):

```bash
ok stop
ok start
```

Force a skill refresh without a full server boot:

```bash
ok repair-skills
```

</Step>

<Step title="Capture a support bundle">

For GitHub issues, generate a redacted global bundle:

```bash
ok bug-report --no-reveal
```

For project-local telemetry and server state, use `ok diagnose bundle` (see [Support bundles](#support-bundles)).

</Step>
</Steps>

## Lock states

Open Knowledge records one lock per project under `.ok/local/` for the collaboration server (`server.lock`) and the editor UI (`ui.lock`). `inspectLock` classifies each file:

| State | Meaning | Typical action |
| --- | --- | --- |
| `missing` | No lock file | Safe to run `ok start` |
| `alive` | PID exists on this host | Server or UI is running; use `ok stop` if you need the port |
| `dead-pid` | Lock references a process that no longer exists | Run `ok clean` |
| `corrupt` | JSON missing or invalid PID | Run `ok clean` |
| `foreign-host` | Lock hostname differs from local machine | Delete the lock file manually when the remote process is confirmed gone |

<RequestExample>

```bash
ok status
```

</RequestExample>

<ResponseExample>

```text
server  alive  pid=48291 port=3847 started=2026-06-25T14:02:11.000Z
ui      not running
```

</ResponseExample>

When `ok status` reports stale or corrupt locks, the rendered text includes an explicit `ok clean` hint. The `server-lock` health check surfaces the same conditions with remediation lines.

<Warning>
`ok clean` only deletes locks in `dead-pid` or `corrupt` state. Live locks are never touched. If `ok start` fails because another process holds the lock, stop that process with `ok stop` (or `ok stop all`) before cleaning.
</Warning>

## `ok diagnose health`

Runs eight named checks in sequence (5 s timeout each):

| Check | Validates |
| --- | --- |
| `git` | `git` on PATH and minimum version |
| `bun` | `bun --version` probe |
| `config-yaml` | `.ok/config.yml` parses and resolves `content.dir` |
| `content-dir` | Content root exists, is a directory, and is writable |
| `server-lock` | Server lock is not held by a live local process |
| `shadow-repo` | Shadow gitdir resolves and has `HEAD` |
| `shadow-health` | Loose objects, packfiles, WIP refs, dead agent chains |
| `macos-codesig` | Desktop app bundle signing (skipped off macOS or in dev mode) |

<ParamField body="--json" type="boolean">
Emit one NDJSON `CheckResult` object per line.
</ParamField>

<ParamField body="--verbose" type="boolean">
Include the `detail` field and probe context in human-readable output.
</ParamField>

<ParamField body="--check" type="string">
Run a single named check. Valid values: `git`, `bun`, `config-yaml`, `content-dir`, `server-lock`, `shadow-repo`, `shadow-health`, `macos-codesig`.
</ParamField>

<ParamField body="--quiet" type="boolean">
Suppress output; return exit code only.
</ParamField>

Each result has `status` (`pass`, `warn`, or `fail`), `summary`, and optional `remediation` and `detail`. Unknown `--check` values exit with code `2`.

<Info>
The `server-lock` check fails (not warns) when a live local process holds the lock — use `ok stop` rather than `ok clean`. Stale and corrupt locks produce warnings with `ok clean` remediation.
</Info>

## Support bundles

Two commands produce zip archives for different audiences. Choose based on what you need to share.

### `ok diagnose bundle`

Project-scoped bundle under `.ok/local/diagnostics/` by default. Collects local telemetry sinks, server logs, lock state, shadow-repo head, and optional live-process diagnostics.

<ParamField body="--pid" type="number">
Include `ok diagnose process <pid>` output under `process/` in the bundle.
</ParamField>

<ParamField body="--out" type="string">
Write the zip to a custom path (parent directory must exist).
</ParamField>

<ParamField body="--yes" type="boolean">
Skip the interactive `Write bundle? [y/N]` prompt.
</ParamField>

<ParamField body="--redact" type="boolean">
Hash `doc.name` telemetry attributes and replace the content-dir prefix with `<CONTENT_DIR>`. Writes a sidecar `.docnames.json` map alongside the zip.
</ParamField>

Default output path:

```text
<contentDir>/.ok/local/diagnostics/bundle-<timestamp>.zip
```

Bundle layout (staged before zip):

| Path | Contents |
| --- | --- |
| `telemetry/spans-current.jsonl` | Current span sink |
| `telemetry/spans-prev.jsonl` | Rotated span sink |
| `logs/server-current.jsonl` | Current server log sink |
| `logs/server-prev.jsonl` | Rotated server log sink |
| `state/server.lock` | Lock metadata when present |
| `state/agent-presence.json` | Live agent presence (server running only) |
| `state/shadow-head.txt` | Recent shadow-repo commits |
| `state/last-spawn-error.log` | Last server spawn failure |
| `state/runtime.json` | OK version, Node, platform, desktop env |
| `state/server-status.txt` | `running` or `not-running (<reason>)` |
| `process/` | Optional process diagnose output (`--pid`) |
| `manifest.json` | Schema version 1 manifest with file inventory |

Local telemetry is controlled by `telemetry.localSink` in project config (see [Configuration reference](/configuration-reference)). If the server is not running, the summary notes that live state is unavailable.

### `ok diagnose process`

Captures deep runtime data for a single hung Node process (typically the collaboration server PID from `ok status`):

```bash
ok diagnose process <pid>
```

<ParamField body="--cpu-profile" type="number" default="15">
CPU profile duration in seconds (Node inspector required).
</ParamField>

<ParamField body="--output" type="string">
Output directory. Defaults to `<contentDir>/.ok/local/diagnostics/process-<pid>-<timestamp>` when the PID matches a known server lock, otherwise `ok-diagnose-<pid>-<timestamp>` in the current directory.
</ParamField>

<ParamField body="--no-inspector" type="boolean">
Collect metadata and `lsof` only; skip CPU profiling.
</ParamField>

<Warning>
Review bundle contents before sharing. `lsof.txt`, `cpu.cpuprofile`, and `stacks.txt` may contain private paths and source file names. `process-stats.jsonl` (CPU/MEM/RSS only) is safer to share.
</Warning>

### `ok bug-report`

Global diagnostic zip written to `~/.ok/bug-reports/<timestamp>-bugreport.zip`. Intended for GitHub issues.

```bash
ok bug-report --no-reveal
```

<ParamField body="--no-reveal" type="boolean">
Skip revealing the bundle in Finder (preferred for agent-driven capture).
</ParamField>

Collects:

| Path | Source |
| --- | --- |
| `logs/` | NDJSON files from `~/.ok/logs/` (filtered to current project when `.ok/config.yml` is present) |
| `lockdir/` | `.ok/local/server.lock`, `last-spawn-error.log` |
| `local-logs/` | `.ok/local/logs/server-current.jsonl`, `server-prev.jsonl` |
| `sysinfo.json` | Platform, Node/Bun versions, OK package version, memory |
| `MANIFEST.json` | File inventory and redaction audit (`disciplineVersion: 1.0.0`) |
| `README.md` | Privacy summary |

All text content passes through auto-redaction (home paths, GitHub PATs, AWS keys, Anthropic/OpenAI tokens, bearer headers, JWTs, URL credentials). The command prints the zip path to stdout and reports redaction counts on stderr when lines were scrubbed.

## Repair skills and MCP configs

Editor integration drifts when CLI versions change or manual edits overwrite bundled entries. Open Knowledge repairs wiring on every `ok start`:

1. **MCP configs** — `repairMcpConfigs` migrates stale entries for Claude Code, Cursor, and Codex (user-global and project-local scopes). Outcomes: `no-entry`, `canonical`, `repaired`, or `write-failed`.
2. **Skills** — `repairSkills` refreshes project skills (`.claude/skills/open-knowledge`, `.cursor/skills/open-knowledge`, `.agents/skills/open-knowledge`) and user-global discovery skills (`~/.agents/skills/open-knowledge-discovery` plus per-host copies).

Force an explicit skill sweep without starting the server:

```bash
ok repair-skills
```

Initial MCP registration happens during `ok init`. If agents still cannot reach Open Knowledge after `ok start`, re-run `ok init` in the project root or verify editor MCP config files contain the `# ok-mcp-v1` sentinel.

<Tip>
Set `OK_RECLAIM_DISABLE=1` to skip automatic MCP, launch.json, and skill repair sweeps during `ok start`. Use this in test environments or when you manage editor configs manually.
</Tip>

## Common failure modes

<AccordionGroup>
<Accordion title="`ok start` says server lock held">

Run `ok status`. If the server is `alive`, another instance is running — use `ok stop` or `ok ps` to identify it. If `stale` or `corrupt`, run `ok clean` then `ok start`.

</Accordion>

<Accordion title="Editor MCP tools do not respond">

1. Confirm the server is alive: `ok status`
2. Run `ok diagnose health --check server-lock`
3. Run `ok start` (triggers MCP config repair) or `ok repair-skills`
4. Re-verify with an `exec` tool prompt (see [Wire agent editors](/wire-agent-editors))

</Accordion>

<Accordion title="Shadow repo or history warnings">

`shadow-repo` and `shadow-health` warnings often clear after `ok start` initializes packing and journal cleanup. Persistent warnings suggest inspecting server logs in `.ok/local/logs/`.

</Accordion>

<Accordion title="macOS desktop app quarantine">

The `macos-codesig` check fails when the app runs translocated from Downloads. Drag Open Knowledge.app to `/Applications/` and relaunch.

</Accordion>

<Accordion title="Spawn errors after crash">

Check `.ok/local/last-spawn-error.log`. Run `ok clean`, then `ok start`. Include `ok bug-report` output when filing an issue.

</Accordion>
</AccordionGroup>

```text
Symptom                    → First command        → If unresolved
─────────────────────────────────────────────────────────────────
Port in use / lock held    → ok status            → ok stop / ok clean
Prereq / config failure    → ok diagnose health   → fix reported check
Agent integration broken   → ok repair-skills     → ok start (MCP repair)
Need logs for GitHub       → ok bug-report        → attach zip to issue
Need project telemetry     → ok diagnose bundle   → --redact if sharing
Hung server process        → ok diagnose process  → include in bundle --pid
```

## Related pages

<CardGroup>
<Card title="CLI reference" href="/cli-reference">
Full `ok` subcommand list including `start`, `stop`, `ps`, `status`, and `clean`.
</Card>
<Card title="Collaboration server" href="/collaboration-server">
Lock lifecycle, server-free MCP reads, and protocol boundaries.
</Card>
<Card title="Wire agent editors" href="/wire-agent-editors">
MCP registration, bundled skills, and `exec` verification prompts.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`telemetry.localSink` and other keys that affect diagnostic collection.
</Card>
</CardGroup>

---

## 22. Contributing

> Public mirror PR flow, `bun install` and `bun run check` gate, package layout, changeset requirements, and local dev commands for app and docs site.

- Page Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/pages/22-contributing.md
- Generated: 2026-06-25T22:48:58.682Z

### Source Files

- `CONTRIBUTING.md`
- `AGENTS.md`
- `package.json`
- `.changeset/config.json`
- `.github/workflows/monorepo-pr-bridge.yml`
- `docs/package.json`
- `.env.example`

---
title: "Contributing"
description: "Public mirror PR flow, `bun install` and `bun run check` gate, package layout, changeset requirements, and local dev commands for app and docs site."
---

Open Knowledge is developed in Inkeep's internal monorepo at `inkeep/agents-private` under `public/open-knowledge/` and mirrored to this public repository with Copybara. Public contributions open pull requests here; the `monorepo-pr-bridge` workflow syncs each PR into the internal tree for review and merge, then Copybara syncs accepted changes back. Local validation before requesting review is `bun install` followed by `bun run check`, which runs Biome and Oxlint linting plus Turbo `build`, `typecheck`, and `test` across workspaces.

## Public mirror PR flow

Review and merge decisions happen in the internal mirror so public and internal development stay on the same history. The public PR is closed (not merged) once the change lands via mirror sync.

```mermaid
sequenceDiagram
    participant Contributor
    participant PublicRepo as inkeep/open-knowledge
    participant Bridge as monorepo-pr-bridge
    participant Internal as agents-private/public/open-knowledge
    participant Copybara
    participant Release as release.yml

    Contributor->>PublicRepo: Open PR
    PublicRepo->>Bridge: pull_request_target event
    Bridge->>Internal: Sync branch public-pr/open-knowledge-{N}
    Note over Internal: Maintainer review + merge
    Internal->>Copybara: Accepted change
    Copybara->>PublicRepo: Sync to main (bot commit)
    PublicRepo->>Contributor: Close PR (not merge)
    opt Behavior-changing PR with changeset
        Copybara->>Release: Mirror .changeset/** change
        Release->>Release: Beta publish to npm + DMG
    end
```

<Steps>
<Step title="Fork and branch">

Clone the public repository, create a focused branch, and keep changes scoped to one concern.

</Step>

<Step title="Open a pull request">

Open the PR against `main` on `inkeep/open-knowledge` (or your fork targeting that base). The bridge workflow skips PRs whose head ref is `copybara/sync` — those originate from the internal sync path.

</Step>

<Step title="Wait for mirror automation">

Within about one minute, a bot posts a sticky comment on your PR indicating that an internal mirror PR has been opened. The link points to a private repository and is not accessible to external contributors; that is expected.

</Step>

<Step title="Address feedback and land the change">

Maintainers review in the internal mirror. Once merged internally, Copybara syncs the change to public `main` and your PR is closed automatically. Your authorship is preserved in PR history and the internal commit even though the synced commit on `main` is attributed to the sync bot.

</Step>
</Steps>

### What to expect on your PR

| Signal | Where it runs | What you see |
| --- | --- | --- |
| Mirror notification | Public PR | Sticky bot comment with internal PR link (private) |
| LLM code review | Public PR via `claude-code-review.yml` | Automated review comments from Claude on non-draft PRs |
| Lint, typecheck, tests | Internal mirror after bridge | Results are **not** surfaced back to the public PR |
| Maintainer review | Internal mirror | Reviewer comments are **not** auto-mirrored to your PR |

<Warning>

If you do not hear back within a few business days, comment on your public PR to nudge maintainers. That is the right coordination channel — internal review threads are not mirrored automatically.

</Warning>

### Path mapping and export boundary

The bridge rewrites four root-level overlay files when syncing into the monorepo:

| Public path | Internal overlay path |
| --- | --- |
| `README.md` | `copybara/public-open-knowledge-overlay/README.md` |
| `CONTRIBUTING.md` | `copybara/public-open-knowledge-overlay/CONTRIBUTING.md` |
| `AGENTS.md` | `copybara/public-open-knowledge-overlay/AGENTS.md` |
| `.gitignore` | `copybara/public-open-knowledge-overlay/.gitignore` |

All other in-tree paths map under `public/open-knowledge/` via the `MONOREPO_PATH_PREFIX`. The bridge excludes internal-only paths such as `.codex/`, `.claude/`, `.cursor/`, `specs/`, `reports/`, `projects/`, `stories/`, `strategy/`, and `private/`.

<Info>

Only source code, public docs, and build or development scripts are exported to this mirror. Internal planning notes, reports, specs, and agent workspace files are intentionally absent.

</Info>

### Maintainer iteration and force-push

Maintainers may push fixes directly to your fork branch when you have enabled **Allow edits from maintainers**. Coordinate via the public PR thread before force-pushing your branch if a maintainer has been actively iterating — force-push can discard their commits.

## Development setup

A fresh clone requires no `.env` file. Install dependencies and run the public PR quality gate:

<CodeGroup>

```bash title="Install and verify"
bun install
bun run check
```

```bash title="Pin toolchain (optional)"
fnm install          # reads .node-version (24)
mise install         # reads .node-version
volta install node@24
```

</CodeGroup>

### Toolchain requirements

| Requirement | Source | Version |
| --- | --- | --- |
| Bun | `package.json` `packageManager`, `.bun-version` | `1.3.13+` |
| Node.js | `package.json` `engines`, `.node-version` | `24+` |

<Note>

If you are on a different Node version, `bun install` may warn with `EBADENGINE`. Install usually succeeds, but tests and builds may fail — pin Node 24+ before reporting toolchain issues.

</Note>

## Package layout

The root workspace includes `packages/*` and `docs`. Turbo orchestrates `build`, `typecheck`, and `test` with upstream dependency ordering.

:::files
open-knowledge/
├── packages/
│   ├── app/          @inkeep/open-knowledge-app     Web editor (Vite)
│   ├── cli/          @inkeep/open-knowledge         npm CLI (`ok`, `open-knowledge`)
│   ├── core/         @inkeep/open-knowledge-core    Shared domain logic
│   ├── desktop/      @inkeep/open-knowledge-desktop Electron macOS app
│   ├── plugin/       @inkeep/open-knowledge-plugin  Agent integration helpers
│   └── server/       @inkeep/open-knowledge-server  Hocuspocus collaboration server
├── docs/             @inkeep/open-knowledge-docs    Fumadocs documentation site
├── .changeset/       Version and changelog queue
└── .github/          Bridge, release, and review workflows
:::

| Package | npm name | Role |
| --- | --- | --- |
| `packages/cli` | `@inkeep/open-knowledge` | Published CLI entrypoint (`ok`) |
| `packages/core` | `@inkeep/open-knowledge-core` | Shared schemas, config, domain logic |
| `packages/server` | `@inkeep/open-knowledge-server` | Collaboration server and MCP surface |
| `packages/app` | `@inkeep/open-knowledge-app` | Web editor UI |
| `packages/desktop` | `@inkeep/open-knowledge-desktop` | macOS desktop shell |
| `packages/plugin` | `@inkeep/open-knowledge-plugin` | Agent integration package |
| `docs` | `@inkeep/open-knowledge-docs` | Public docs site (private workspace) |

Top-level overlay docs (`README.md`, `CONTRIBUTING.md`, `AGENTS.md`) are public-safe standalone files maintained for the mirror; keep them free of secrets, internal paths, and private customer context.

## Quality gate commands

`bun run check` is the public PR gate. It expands to root linting plus Turbo `build`, `typecheck`, and `test`.

| Command | What it runs |
| --- | --- |
| `bun run check` | `lint` → Turbo `build` + `typecheck` + `test` |
| `bun run lint` | Biome check + Oxlint (`--max-warnings 0`) |
| `bun run format` | Biome write (unsafe) |
| `bun run typecheck` | Turbo `typecheck` across workspaces |
| `bun run test` | Turbo `test` across workspaces |
| `bun run build` | Turbo `build` across workspaces |
| `bun run notices` | Regenerate `THIRD_PARTY_NOTICES.md` |
| `bun run changeset` | Interactive changeset authoring |

For targeted work, run package-scoped commands from the package directory:

```bash
cd packages/app
bun run test
```

<Tip>

For UI or editor changes, run `bun run check` at the root and also run the affected package tests from `packages/app` before requesting review.

</Tip>

Husky and `lint-staged` format staged `packages/**` and `docs/**` files with Biome on commit when hooks are installed via `bun install` (`prepare` script).

## Local development surfaces

<Tabs>
<Tab title="Web editor">

```bash
bun run --filter @inkeep/open-knowledge-app dev
```

The Vite dev server starts after building `@inkeep/open-knowledge-core` and `@inkeep/open-knowledge-server`. Default Vite port is `5173`; override with optional `VITE_PORT` in `.env` (see `.env.example`).

</Tab>

<Tab title="Docs site">

```bash
cd docs
bun run dev
```

The Fumadocs site serves on port `3010`. `postinstall` runs `fumadocs-mdx`; `prebuild` validates links via `bun validate-link`.

</Tab>

<Tab title="Desktop app">

```bash
cd packages/desktop
bun run dev
```

Runs Electron via `electron-vite dev --watch` with upstream Turbo builds.

</Tab>
</Tabs>

### Optional environment variables

All variables are optional. Copy `.env.example` to `.env` only when you need observability or a custom dev port:

<ParamField body="OTEL_SDK_DISABLED" type="boolean">
Set to `false` to enable server-side OpenTelemetry instrumentation.
</ParamField>

<ParamField body="VITE_OTEL_ENABLED" type="boolean">
Set to `true` to enable frontend (Vite) OpenTelemetry instrumentation.
</ParamField>

<ParamField body="OTEL_EXPORTER_OTLP_ENDPOINT" type="string">
OTLP endpoint for traces and metrics. Default: `http://localhost:4318`.
</ParamField>

<ParamField body="VITE_PORT" type="number">
Custom port for the Vite dev server (`packages/app`). Does not affect `ok start` / `ok ui` port resolution.
</ParamField>

## Changesets

Every behavior-changing pull request ships a `.changeset/<descriptive-kebab-slug>.md` file. The body becomes the user-facing entry on the next beta GitHub Release and aggregated stable release notes — how npm consumers and DMG auto-update users learn what changed.

<Check>

Skip changesets for docs-only, test-only, or CI-only edits that do not change runtime behavior.

</Check>

### Authoring

```bash
bun run changeset
```

The interactive CLI writes well-formed frontmatter. You can also hand-write a file, but prefer the CLI.

<RequestExample>

```md title=".changeset/fix-search-ranking.md"
---
"@inkeep/open-knowledge": patch
---

Fix: `ok search` now ranks exact title matches above partial body matches when semantic search is disabled.
```

</RequestExample>

### Pre-1.0 semver convention

While Open Knowledge is below `1.0.0`, bump types follow a **shift-down** convention. **Never declare `major` in a changeset** — a single `major` on any fixed-group package would pull the entire group to `1.0.0`.

| Consumer impact | Pre-1.0 bump type |
| --- | --- |
| Breaking change (rename, removal, schema change) | `minor` |
| New feature or additive API surface | `minor` |
| Bug fix or internal change with no user-visible API impact | `patch` |

### Fixed-group lock-step

These packages bump together on the highest declared bump type across queued changesets:

- `@inkeep/open-knowledge`
- `@inkeep/open-knowledge-core`
- `@inkeep/open-knowledge-server`
- `@inkeep/open-knowledge-app`
- `@inkeep/open-knowledge-desktop`

Do not write inline sibling-package version numbers in changeset bodies — lock-step versions are computed at release time.

### Release cadence

Merging an internal PR that includes a changeset triggers Copybara to mirror `.changeset/**` changes. The `release.yml` workflow on the public mirror then publishes a beta to npm (`@inkeep/open-knowledge@X.Y.Z-beta.N` with `--tag beta`) and dispatches desktop DMG builds within minutes.

Changeset body style:

- Lead with a user-visible verb
- Name the affected command or surface in a code span
- Show before/after when relevant
- Skip internal spec IDs or story numbers

## Contribution guidelines

- Keep pull requests focused and small enough to review.
- Include tests or a clear manual verification note for behavior changes.
- Run `bun run check` before requesting review.
- Commit `bun.lock` when dependency changes require it.
- Run `bun run notices` and include `THIRD_PARTY_NOTICES.md` changes when dependencies affect third-party notices.
- Do not include secrets, credentials, customer data, local machine paths, or generated debug artifacts.
- Keep overlay docs (`README.md`, `CONTRIBUTING.md`, `AGENTS.md`) public-safe and standalone.

## License

Open Knowledge is licensed under the [GNU General Public License v3.0 or later](https://github.com/inkeep/open-knowledge/blob/main/LICENSE) (`GPL-3.0-or-later`). By submitting a contribution, you agree it is licensed under the same terms.

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Toolchain prerequisites for contributors: Node.js 24+, Bun 1.3.13+, and install paths for the npm CLI and macOS desktop app.
</Card>
<Card title="Overview" href="/overview">
Monorepo packages, runtime surfaces (desktop app, `ok` CLI web editor, MCP server), and the shortest path from install to first agent-driven edit.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Diagnose server lock state, run health diagnostics, repair stale skills/MCP configs, and recover from crash leftovers.
</Card>
</CardGroup>

---
