# okf-cli > Command line and MCP server for working with Open Knowledge Format (OKF) catalogues. This is the documentation of the CLI, not of the OKF specification. # Command line and MCP server for OKF catalogues Source: https://rierek.github.io/okf-cli/ {/* Facts: README intro and FACTS §okf find (arms, --where), §okf check, §okf ctx show, §okf rename, §Topic: --json envelope, §Topic: MCP server. Output in the windows is captured by site/scripts/capture-demo.mjs (just docs-capture; the semantic window needs VOYAGE_API_KEY). The specification statement follows Q01 (closed 2026-10-18). */} okf-cli is the command line for catalogues of Open Knowledge Format (OKF) v0.2 bundles: Markdown files with YAML frontmatter. Humans read them, agents query them, scripts check them.
provenance names the arm of every hit.' }, ]} >
okf find searches every bundle under the catalogue root. Two bundles answer here.' }, { id: 'one', label: 'One bundle', icon: Folder, caption: 'One --where filter narrows the answer to a bundle, a type, a tag or a date. Every arm applies it before it ranks.' }, ]} >
A catalogue is a folder of Markdown files. Read it, edit it, diff it like code. `okf install` registers an MCP server and a skill. `okf prime` onboards a fresh session in one call. Every command answers `--json` with one envelope, and every failure has an exit code that names its class. `--root` points one binary at any catalogue, so private, project and shared bundles stay apart. okf-cli follows the OKF specification and is adapted when it changes. --- # okf in brief: CLI and MCP server for OKF catalogues Source: https://rierek.github.io/okf-cli/overview/ {/* Facts: README intro; FACTS §Topic: catalogue, bundle, concept and type model; §Topic: install; §Topic: MCP server and `OKF_BIN`; §Section index of commands. Specification statement per Q01 (PO 2026-10-18). */} **okf is one binary that searches, checks and edits a catalogue of OKF v0.2 bundles, so you can pick the chapter that matches your task: install it, try it, or connect an agent.** okf follows the Open Knowledge Format specification and is adapted when the specification changes. It is bound to OKF v0.2: the v0.2 rules run only in bundles whose root index declares `okf_version: "0.2"`. No release date is promised for a new specification version. ## What okf is - A single static Go binary named `okf`. It needs no Python and no `uv` at run time. `uv` is needed only by the MCP server that `okf install` registers. - It works on a *catalogue*: a directory whose subdirectories are OKF *bundles* of Markdown files with YAML frontmatter. - Four groups of commands, as in `okf --help`: navigation, search, tags and maintenance. ## What you can do ## Where to go next --- # Agent onboarding: MCP server, skill and okf prime Source: https://rierek.github.io/okf-cli/overview/agent-onboarding/ {/* Facts: FACTS §Topic: install, §Topic: MCP server and `OKF_BIN`, §Topic: agent skill, §okf prime, §Defect list h and j. Runs measured with okf 0.2.5 in a throwaway HOME and a copy of the golden catalogue. */} **After this page your agent calls okf through MCP tools or the shell: one `okf install` registers the skill and the MCP server, and `okf prime` teaches the agent the catalogue in one call.** This page covers the MCP server for a markdown knowledge base, the Claude Code setup, `okf prime` and the agent skill. Output below comes from okf 0.2.5. ## One command: okf install `okf install` registers okf with every agent and shell it finds on the machine. It needs no catalogue. Without flags it prints a plan and changes nothing. 1. Preview the plan. ```bash title="Terminal" okf install ``` 2. Read the plan. A line marked `BLOCKED` stops the apply and makes the command exit 1, even for a plan. 3. Apply it. ```bash title="Terminal" okf install --apply ``` 4. Open a new shell and restart your agents. A second `--apply` changes nothing. In a throwaway home directory: ```text title="okf install --apply --only opencode,kimi,hermes (second run)" $ okf install --apply --only opencode,kimi,hermes | tail -3 unchanged hermes mcp_servers.okf in ~/.hermes/config.yaml nothing to do: already up to date [exit 0] ``` `--only` narrows the run to a comma-separated list of agents (`claude`, `omp`, `codex`, `opencode`, `kimi`, `openclaw`, `hermes`) and shells (`zsh`, `bash`, `fish`). `--uninstall --apply` removes what install created. Details: [`okf install`](/okf-cli/reference/commands/okf-install/). An agent counts as detected when its binary is on PATH or its configuration directory exists. A lone release binary installs shell completion only. The skill and the MCP server script live in `skills/okf` and `mcp/okf_mcp.py` beside the binary, so install from a Homebrew keg or a clone. Otherwise the plan says `BLOCKED claude source not found: …/skills/okf` and exits 1 (known issue `fs8c`, see [known limitations](/okf-cli/known-limitations/)). The MCP server runs as `uv run --with 'mcp<2' …/okf_mcp.py`. When `uv` is not on PATH, every MCP step is `BLOCKED uv is not on PATH; install it (https://docs.astral.sh/uv/), then re-run`. The skill steps still apply. The `okf` binary itself never needs `uv`. ## MCP server The server is a thin wrapper: each tool runs the `okf` binary with `--json` and returns the `data` and `warnings` of the envelope. Correctness stays in the binary. - 21 tools. Every tool takes `root` (catalogue path; default: search upward from the server's working directory, then `~/.config/okf/config.toml`) and `use_global`. Passing both fails with `invalid_input`. - Binary lookup order: `$OKF_BIN`, then `bin/okf` or `go/bin/okf` next to `mcp/`, then `okf` on PATH. - A result longer than `OKF_MCP_MAX_CHARS` (default `120000`) is an error that asks the caller to narrow the call. - Not exposed as tools: `verify`, `migrate`, `archive`, `merge-tags`, `normalize-tags`, `remove-tags`, `registry convert`, `fix-*`, `rewrite-path`, `visualize`, `recent` and the `ctx` write verbs. - `okf_reindex` has no `check` argument, so the tool writes. This comes from the tool signature in `mcp/okf_mcp.py`; no MCP call was run. Where `okf install` puts the entry: The plan runs the Claude Code CLI: ```text title="okf install --only claude (plan line)" create claude run: claude mcp add --scope user --transport stdio okf -- uv run --with 'mcp<2' /mcp/okf_mcp.py ``` The skill goes to `~/.claude/skills/okf`. Without `claude` on PATH, install merges `mcpServers` into `~/.claude.json`. ```text title="okf install --only codex (plan line)" create codex run: codex mcp add okf -- uv run --with 'mcp<2' /mcp/okf_mcp.py ``` The skill goes to the shared `~/.agents/skills/okf`. `mcpServers.okf` in `~/.omp/agent/mcp.json`: ```json title="~/.omp/agent/mcp.json" { "mcpServers": { "okf": { "type": "stdio", "command": "uv", "args": ["run", "--with", "mcp<2", "/mcp/okf_mcp.py"], "env": {} } } } ``` The skill goes to the shared `~/.agents/skills/okf`. `mcp.okf` in `~/.config/opencode/opencode.json` (written in a throwaway home with a stub `uv` on PATH): ```json title="~/.config/opencode/opencode.json" { "$schema": "https://opencode.ai/config.json", "mcp": { "okf": { "type": "local", "command": ["uv", "run", "--with", "mcp<2", "/mcp/okf_mcp.py"], "enabled": true, "timeout": 30000 } } } ``` `mcpServers.okf` in `~/.kimi-code/mcp.json` (written in a throwaway home): ```json title="~/.kimi-code/mcp.json" { "mcpServers": { "okf": { "command": "uv", "args": ["run", "--with", "mcp<2", "/mcp/okf_mcp.py"] } } } ``` Install runs `openclaw mcp set okf …` and copies the skill to `~/.openclaw/skills/okf`. The entry itself was not checked: the `openclaw` CLI was not available. A managed block in `~/.hermes/config.yaml` (written in a throwaway home): ```yaml title="~/.hermes/config.yaml" # >>> okf-cli >>> mcp_servers: okf: command: "uv" args: ["run", "--with", "mcp<2", "/mcp/okf_mcp.py"] # <<< okf-cli <<< ``` The skill goes to `~/.hermes/skills/okf`. `` is the package root: the Homebrew prefix or the clone. To register the server by hand, use the same `uv run --with 'mcp<2' /okf_mcp.py` command in your agent's MCP configuration. ## Skill The `okf` skill in `skills/okf` teaches an agent the commands and the OKF v0.2 rules. `SKILL.md` says it operates catalogues with the command line tool or its `okf_*` MCP tools. It alone is enough for search only. Flags, previews and provenance rules live in its `references/` files (context bundles, create a bundle, maintain a catalogue, migrate to v0.2, write concepts). `okf install` copies the skill from a package, or links it from a source tree: | Agent | Skill directory | |---|---| | Claude Code | `~/.claude/skills/okf` | | Codex, omp, opencode, Kimi Code | `~/.agents/skills/okf` (shared) | | Hermes | `~/.hermes/skills/okf` | | OpenClaw | `~/.openclaw/skills/okf` | `--uninstall` keeps the shared directory while another agent still reads it. ## okf prime `okf prime` is the first call of an agent session. One call prints the usage guide, the command directory by purpose, and the state of the catalogue: root, bundle and concept counts, whether the search indexes exist, the tag vocabulary and the context bundles. ```text title="okf --root prime --compact (excerpt)" ## This catalogue root: bundles: 4 · concepts: 24 fts index: absent — `find` degrades to a substring scan; run `build-index` semantic index: absent — `find` runs its lexical arm only tag vocabulary: closed — 11 tags in TAGS.md; check a new one with `tags --validate`, adopt it with `tags --register` ctx bundles: 3 — cooking · legacy · onboarding — `ctx show ` lists one root docs: SPEC.md · TYPES.md · PROPERTIES.md · TAGS.md ## Bundles - _ctx (3 concepts) - handbook (10 concepts) - kc-gov (2 concepts) - recipes (9 concepts) ``` - `--compact` lists bundles as name and count. `--full` adds types and tags per bundle. `--check-semantic` runs the exact semantic coverage check instead of only reporting whether the index exists. - `--json` prints an envelope whose `data` holds `guide`, `commands`, `find`, `state` and `bundles`. Over MCP the same call is `okf_prime`. - When the semantic index is partial, the state text names `cli/embeddings/build_tagvec.py`, which a Go install does not ship (known issue `erpf`). Ignore the script name; `okf build-tagvec --produce` is the Go command. ## llms.txt and llms-full.txt The site publishes two plain-text files for agents that read the web: - [llms.txt](https://rierek.github.io/okf-cli/llms.txt): an index of all pages with title, URL and description. - [llms-full.txt](https://rierek.github.io/okf-cli/llms-full.txt): the text of every page in one file. ## Reference --- # OKF concepts: catalogue, bundle, concept and frontmatter Source: https://rierek.github.io/okf-cli/overview/concepts/ ## OKF concepts: catalogue, bundle, concept and frontmatter **After this page you can tell a catalogue from a bundle from a concept, and you know which frontmatter fields okf reads.** Terms are defined once in the [glossary](/okf-cli/reference/glossary/). Examples come from the test catalogue of the okf repository, measured with okf 0.2.5. ## Catalogue, bundle, concept ```mermaid flowchart TD C["Catalogue (root directory)"] --> B1["Bundle: handbook"] C --> B2["Bundle: recipes"] C --> B3["Bundle: _ctx"] C --> F["TYPES.md, PROPERTIES.md, TAGS.md, .okf.toml"] B1 --> K1["Concept: ops/deploy"] B1 --> K2["Concept: glossary"] ``` - A **catalogue** is a directory: the root. okf finds it by `.okf.toml`, or by `SPEC.md` together with `TYPES.md`. - A **bundle** is an immediate subdirectory of the root. Names that start with `.` or `_` are not bundles. `_ctx/` is the one exception: it holds the [context bundles](/okf-cli/features/context-bundles/). `_archive/` holds archived concepts and stays hidden from search and list unless you name it. - A **concept** is a `.md` file in a bundle with YAML frontmatter. Its concept-id is its path below the bundle, without `.md`. The `path` in okf output is `/`. ```text title="A catalogue on disk" catalogue/ ├── .okf.toml ├── SPEC.md ├── TYPES.md ├── PROPERTIES.md ├── TAGS.md ├── handbook/ │ ├── index.md │ ├── glossary.md │ └── ops/ │ └── deploy.md ├── recipes/ └── _ctx/ └── onboarding.md ``` `handbook/ops/deploy.md` is the concept with bundle `handbook`, concept-id `ops/deploy` and path `handbook/ops/deploy`. Four file names are reserved and are not concepts: `index.md`, `log.md`, `readme.md` and `index-map.md`. They never carry `type`. Files that are not Markdown belong under `assets/`. ## Specification-first :::note[okf follows the specification] okf implements the Open Knowledge Format (OKF) specification. okf-cli follows the specification and is adapted when it changes. This page promises no release date and no release for each specification version. ::: The `okf_version` key in the `index.md` at the root of a bundle selects the rules. The test catalogue declares `okf_version: "0.2"`. The normative text is the [OKF specification](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md); [okf.md](https://okf.md/) is a community guide. ## Types and frontmatter Every concept starts with a YAML frontmatter block. `type` is the one required field. The catalogue lists the valid types in `TYPES.md` and the properties in `PROPERTIES.md`. `okf info` prints both. ```text title="handbook/ops/incident-2026.md" --- type: Note title: "Incident 2026" description: A past incident tags: - incident stale_after: 2026-01-05T00:00:00Z --- # Incident 2026 ## Timeline Short timeline. ``` | Field | Meaning | |---|---| | `type` | Required, not empty, registered in `TYPES.md`. | | `title`, `description` | Display title and one-line summary. | | `tags` | Tags registered in `TAGS.md`, in block-list form. | | `status` | `draft`, `stable` or `deprecated`. No `status` means stable. | | `generated` | Provenance of the content: `by`, `at`. `by` must not be empty. | | `sources` | List of sources, each with a `resource`. | | `verified` | List of review entries `{by, at}`. `okf verify` writes them. | | `stale_after` | Instant after which the concept counts as stale. | | `superseded_by` | Replacement concept. For Decision concepts only. | The full field list with the check rule of each field is in [Frontmatter and types](/okf-cli/reference/frontmatter-and-types/). ## Provenance and freshness OKF v0.2 adds four provenance fields: `generated`, `sources`, `verified` and `stale_after`. - `okf check` reports problems. A `stale_after` in the past is a warning: `okf/lifecycle/stale-after`. A `generated` without `by` is an error: `okf/trust/generated`. - `okf verify` records a review in `verified`. The reviewer is `human:`, `process:` or `/`. - The trust tier follows from `verified` only: `unverified` (no entry with a `by`), `human-reviewed` (at least one `human:`), otherwise `machine-confirmed`. The `--json` output of `list`, `find`, `read` and `recent` shows `tier` and `stale`. The v0.2 rules apply in bundles whose root `index.md` declares `okf_version: "0.2"`. Without that key the v0.2 rule `okf/trust/generated` does not fire; see [Conformance checks](/okf-cli/features/conformance-checks/). ## Context bundles, tags, canon blocks - **Context bundle.** A file `_ctx/.md` with `type: Context`. It lists concepts in reading order, each one *required* or *reference*. See [Context bundles](/okf-cli/features/context-bundles/). - **Tags.** `TAGS.md` is the tag registry. `okf check` warns about a tag that is not registered. See [Tags and registry](/okf-cli/features/tags-and-registry/). - **Canon block.** A marked block in a concept that mirrors text owned by one other concept. `okf sync-*` keeps the copies equal. See [Canon blocks](/okf-cli/features/canon-blocks/). ## Search and index artefacts okf writes two optional caches inside the catalogue: - `.fts/fts.sqlite`: the lexical (BM25) index. `okf build-index` writes it. It holds title, tags and body of each concept. - `.tagvec/tag_vectors.sqlite`: tag and concept embeddings for the semantic arm. `okf build-semantic-index` writes it. Embeddings come from Voyage (needs `VOYAGE_API_KEY`) or from Ollama with `bge-m3` on `localhost:11434`. Neither file is part of your content. In a git repository both show as untracked. See [Search](/okf-cli/features/search/). ## Reference - [Glossary](/okf-cli/reference/glossary/) - [Frontmatter and types](/okf-cli/reference/frontmatter-and-types/) - [First steps](/okf-cli/overview/first-steps/): run the commands on a test catalogue --- # Getting started with okf: search a Markdown knowledge base Source: https://rierek.github.io/okf-cli/overview/first-steps/ ## Getting started with okf: search a Markdown knowledge base **After these five steps you have found a concept with `okf find`, read it with `okf read` and run a conformance check with `okf check`.** The output below comes from okf 0.2.5. It was run against a throwaway copy of the test catalogue of the okf repository (`tests/golden/catalogue`). Use that copy, or your own catalogue, so that you never run your first experiments on live content. Install okf first: see [Installation](/okf-cli/overview/installation/). 1. **Point okf at a catalogue.** okf needs one catalogue root. It finds it in this order: 1. `--global`: the `root` key of `~/.config/okf/config.toml`. 2. `--root PATH`. 3. The nearest catalogue above the working directory. A directory counts as a catalogue when it holds `.okf.toml`, or `SPEC.md` together with `TYPES.md`. 4. The `root` key of the user config. Copy the test catalogue and change into it. The copy holds `.okf.toml`, so okf finds it from there: ```bash title="Make a throwaway catalogue" cp -R tests/golden/catalogue /tmp/okf-try && cd /tmp/okf-try ``` Details: [Configuration](/okf-cli/reference/configuration/). 2. **List the bundles with `okf index`.** A bundle is a top-level directory of the catalogue. `okf index` prints one line per bundle. ```text title="okf index" $ okf index _ctx: Curated, ordered reading lists per topic — one file per ctx slug, managed by `okf ctx`. handbook: Welcome to the handbook. kc-gov: recipes: [exit 0] ``` Three bundles hold concepts. `_ctx` holds [context bundles](/okf-cli/features/context-bundles/). Bundles with no description print an empty text after the colon. 3. **Search with `okf build-index` and `okf find`.** Ask first, without an index. `find` falls back to a substring scan and says so on stderr: ```text title="okf find without an index" $ okf find bread --limit 2 note: lexical index absent — falling back to substring scan; run `okf build-index` for BM25 ranking ------ rank: 1 title: Bread type: Recipe path: recipes/bread description: Bread with a long rise body-snippet: # Bread ## Steps Mix, rise, bake. Related: [pasta](pasta.md).… provenance: lexical ------ rank: 2 title: cooking type: Context path: _ctx/cooking description: Cooking basics body-snippet: …comment | |---|---|---|---| | 1 | pflicht | [bread](/okf-cli/recipes/bread.md) | | | 2 | pflicht | [pasta](/okf-cli/recipes/pasta.md) | | | 3 | pflicht |… provenance: lexical ------ [exit 0] ``` Build the FTS5 index once. It lands in `.fts/fts.sqlite` inside the catalogue. Then ask again for BM25 ranking: ```text title="okf build-index, then okf find" $ okf build-index indexed 25 concept(s) -> /.fts/fts.sqlite [exit 0] $ okf find bread --limit 2 note: semantic arm skipped (.tagvec cache/concept embeddings absent) — lexical results only ------ rank: 1 title: Bread type: Recipe path: recipes/bread description: Bread with a long rise body-snippet: # Bread ## Steps Mix, rise, bake. Related: [pasta](pasta.md).… provenance: lexical ------ rank: 2 title: Recipes overview type: Concept path: recipes/overview description: Overview of the recipes body-snippet: # Overview See [pasta](pasta.md), [bread](/okf-cli/recipes/bread.md), [soup](soup.md) and [[salad]].… provenance: lexical ------ [exit 0] ``` Semantic search is optional. See [Markdown knowledge base search](/okf-cli/features/search/). 4. **Read one concept with `okf read`.** Pass the `path` value from the search result: ```text title="okf read recipes/bread" $ okf read recipes/bread --- type: Recipe title: Bread description: Bread with a long rise tags: - food - bread timestamp: 2025-12-02T08:00:00Z status: todo --- # Bread ## Steps Mix, rise, bake. Related: [pasta](pasta.md). [exit 0] ``` 5. **Check the catalogue with `okf check`.** `check` scans against the format rules. Name a bundle to narrow the scan. The test catalogue breaks rules on purpose, so the exit code is 4: ```text title="okf check recipes" $ okf check recipes ERROR okf/type/reserved-no-type recipes/readme.md (reserved file must not carry `type`) ERROR kc/supersede/resolves recipes/salad.md (superseded_by target does not resolve: recipes/soup) ERROR kc/asset/placement recipes/shopping.txt (non-md file outside assets/) ERROR kc/supersede/resolves recipes/stew.md (superseded_by target does not resolve: handbook/ops/deploy) WARNING kc/link/crosslink-format recipes/bread.md (non catalogue-absolute link(s) (use /bundle/…): pasta.md) WARNING kc/link/crosslink-format recipes/drafts/ideas.md (non catalogue-absolute link(s) (use /bundle/…): pie.md) WARNING kc/link/crosslink-format recipes/overview.md (non catalogue-absolute link(s) (use /bundle/…): pasta.md, soup.md) WARNING kc/frontmatter/tags-block-form recipes/overview.md (tags field uses inline form — local convention requires YAML block-list) WARNING kc/link/crosslink-format recipes/salad.md (non catalogue-absolute link(s) (use /bundle/…): soup.md) WARNING kc/supersede/scope recipes/salad.md (superseded_by used on non-Decision concept (type=Recipe)) WARNING kc/frontmatter/tags-block-form recipes/soup.md (tags field uses inline form — local convention requires YAML block-list) WARNING kc/markup/xml-like-tags recipes/soup.md (angle-bracket placeholder(s) not in code-fence: ) WARNING kc/tag/registered recipes/stew.md (tag `misc` not registered in TAGS.md) WARNING kc/frontmatter/english-keys recipes/stew.md (German frontmatter key(s): beschreibung, titel) WARNING kc/supersede/scope recipes/stew.md (superseded_by used on non-Decision concept (type=Recipe)) 4 VIOLATIONS [exit 4] ``` The summary counts the `ERROR` lines. Warnings print but do not count, unless you add `--strict`. Exit codes: [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/). ## Where options go `--root` and `--global` are options of `okf` itself. Put them before the command. You can then run okf from any directory: ```text title="--root before the command" $ cd /tmp $ okf --root /tmp/okf-try index _ctx: Curated, ordered reading lists per topic — one file per ctx slug, managed by `okf ctx`. handbook: Welcome to the handbook. kc-gov: recipes: [exit 0] ``` Without `--root`, a directory with no catalogue above it and no `root` in the user config fails: ```text title="No catalogue found" $ okf index ERROR: no catalogue found in cwd or its ancestors and no root in ~/.config/okf/config.toml; pass --root [exit 3] ``` `--json` is not global. Each command takes its own `--json` flag, after the command name: `okf check --json`. ## Result You have a catalogue root that okf resolves, a lexical index in `.fts/fts.sqlite`, and one real run each of `index`, `find`, `read` and `check`. ## Go on --- # Install okf CLI: Homebrew, release binary or source build Source: https://rierek.github.io/okf-cli/overview/installation/ **After this page, `okf --help` runs on your machine.** `okf` is one binary written in Go. It needs no Python and no `uv`. It runs on macOS and Linux, on `amd64` and `arm64`. Only the MCP server that `okf install` registers needs `uv`. Pick one channel: ```bash title="Terminal" brew install okf-cli ``` The formula builds `okf` from source with `just go-build`. `go` and `just` are build dependencies. `uv` is a runtime dependency for the MCP server only. The keg also holds the agent skill and the MCP server script. :::caution The formula in the repository declares only `head`: it has no `url`, `version` or `sha256`. With that copy a plain `brew install okf-cli` has no stable version. Use `brew install --HEAD okf-cli`. Whether the tap copy differs is not visible from the repository. See [71rn] in [Known limitations](/okf-cli/known-limitations/). ::: A release carries one binary per platform: `okf-darwin-arm64`, `okf-darwin-amd64`, `okf-linux-amd64` and `okf-linux-arm64`. It also carries a wheel, an sdist and the provenance file `multiple.intoto.jsonl`. Replace `` with the release tag and pick your platform. ```bash title="Terminal" mkdir -p ~/.local/bin curl -fsSL -o ~/.local/bin/okf \ https://github.com/rierek/okf-cli/releases/download//okf-darwin-arm64 chmod +x ~/.local/bin/okf ``` `~/.local/bin` must be on your `PATH`. If it is not, add `export PATH="$HOME/.local/bin:$PATH"` to your rc file. :::caution A lone release binary has no `skills/okf` and no `mcp/okf_mcp.py` next to it. The skill and MCP steps of `okf install` stay `BLOCKED`. Use Homebrew or a clone if you want them. ::: :::caution Nothing in the repository states that the macOS binaries are signed or notarised [c0mc]. The release workflow does not run a built binary before upload [dqq5]. Run `okf --help` on the downloaded file before you rely on it. ::: You need Go 1.26 or newer and [`just`](https://github.com/casey/just). The build is pure Go: no cgo, no C compiler. ```bash title="Terminal" git clone https://github.com/rierek/okf-cli cd okf-cli just go-build # writes go/bin/okf ``` Run `go/bin/okf` in place, or put it on your `PATH`. The clone keeps `skills/okf` and `mcp/okf_mcp.py` next to the binary, so `okf install` finds them. Some older files in the repository still name the owner `xRiErOS` in clone and release URLs. The repository is `rierek/okf-cli` [zs6t]. ## Verify the install There is no `okf --version` [i2ws]. Check the path, the file type and `--help`: ```bash title="Terminal" command -v okf file "$(command -v okf)" okf --help ``` `file` must report a compiled executable (Mach-O on macOS, ELF on Linux), not a script. A script means the old Python CLI is first on your `PATH`: see [Troubleshooting](/okf-cli/overview/troubleshooting/). `okf --help` starts like this (okf 0.2.5, macOS arm64): ```text title="okf --help" usage: okf [-h] [--root ROOT] [--global] {archive,build-index,build-semantic-index,check,ctx,find,...} ... okf — navigation, search and maintenance for an OKF Knowledge Catalogue. ``` ## Register okf with agents and shells (optional) `okf install` prints a plan and changes nothing. `okf install --apply` performs it. `okf install --uninstall --apply` removes what `install` created. It covers the agent skill, the MCP server entry and TAB completion for zsh, bash and fish. 1. Preview: `okf install` 2. Apply: `okf install --apply` (add `--only zsh,omp` to limit the targets) 3. Open a new shell and restart your agents. Plan from a lone binary with no skill next to it (okf 0.2.5): ```text title="okf install --only zsh,claude" okf install - PLAN only, nothing is changed (add --apply to perform it) source: /private/tmp/ovit (installed package, skills are copied) skills BLOCKED claude source not found: /private/tmp/ovit/skills/okf mcp BLOCKED claude MCP server script not found: /private/tmp/ovit/mcp/okf_mcp.py shell skip PATH okf is already on PATH: /opt/homebrew/bin/okf create zsh completion ~/.zsh/completions/_okf create zsh rc ~/.zshrc (managed block) 2 step(s) blocked or failed: read the marked lines above 2 step(s) planned. Run again with --apply to perform them. install failed with exit code 1 ``` A blocked plan exits 1 and ends with only `install failed with exit code 1` [fs8c]. Read the plan above that line. ## Update and uninstall - **Update.** Homebrew: `brew upgrade okf-cli`. Release binary: download the new asset over the old file. Clone: `git pull && just go-build`. Then run `okf install --apply` again: the installed skill copies and completion scripts refresh only then. - **Uninstall.** Run `okf install --uninstall --apply`, then remove the binary (`brew uninstall okf-cli`, or delete the file). ## Existing indexes Indexes built earlier (`.fts/fts.sqlite`, `.tagvec/tag_vectors.sqlite`) keep working. You do not need to rebuild them. ## Next --- # okf troubleshooting: command not found, completion, MCP server Source: https://rierek.github.io/okf-cli/overview/troubleshooting/ ## How to read this page Each entry has the symptom as heading, then the cause and the fix. Pick the heading that matches what you see. Short IDs such as [fs8c] point to [Known limitations](/okf-cli/known-limitations/). ## `command not found: okf` and the old Python CLI first on PATH **Cause.** The directory of the binary is not on `PATH`. Or `okf` runs, but an earlier install is first on `PATH`: a Python script from an older Homebrew formula, a `pipx` or `uv tool` install, or a wrapper `~/.local/bin/okf`. **Fix.** List every `okf` in `PATH` order and check each: ```bash title="Terminal" type -a okf # zsh: whence -a okf file "$(command -v okf)" # "Python script text" or a shell script = the old CLI ``` Remove the stale launcher (`brew upgrade okf-cli`, `pipx uninstall okf-cli`, `uv tool uninstall okf-cli`, or delete the wrapper file). Or put the directory of the Go binary first on `PATH`. For `~/.local/bin`, add `export PATH="$HOME/.local/bin:$PATH"` to your rc file and open a new shell. While an `okf` is on `PATH`, `okf install` does not create its own wrapper. ## TAB completion does not work **Fix.** Go in this order: 1. Run `okf install --only zsh` (or `bash`, `fish`) and read the plan. `create` means the file or the rc block is missing: run `okf install --apply`. 2. Check the file: `ls ~/.zsh/completions/_okf`. Check that `~/.zshrc` holds the block between `# >>> okf-cli >>>` and `# <<< okf-cli <<<`. 3. Open a new shell (`exec zsh`). 4. An alias must start with the word `okf`, resolved through `PATH`. `alias okf='okf --root ~/kc'` works. An alias that starts with an absolute path does not trigger completion. 5. After a binary upgrade, run `okf install --apply`: the completion scripts are copies. :::note The installed zsh script has a comment that names argcomplete and `okf-cli.py`. The comment is stale. The Go binary answers completion requests itself, and completion works [jh7t]. ::: ## `BLOCKED source not found` and `uv is not on PATH` **`source not found: …/skills/okf`.** The binary has no `skills/okf` and `mcp/okf_mcp.py` next to it, as with a lone release binary. Install with Homebrew, or from a clone and run `go/bin/okf install`. **`uv is not on PATH`.** The MCP server runs through `uv`. Install [`uv`](https://docs.astral.sh/uv/), then run `okf install --apply` again. The CLI itself does not need it. A blocked plan exits 1 and prints only `install failed with exit code 1` [fs8c]. Read the plan above that line, or run `okf install --json` and look for `steps[].status` = `BLOCKED`. ## The MCP server starts the wrong binary or none The server picks the binary in this order: `$OKF_BIN`, `/bin/okf`, `/go/bin/okf`, then `okf` on `PATH`. Set `OKF_BIN` to the binary you want, in the environment where the agent starts the server (the MCP entry can carry an `env` block). Or put the binary on the `PATH` of that agent. See [Configuration](/okf-cli/reference/configuration/). ## `find` prints `note: lexical index absent` ```text title="okf --root kc find git (okf 0.2.5, no index built)" note: lexical index absent — falling back to substring scan; run `okf build-index` for BM25 ranking note: semantic arm skipped (.tagvec cache/concept embeddings absent) — lexical results only (no matches for: git) ``` **Fix.** Run `okf build-index` once. Run it again after the catalogue changes. `note: semantic arm skipped` is normal without an embedding setup: the lexical results are complete. See [Search](/okf-cli/features/search/). ## `usage: okf ...` and exit 2 for `okf find git --root ...` `--root` and `--global` go before the command. This fails with `okf: error: unrecognized arguments: --root x` and exit 2: ```bash title="Terminal" okf find git --root /path # wrong okf --root /path find git # right ``` ## A tag or `migrate` command exits 2 with `: ` The line patcher of the frontmatter writer refused a file it cannot patch safely. It wrote nothing, also in a dry run. Edit the file by hand or repair its frontmatter, then run the command again. See [Safe edits](/okf-cli/features/safe-edits/) and [Known limitations](/okf-cli/known-limitations/). ## A tag registration warns about `build_tagvec.py` The warning names a script the Go binary does not ship [erpf]. Ignore it, or run `okf build-tagvec --produce`. --- # Features Source: https://rierek.github.io/okf-cli/features/ ## What okf does, by activity Find the task, then the command. Each link opens the reference page of the command. Find concepts and list them with a `--where` filter. See [Search](/okf-cli/features/search/). Curated reading lists per topic. See [Context bundles](/okf-cli/features/context-bundles/). Keep tags consistent. See [Tags and registry](/okf-cli/features/tags-and-registry/). Validate a catalogue against the OKF rules. See [Conformance checks](/okf-cli/features/conformance-checks/). Move and repair files without broken links. See [Safe edits](/okf-cli/features/safe-edits/). One owner text, copied into many concepts. See [Canon blocks](/okf-cli/features/canon-blocks/). A keyboard-first screen: search, filters, session buffer, hand-off to an agent, maintenance tasks. See [Terminal UI](/okf-cli/features/tui/). Install the skill, the MCP server and shell completion. ## Maintenance of this page This activity mapping is written by hand. It is checked at every release, like [Known limitations](/okf-cli/known-limitations/). ## Reference See the [command overview](/okf-cli/reference/commands/) for all commands, grouped as in `okf --help`. --- # Canon blocks: one source of truth for shared rules in markdown Source: https://rierek.github.io/okf-cli/features/canon-blocks/ # Canon blocks: sync a shared rule across markdown concepts **Write a rule once in its owner concept. okf copies it into other concepts and tells you when a copy drifts.** The measured version is okf 0.2.5. The runs below use a throwaway copy of the test catalogue. ## How it looks `okf sync-status` lists every copy of a canon block and its state. Here the owner text changed after one copy was made: ```text title="okf sync-status" $ okf --root sync-status synced handbook/ops/setup.md /kc-gov/ownership.md#ownership-rule source-changed kc-gov/naming.md /kc-gov/ownership.md#naming-rule sync-status: 1 synced, 1 source-changed, 0 consumer-modified, 0 both-changed, 0 source-missing, 0 malformed [exit 5] ``` Exit 5 means drift was found. After `okf sync-update --apply` every copy is `synced` and the exit code is 0: ```text title="okf sync-update --apply, then sync-status" $ okf --root sync-update --apply --- a/kc-gov/naming.md +++ b/kc-gov/naming.md @@ -9,8 +9,8 @@ - + > [!quote] Synchronisiert aus `/kc-gov/ownership.md` — Canon-Owner, hier nicht bearbeiten. -Names are lowercase, hyphenated and stable. +Names are lowercase, hyphenated, stable and unique. updated kc-gov/naming.md /kc-gov/ownership.md#naming-rule sync-update: 1 updated, 0 refused, 0 unresolvable. [exit 0] $ okf --root sync-status synced handbook/ops/setup.md /kc-gov/ownership.md#ownership-rule synced kc-gov/naming.md /kc-gov/ownership.md#naming-rule sync-status: 2 synced, 0 source-changed, 0 consumer-modified, 0 both-changed, 0 source-missing, 0 malformed [exit 0] ``` ## Why it matters **Edit the canon once and every consumer follows.** You do not search for copies of a rule by hand. ## What you get ```mermaid flowchart LR O["Owner concept
okf:canon id=naming-rule"] -- "sync-insert" --> C1["Consumer A
okf:sync block"] O -- "sync-insert" --> C2["Consumer B
okf:sync block"] O -. "edit the owner text" .-> D{"sync-status"} D -- "source-changed, exit 5" --> U["sync-update --apply"] U --> C1 U --> C2 ``` - [`okf sync-export`](/okf-cli/reference/commands/okf-sync-export/) prints the payload of one canon block. - [`okf sync-insert`](/okf-cli/reference/commands/okf-sync-insert/) materialises a canon block in a consumer concept. - [`okf sync-status`](/okf-cli/reference/commands/okf-sync-status/) reports the state of every copy. - [`okf sync-update`](/okf-cli/reference/commands/okf-sync-update/) re-materialises outdated copies from their owner. `okf rename` and `okf archive` of an owner rewrite `source=` in all its sync blocks (see [Safe edits](/okf-cli/features/safe-edits/)). ## Details ### Marker syntax The owner concept declares a canon block between two comments: ```markdown title="kc-gov/ownership.md (owner)" Every normative statement has exactly one owner. ``` A consumer concept holds the managed copy, a sync block. It carries the owner path in `source=` and a 12-hex `hash=`: ```markdown title="handbook/ops/setup.md (consumer)" > [!quote] Synchronisiert aus `/kc-gov/ownership.md` — Canon-Owner, hier nicht bearbeiten. Every normative statement has exactly one owner. ``` Do not edit the text between `okf:sync-body` and `/okf:sync`. The next `sync-update` replaces it. ### Insert and update preview first The argument order of `sync-insert` is consumer, owner concept, block id. Without `--apply` okf prints a diff and writes nothing: ```text title="okf sync-insert" $ okf --root sync-insert handbook/glossary kc-gov/ownership naming-rule --- a/handbook/glossary.md +++ b/handbook/glossary.md @@ -14,3 +14,9 @@ ## Concept One Markdown file with frontmatter. + + +> [!quote] Synchronisiert aus `/kc-gov/ownership.md` — Canon-Owner, hier nicht bearbeiten. + +Names are lowercase, hyphenated, stable and unique. + would insert handbook/glossary.md /kc-gov/ownership.md#naming-rule sync-insert (dry-run): re-run with --apply. ``` `sync-update` takes an optional target (`` or `/`), `--source` and `--block` to narrow the blocks, and `--force` to discard local edits of a consumer. ### Drift states `sync-status` reports one of: `synced`, `source-changed`, `consumer-modified`, `both-changed`, `source-missing`, `malformed`. The `okf check` rules `kc/sync/block`, `kc/sync/canon-block` and `kc/sync/stale` cover the same blocks (see [Conformance checks](/okf-cli/features/conformance-checks/)). ## Limits - The statuses `consumer-modified`, `both-changed`, `source-missing` and `malformed` are listed by okf; this page shows only runs with `synced` and `source-changed`. - After an owner `rename --apply`, the `> [!quote]` line of a consumer still names the old path until `sync-update` runs. Observed: `source=` is rewritten at once, the quote line is not. The status stays `synced` because the payload did not change [6peo]. - Only one error path was run: `sync-insert` with a concept argument that does not exist prints `not found: naming-rule` and exits 3. `sync-insert` with a block id that does not exist in the owner prints ``no canon block `nope` in recipes/soup.md`` and also exits 3. `sync-update` on a path that is not a directory exits 3. ## Reference - [`okf sync-export`](/okf-cli/reference/commands/okf-sync-export/), [`okf sync-insert`](/okf-cli/reference/commands/okf-sync-insert/) - [`okf sync-status`](/okf-cli/reference/commands/okf-sync-status/), [`okf sync-update`](/okf-cli/reference/commands/okf-sync-update/) - [Canon block](/okf-cli/reference/glossary/#canon-block) in the glossary --- # Conformance checks Source: https://rierek.github.io/okf-cli/features/conformance-checks/ ## Conformance checks: validate markdown frontmatter and links **Run `okf check` to see which concepts break the OKF rules, then fix them before they reach readers or CI.** All outputs on this page come from okf 0.2.5 (`rel-0-2-4-115-g91f54bf`), run against a copy of the test catalogue. ## How it looks ```text title="okf --root check recipes" ERROR okf/type/reserved-no-type recipes/readme.md (reserved file must not carry `type`) ERROR kc/supersede/resolves recipes/salad.md (superseded_by target does not resolve: recipes/soup) ERROR kc/asset/placement recipes/shopping.txt (non-md file outside assets/) ERROR kc/supersede/resolves recipes/stew.md (superseded_by target does not resolve: handbook/ops/deploy) WARNING kc/link/crosslink-format recipes/bread.md (non catalogue-absolute link(s) (use /bundle/…): pasta.md) WARNING kc/link/crosslink-format recipes/drafts/ideas.md (non catalogue-absolute link(s) (use /bundle/…): pie.md) WARNING kc/link/crosslink-format recipes/overview.md (non catalogue-absolute link(s) (use /bundle/…): pasta.md, soup.md) WARNING kc/frontmatter/tags-block-form recipes/overview.md (tags field uses inline form — local convention requires YAML block-list) WARNING kc/link/crosslink-format recipes/salad.md (non catalogue-absolute link(s) (use /bundle/…): soup.md) WARNING kc/supersede/scope recipes/salad.md (superseded_by used on non-Decision concept (type=Recipe)) WARNING kc/frontmatter/tags-block-form recipes/soup.md (tags field uses inline form — local convention requires YAML block-list) WARNING kc/markup/xml-like-tags recipes/soup.md (angle-bracket placeholder(s) not in code-fence: ) WARNING kc/tag/registered recipes/stew.md (tag `misc` not registered in TAGS.md) WARNING kc/frontmatter/english-keys recipes/stew.md (German frontmatter key(s): beschreibung, titel) WARNING kc/supersede/scope recipes/stew.md (superseded_by used on non-Decision concept (type=Recipe)) 4 VIOLATIONS [exit 4] ``` Each line has the form `SEVERITY rule-id path (message)`. The last line counts the violations. Here the count is 4: only the `ERROR` lines count. ## Why it matters **One command names every concept that breaks the rules, with the rule ID and the file.** ## What you get - **A finding per rule and file.** Pass `` or `/` to limit the scan. Without a target, `okf check` scans the whole catalogue. - **Tuning.** `--ignore PATTERN` suppresses a rule: an exact rule ID or a prefix ending in `*` (`kc/*`, `okf/link/*`). The option is repeatable. Suppressed findings neither print nor count. `--strict` counts warnings toward the exit code. - **A JSON envelope for CI.** `--json` prints one envelope. The findings are in `data.findings`, each with `rule`, `severity`, `path` and `message`. When findings exist, the envelope has `ok: false` and the same data rides in the error (`error.kind` is `non_conformant`). - **Verification dates.** `okf verify --by ACTOR /` records that a person or process checked a concept against its sources. It appends `{by, at}` to the `verified` list in the frontmatter. It writes at once, without a preview. - **A wikilink report.** `okf wikilinks` lists every Obsidian `[[…]]` link with its status: `resolved`, `ambiguous` or `unresolved`. ```text title="okf --root verify --by human:erik handbook/glossary" verified handbook/glossary by human:erik at 2026-10-18T19:48:00Z ``` ```yaml title="handbook/glossary.md, frontmatter after verify" verified: - by: human:erik at: 2026-10-18T19:48:00Z ``` ```text title="okf --root wikilinks (excerpt)" resolved body handbook/getting-started.md [[glossary]] resolved body handbook/getting-started.md [[deploy|the deploy page]] unresolved body handbook/getting-started.md [[does-not-exist]] ``` `okf wikilinks` exits 0 even when links are unresolved. Use `--unresolved` to list only the broken ones. The rule `kc/link/wikilink-resolvable` raises them as warnings in `okf check`. ## Details - **Two rule families.** Rule IDs that start with `okf/` are the OKF hard rules (SPEC §9). IDs that start with `kc/` are local invariants of the catalogue, such as link format, registered tags and types, and ctx bundle health. - **Severity.** `ERROR` sets the exit code. `WARNING` prints, but changes the exit code only with `--strict`. - **Provenance fields.** The hard rules check the trust fields in the frontmatter: `generated`, `sources`, `verified` and `stale_after`. Example: `okf/trust/generated` reports a `generated` field without a non-empty `by`. See [Frontmatter and types](/okf-cli/reference/frontmatter-and-types/). - **Exit code.** Findings give exit 4 (`NonConformant`). A target that is not a directory gives exit 3. See [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/). - **No git, no drift check.** Outside a git repository, `okf check` prints `note: drift check skipped: the catalogue root is not inside a git repository` on stderr and runs the other rules. ## Limits - The v0.2 rules apply only to bundles whose root `index.md` declares `okf_version: "0.2"`. Test: after the line `okf_version: "0.2"` was removed from `handbook/index.md`, `okf check handbook` no longer reported `okf/trust/generated` for `handbook/wiki-notes.md`. - `okf check` is read-only. It reports problems and does not fix them. ## Reference - [`okf check`](/okf-cli/reference/commands/okf-check/) - [`okf verify`](/okf-cli/reference/commands/okf-verify/) - [`okf wikilinks`](/okf-cli/reference/commands/okf-wikilinks/) - [`okf ctx check`](/okf-cli/reference/commands/okf-ctx-check/) - [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/) --- # Context bundles Source: https://rierek.github.io/okf-cli/features/context-bundles/ ## Context bundles: curated context for AI agents **Print the pages a topic needs, in reading order, with their real titles and a token estimate, and give them to a session or an agent.** All outputs on this page come from okf 0.2.5 (`rel-0-2-4-115-g91f54bf`), run against a copy of the test catalogue. ## How it looks ```text title="okf --root ctx show onboarding" ctx onboarding — Read this first on day one required — read in this order: 1. /handbook/getting-started.md [Guide] Getting started ~158 tok First steps: accounts, tools, and where to find things 2. /handbook/glossary.md [Concept] Glossary ~62 tok Terms used here: "bundle" and "concept" 3. /handbook/ops/deploy.md [Runbook] Deploy ~150 tok How to deploy: build, ship, verify reference — query when needed: - /handbook/security.md [Gadget] Security basics ~55 tok why: Background on passwords 4 entries (3 required, 1 reference), ~370 tokens for the required set ``` ```mermaid flowchart LR A[Session starts] --> B["okf ctx show onboarding"] B --> C[Read required entries in order] C --> D[Query reference entries when needed] ``` ## Why it matters **A session starts with the right pages, in the right order, and you know the token cost of the required set before you read it.** ## What you get - **A bundle is one file.** It lives at `_ctx/.md` in the catalogue. The file holds a table of entries: position, mode, path and comment. The mode is `pflicht` (shown as *required*) or `nachschlag` (shown as *reference*). ```markdown title="_ctx/onboarding.md (excerpt)" | pos | mode | path | comment | |---|---|---|---| | 1 | pflicht | [getting-started](/okf-cli/handbook/getting-started.md) | | | - | nachschlag | [security](/okf-cli/handbook/security.md) | Background on passwords | ``` - **A short form.** `okf ctx ` means `okf ctx show `. `okf ctx list` lists all bundles with their entry counts. - **Live metadata.** `show` reads title, type and description from each entry file when you run it. The bundle file holds only paths, so it does not go stale. - **Bulk verbs.** `read`, `toc`, `inbound` and `outbound` run over all entries of a bundle: print the full required entries, print all headings, list who links to each entry, list the links each entry points to. - **Curation verbs.** `create`, `add`, `drop`, `move` and `rename` change one bundle. `edit` opens it in `$VISUAL`, `$EDITOR` or `vi` and validates it. `remove` deletes a bundle and lists the files that still link to it. ## Details - The write verbs `create`, `add`, `drop`, `move`, `rename` and `remove` write at once. They have no preview. Use git to review or undo a change. - `okf ctx check` validates all bundles. Findings use the same format as `okf check`: ```text title="okf --root ctx check" ERROR kc/ctx/dead-entry _ctx/legacy.md (dead entry: handbook/ops/gone.md) WARNING kc/ctx/empty-description _ctx/legacy.md (empty description) WARNING kc/ctx/archived-entry _ctx/legacy.md (entry under _archive/: _archive/handbook/old-process.md) 1 VIOLATIONS [exit 4] ``` - `okf check` runs the same ctx rules, for example `kc/ctx/dead-entry`. See [Conformance checks](/okf-cli/features/conformance-checks/). ## Limits - A dead entry, a path that no longer exists, makes `ctx show` print `[DEAD]` and the warning `this list is incomplete`, and the exit code is 4. The `--help` text of `ctx show`, `ctx read`, `ctx check` and `ctx edit` says exit 1 [vf2r]. Script against 4. ```text title="okf --root ctx show legacy (excerpt)" 3. [DEAD] /handbook/ops/gone.md why: removed long ago WARNING: 1 dead entry — this list is incomplete; fix with `ctx edit legacy` [exit 4] ``` ## Reference - [`okf ctx`](/okf-cli/reference/commands/okf-ctx/) - Verbs: [`show`](/okf-cli/reference/commands/okf-ctx-show/), [`list`](/okf-cli/reference/commands/okf-ctx-list/), [`read`](/okf-cli/reference/commands/okf-ctx-read/), [`toc`](/okf-cli/reference/commands/okf-ctx-toc/), [`inbound`](/okf-cli/reference/commands/okf-ctx-inbound/), [`outbound`](/okf-cli/reference/commands/okf-ctx-outbound/), [`check`](/okf-cli/reference/commands/okf-ctx-check/), [`create`](/okf-cli/reference/commands/okf-ctx-create/), [`add`](/okf-cli/reference/commands/okf-ctx-add/), [`drop`](/okf-cli/reference/commands/okf-ctx-drop/), [`move`](/okf-cli/reference/commands/okf-ctx-move/), [`rename`](/okf-cli/reference/commands/okf-ctx-rename/), [`edit`](/okf-cli/reference/commands/okf-ctx-edit/), [`remove`](/okf-cli/reference/commands/okf-ctx-remove/) --- # Safe edits: rename markdown files and keep every link Source: https://rierek.github.io/okf-cli/features/safe-edits/ # Safe edits: rename markdown files and update links **Preview a rename or archive as a diff, then apply it. okf moves the concept and rewrites every link that pointed to it.** Reorganising a knowledge base by hand breaks links. With `okf rename` and `okf archive` the move, the link fixes and the index refresh happen in one command. Without `--apply` nothing is written. ## How it looks The measured version is okf 0.2.5. The runs below use a throwaway copy of the test catalogue that is also a git repository. Step 1: a dry run. okf prints a diff for each file it would change, then a summary. ```text title="okf rename: dry run (output shortened)" $ okf --root rename handbook/overview.md handbook/intro.md --- a/handbook/getting-started.md +++ b/handbook/getting-started.md @@ -25,4 +25,4 @@ ## Next -Continue with [[overview]]. +Continue with [[intro]]. --- a/handbook/index.md +++ b/handbook/index.md @@ -14,7 +14,7 @@ -* [Handbook overview](/okf-cli/handbook/overview.md) - Overview of the handbook +* [Handbook overview](/okf-cli/handbook/intro.md) - Overview of the handbook … rename (dry-run): 1 inbound, 0 outbound link(s), 2 wikilink(s) (0 skipped-ambiguous), 1 bundle(s), 0 sync source(s). Re-run with --apply. ``` After the dry run `git status --short` prints nothing: no file changed. Step 2: apply. ```text title="okf rename --apply" $ okf --root rename handbook/overview.md handbook/intro.md --apply moved (git mv) handbook/overview.md -> handbook/intro.md rewrote 1 inbound link(s) in 1 file(s), 0 outbound link(s) rewrote 2 wikilink(s) in 2 file(s) rewrote 0 sync source(s) in 0 file(s) reindexed 0 index file(s) across: handbook staged for commit — run `okf check` before committing. ``` ```text title="git status --short" M handbook/getting-started.md M handbook/index.md R handbook/overview.md -> handbook/intro.md M handbook/wiki-notes.md ``` Run [`okf check`](/okf-cli/features/conformance-checks/) before you commit. ## Why it matters **One command moves a concept and rewrites every inbound and outbound link, so a reorganisation does not leave dead links behind.** ## What you get - **Move.** [`okf rename`](/okf-cli/reference/commands/okf-rename/) moves a concept to a new path. Inbound links, outbound links, wikilinks, `source=` of [canon blocks](/okf-cli/features/canon-blocks/) and ctx bundle entries follow. - **Archive.** [`okf archive`](/okf-cli/reference/commands/okf-archive/) moves a concept to `_archive//`. `okf archive --restore` moves it back. Links are rewritten both ways. - **Bulk edits.** - [`okf rewrite-path`](/okf-cli/reference/commands/okf-rewrite-path/) replaces a literal path string in text files. - [`okf fix-crosslinks`](/okf-cli/reference/commands/okf-fix-crosslinks/) rewrites links to the catalogue-root-absolute form `/bundle/subdir/concept.md`. - [`okf fix-xml-tags`](/okf-cli/reference/commands/okf-fix-xml-tags/) wraps `` placeholders in inline code. - [`okf reindex`](/okf-cli/reference/commands/okf-reindex/) regenerates the marker-bounded index listings. - [`okf migrate`](/okf-cli/reference/commands/okf-migrate/) migrates frontmatter to OKF 0.2. ## Details An archive dry run lists every change it plans: ```text title="okf archive: dry run" $ okf --root archive handbook/ops/retired-tool would move handbook/ops/retired-tool.md -> _archive/handbook/ops/retired-tool.md inbound _ctx/legacy.md /handbook/ops/retired-tool.md -> /_archive/handbook/ops/retired-tool.md inbound handbook/getting-started.md ops/retired-tool.md -> /_archive/handbook/ops/retired-tool.md inbound handbook/ops/index.md /handbook/ops/retired-tool.md -> /_archive/handbook/ops/retired-tool.md outbound deploy.md -> /handbook/ops/deploy.md ctx legacy (pos 1) reindex _archive, handbook rename (dry-run): 3 inbound, 1 outbound link(s), 0 wikilink(s) (0 skipped-ambiguous), 2 bundle(s), 0 sync source(s). Re-run with --apply. ``` The dry run is not the same for every command: | Behaviour | Commands | |---|---| | Preview first, `--apply` writes | `archive`, `rename`, `migrate`, `merge-tags`, `normalize-tags`, `remove-tags`, `registry convert`, `sync-insert`, `sync-update`, `install` | | Write by default, `--check` is the dry run | `fix-crosslinks`, `fix-xml-tags`, `rewrite-path`, `reindex` | | Write at once, no preview | `verify`, `tags --register`, `build-index`, `visualize`, and the ctx verbs `create`, `add`, `drop`, `move`, `rename`, `remove` | :::caution Do not assume that a changing command previews first. `okf rewrite-path old new` writes at once. Add `--check` to see the effect first. ::: Frontmatter edits (`migrate` and the tag commands) patch single lines and keep your formatting. If a frontmatter has a shape okf cannot patch safely, okf refuses. The run ends with exit 2 and the message `: `, and nothing is written, also in a dry run. `--force` on `okf archive` overwrites an existing destination and also proceeds when a new basename is already taken and wikilinks would turn ambiguous. ## Limits - Only `rename` and `archive` are shown with real output on this page. Run the other commands with `--check` or without `--apply` first. - A refused frontmatter ends the whole run of the tag commands and `migrate`. It does not skip the one file. - Over MCP, the tool `okf_reindex` has no `check` argument, so it writes. This comes from the tool signature in `mcp/okf_mcp.py`; no MCP call was run. - `rename` and `archive` exit 3 when the source is not found, and 2 for a path that is not a `.md` concept file or a reserved file such as `index.md`. `rename --apply` exits 2 when the destination exists, unless `--force` is given. A dry run of `rename` onto an existing destination exits 0. ## Reference - [`okf rename`](/okf-cli/reference/commands/okf-rename/), [`okf archive`](/okf-cli/reference/commands/okf-archive/), [`okf rewrite-path`](/okf-cli/reference/commands/okf-rewrite-path/) - [`okf fix-crosslinks`](/okf-cli/reference/commands/okf-fix-crosslinks/), [`okf fix-xml-tags`](/okf-cli/reference/commands/okf-fix-xml-tags/) - [`okf reindex`](/okf-cli/reference/commands/okf-reindex/), [`okf migrate`](/okf-cli/reference/commands/okf-migrate/) - [Dry run](/okf-cli/reference/glossary/#dry-run) and [Apply](/okf-cli/reference/glossary/#apply) in the glossary --- # Markdown knowledge base search: okf find Source: https://rierek.github.io/okf-cli/features/search/ ## Markdown knowledge base search with `okf find` **Run `okf find` and get ranked concepts from the shell, narrowed by one `--where` filter. Add the semantic arm when you want embeddings.** The output below comes from okf 0.2.5, run against a throwaway copy of the test catalogue. ## How it looks Without an index, `find` falls back to a substring scan and says so on stderr. Build the FTS5 index once, then ask again: ```text title="Build the index, then search" $ okf build-index indexed 25 concept(s) -> /.fts/fts.sqlite [exit 0] $ okf find recipe --limit 2 note: semantic arm skipped (.tagvec cache/concept embeddings absent) — lexical results only ------ rank: 1 title: Recipes overview type: Concept path: recipes/overview description: Overview of the recipes body-snippet: # Overview See [pasta](pasta.md), [bread](/okf-cli/recipes/bread.md), [soup](soup.md) and [[salad]].… provenance: lexical ------ rank: 2 title: cooking type: Context path: _ctx/cooking description: Cooking basics body-snippet: …| |---|---|---|---| | 1 | pflicht | [bread](/okf-cli/recipes/bread.md) | | | 2 | pflicht | [pasta](/okf-cli/recipes/pasta.md) | | | 3 | pflicht | [soup](… provenance: lexical ------ [exit 0] ``` Every hit carries `rank`, its 1-based position in the list, and `provenance`, the arm that produced it. Add `--breakdown` to see the raw score per hit: ```text title="okf find recipe --limit 2 --breakdown (score lines only)" score: 100/100 contrib: bm25 (fts5 index; field-level breakdown not available) score: 88/100 contrib: bm25 (fts5 index; field-level breakdown not available) ``` ## Why it matters **You get ranked hits without leaving the shell, and the same `--where` filter selects the same concepts for [`okf list`](/okf-cli/reference/commands/okf-list/).** ## What you get - Ranked results from a SQLite FTS5 index with BM25 weights: title 3.0, tags 2.0, body 1.0. - An optional semantic arm that ranks by embedding similarity. - One `--where` expression that narrows by type, tag, path, archived state and any frontmatter key. See the [`--where` reference](/okf-cli/reference/where-filter/). - `okf list` returns the complete, unranked set that a `--where` selects. ```text title="okf find recipe --where 'type = Recipe' --limit 1" ------ rank: 1 title: Soup type: Recipe path: recipes/soup description: Tomatensuppe body-snippet: …weis Use a pot . Compare with [bread](/okf-cli/recipes/bread.md).… provenance: lexical ------ [exit 0] ``` ```text title="okf list --where 'tag = soup'" ------ title: Gazpacho type: Recipe path: recipes/seasonal/gazpacho description: Cold tomato soup ------ title: Soup type: Recipe path: recipes/soup description: Tomatensuppe ------ list: 2 concept(s) [exit 0] ``` The filter applies before the cut to `--limit`. Archived concepts stay hidden unless the expression names `archived`. ## How the two arms work ```mermaid flowchart LR Q[query + --where] --> L[Lexical arm: FTS5 / BM25] Q --> S[Semantic arm: embeddings] L --> R[Ranked list] S --> R R --> O[rank 1..limit] ``` The list order is the rank. The two arms never blend scores: the arm that owns the ranking hands over its order. Raw scores stay out of the default output. `--breakdown` prints them, one `score:` line per hit. ## Details - `okf build-index` builds or refreshes `.fts/fts.sqlite`. `--if-stale` skips the rebuild when the index is current. The file shows as untracked in git. - Without the index, `find` scans substrings and prints `note: lexical index absent — falling back to substring scan; run `okf build-index` for BM25 ranking`. Hits then print `provenance: lexical`, not `substring` as the help says [gnnc]. - The semantic arm reads `.tagvec/tag_vectors.sqlite`. `okf build-semantic-index` fills it with `voyage-3-large` embeddings and needs `VOYAGE_API_KEY`. The tag commands use a local Ollama `bge-m3` at `localhost:11434` (see [Tags and registry](/okf-cli/features/tags-and-registry/)). The endpoint and model are fixed today; a configurable endpoint is planned [76eb]. - When the semantic data is absent, `find` prints `note: semantic arm skipped (.tagvec cache/concept embeddings absent) — lexical results only` and the lexical result stands alone. Degradation never changes the exit code. - `okf recent` lists the newest concepts and takes the same `--where`. - `--include-topics` widens the search from the `.okf.toml` bundle to topic folders. - `--json` prints one envelope. `data` holds `hits` and `nearest_tags`; degradation notes arrive in `warnings`. ## Limits - `build-semantic-index` without `VOYAGE_API_KEY` exits 0 and writes nothing. It prints `note: semantic index build skipped (VOYAGE_API_KEY not set)`. Check the exit code and the note, not only success. - `--expand-tags` is off by default. The project measured it as ineffective for the vocabulary mismatch it targets. - The semantic arm was not run for this page. Its behaviour with a key is read from the code, not from a run. ## Reference - [`okf find`](/okf-cli/reference/commands/okf-find/) - [`okf list`](/okf-cli/reference/commands/okf-list/) - [`okf recent`](/okf-cli/reference/commands/okf-recent/) - [`okf build-index`](/okf-cli/reference/commands/okf-build-index/) - [`okf build-semantic-index`](/okf-cli/reference/commands/okf-build-semantic-index/) - [`--where` filter](/okf-cli/reference/where-filter/) --- # Tag management and the TAGS.md registry Source: https://rierek.github.io/okf-cli/features/tags-and-registry/ ## Tag management and the `TAGS.md` registry **List, check, merge and register the tags of a catalogue from the shell, against one closed vocabulary in `TAGS.md`.** Output below comes from okf 0.2.5, run against a throwaway copy of the test catalogue. ## How it looks ```text title="okf tags" food 9 ops 5 glossary 2 governance 2 onboarding 2 soup 2 bread 1 deploy 1 incident 1 misc 1 pasta 1 security 1 x-experimental 1 [exit 0] ``` ```text title="okf tags sup --limit 3" == searched: sup == - soup (2) [exit 0] ``` ```text title="okf tags --validate soup nonsense" UNKNOWN nonsense 1 UNKNOWN — register with: okf tags --register "" [exit 4] ``` ## Why it matters **One tag vocabulary, checked and repairable from the shell.** ## What you get - `okf tags` lists usage counts, most used first. `--singletons` shows tags used once. With a query it fuzzy-searches the tag universe. - `okf tags --validate TAG...` checks tags against `TAGS.md`. - `okf tags --register TAG DESCRIPTION` adds a tag. The description is required, 150 characters at most. Repeat the option for several tags. - `okf merge-tags`, `okf remove-tags` and `okf normalize-tags` edit tags across concepts. Each one takes `--where` to limit the concepts. - `okf registry convert` rewrites the entries of `TAGS.md` or `TYPES.md` between table and block form. The registry is `TAGS.md` in the catalogue root: a table `| Tag | Description |`, or block form. The `check` rule `kc/tag/registered` warns for a tag that is not in it: ```text title="okf check (tag lines)" WARNING kc/tag/registered handbook/security.md (tag `x-experimental` not registered in TAGS.md) WARNING kc/tag/registered recipes/stew.md (tag `misc` not registered in TAGS.md) ``` ### Merge preview ```text title="okf merge-tags misc --into food" --- a/recipes/stew.md +++ b/recipes/stew.md @@ -4,7 +4,6 @@ beschreibung: Deutscher Eintopf tags: - food - - misc superseded_by: handbook/ops/deploy --- unparseable handbook/broken-yaml.md (frontmatter does not parse: did not find expected ',' or ']' (line 3, column 9) — skipped) merge-tags: 1 file(s) would rewrite, 1 unparseable — misc -> food [exit 0] ``` Nothing is written until you add `--apply`. The merge skips a file whose frontmatter does not parse and reports it. ### Registry conversion preview ```text title="okf registry convert TAGS.md --to block (diff head)" --- a/TAGS.md +++ b/TAGS.md @@ -2,16 +2,35 @@ Closed vocabulary for concept `tags:`. -| Tag | Description | -|-----|-------------| -| `bread` | Bread and baking | +### `bread` +Bread and baking + +### `deploy` +Deployment procedures ``` The last line of the real run is `registry convert: 11 entries in TAGS.md would rewrite as block`. Text outside the entries stays byte for byte. The `.okf.toml` key `registry_format` sets the form that `--register` writes. ## Details - `merge-tags`, `remove-tags`, `normalize-tags` and `registry convert` preview a diff and need `--apply` to write. - `tags --register` writes `TAGS.md` immediately. It has no preview. - `--validate` and `--json` follow the [envelope and exit-code rules](/okf-cli/reference/json-output-and-exit-codes/). `--json` data is `{tags: [{tag, count}]}` for the list and `{closed_vocabulary, unknown}` for `--validate`. :::tip[Tag hygiene] Register every tag in `TAGS.md`. Run `okf tags --validate` in CI: an unregistered tag exits 4 and fails the job. Run `okf tags --singletons` to find near-duplicates, then `okf merge-tags` them into one tag. ::: ## Limits - `tags --validate` exits 4. The `--help` text says 1 [irxe]. - `tags --register` then tries to embed the new tag with a local Ollama `bge-m3` at `http://localhost:11434/api/embed`. Without that server it warns `auto-embed skipped for new tag(s) (connection refused)` and names `cli/embeddings/build_tagvec.py`, a script a Go install does not contain [erpf]. The tag is registered anyway. The endpoint is fixed today; a configurable endpoint is planned [76eb]. ## Reference - [`okf tags`](/okf-cli/reference/commands/okf-tags/) - [`okf merge-tags`](/okf-cli/reference/commands/okf-merge-tags/) - [`okf remove-tags`](/okf-cli/reference/commands/okf-remove-tags/) - [`okf normalize-tags`](/okf-cli/reference/commands/okf-normalize-tags/) - [`okf registry convert`](/okf-cli/reference/commands/okf-registry-convert/) --- # Terminal UI for a Markdown knowledge base: okf tui Source: https://rierek.github.io/okf-cli/features/tui/ ## A keyboard-first terminal UI for your catalogue: `okf tui` **Run `okf tui` in a catalogue and search it, filter the hits, keep the good ones in a buffer and hand them to an agent. All without leaving the keyboard.** The recordings below show each pressed key in a small box at the bottom right. They run the real binary against a throwaway copy of the test catalogue. Light and dark variants follow the site theme. :::note `okf tui --help` names the settings, the state file and the environment variables. ::: ## Start Change into a catalogue and run `okf tui`. It takes the root as `okf find` does: `--root`, then `--global`, then the nearest catalogue above the current directory, then `root` in the user config. Without a root it prints the message of `okf find` and exits with code 3. ```text title="Start the TUI" $ cd ~/Knowledge-Catalogue $ okf tui ``` The TUI opens on the search screen with the focus in the query field. The header shows the root, the two index dots (lexical and semantic), the buffer count and `ctrl+k` for the command center. ## Search Type a query and press `enter`. The search runs only on `enter`, never while you type; it is `okf find --limit 50` on the active root. The focus moves to the list. `j` and `k` move through the hits, `v` cycles the layout (list and detail, list, detail), `/` edits the query again, `?` opens the help. ## Filters Below the query field a bar of six boxes narrows the search: **Root**, **Bundle**, **Type**, **Tag**, **Status** and **Tier**. You never write `--where` syntax; the TUI builds the expression for you. `f` focuses the bar, `←` and `→` move between boxes, `enter` opens the menu of a box, `space` selects a value and `esc` closes the menu. In a long menu, `/` opens a query line and the menu shows only the matching values. `X` clears every filter. ## Buffer and hand-off The session buffer collects hits across searches and roots. `space` adds the focused hit (a `◆` marks it), `n` writes a note for it, `b` opens the buffer view. `ctrl+s` hands the buffer to an agent: the text goes into the agent pane under the herdr popup, or a picker lists the agents herdr knows. Outside herdr the text goes to the clipboard. `y` copies it to the clipboard on its own. The recording uses a stand-in `herdr` that lists three agents, so it shows the picker. The last lines print the text the agent received. ## Command center `ctrl+k` opens the command center from every view, also inside the query field. It lists every action of the key map and every view as `: ` with its key at the right. Type to filter, `↑` and `↓` select, `enter` runs. It is the only way to open the types view and the tags view. ## Maintenance tasks Found something wrong in a concept? Press `t` on the hit. A text area opens: the first line is the title, the rest is the body. `ctrl+s` saves the task to the tracker you configured in `[tui.tracker]`; the TUI keeps your search, list and buffer. The recording uses the `file` kind, which appends an Obsidian Tasks line. ## Index dots Two dots in the header show the state of the indexes: green current, yellow stale or partial, red absent, grey not checked yet (lexical) or unavailable without `VOYAGE_API_KEY` (semantic). `r` opens a dialog: `l` rebuilds the lexical index, `s` embeds the missing concepts for the semantic index (it names the number of concepts and asks first). ## Open a hit in the editor `e` on a hit opens its file in your editor (`editor` in `[tui]`, then `$VISUAL`, `$EDITOR`, `vi`) and suspends the TUI. When the editor exits, the TUI reads that hit again, so the detail pane shows your change. ## Keys and settings The [`okf tui` reference](/okf-cli/reference/commands/tui/) lists every key, the `[tui]` settings (editor, theme, accent), the `[tui.tracker]` kinds, the herdr popup and the exact hand-off text. ## Reproduce the media The recordings come from versioned [vhs](https://github.com/charmbracelet/vhs) tapes in `scripts/tui-media/`. `just docs-tui-media` runs them against a throwaway copy of the test catalogue with an isolated home and config, and draws the key boxes from the key timeline of each tape. --- # okf use cases Source: https://rierek.github.io/okf-cli/use-cases/ **A use case shows what okf makes possible. Pick the one that matches your problem.** ## Boundary A use case shows what okf makes possible. Advice and experience belong to the [Blog](/okf-cli/blog/). --- # Define canon knowledge once and reuse it across projects Source: https://rierek.github.io/okf-cli/use-cases/define-canon-knowledge/ # Define canon knowledge: reusable rules across projects with okf sync **Define a rule once and every project concept that carries it holds the same text.** The output below comes from okf 0.2.5 on a throwaway copy of the test catalogue. ```mermaid flowchart LR O["kc-gov/ownership.md
okf:canon naming-rule"] -->|sync-insert / sync-update| A["kc-gov/naming.md
okf:sync"] O -->|sync-insert / sync-update| B["recipes/soup.md
okf:sync"] O -.->|sync-status: drift report| A O -.->|sync-status: drift report| B ``` ## Set it up Mark the rule in its owner concept. The owner holds the text; consumers get copies. ```markdown title="kc-gov/ownership.md" Names are lowercase, hyphenated, stable and unique. ``` Put the catalogue under git so each change is one diff. Details of the markers: [Canon blocks](/okf-cli/features/canon-blocks/). ## Work through it 1. Print the payload without its markers: ```text title="okf sync-export" $ okf sync-export kc-gov/ownership naming-rule Names are lowercase, hyphenated, stable and unique. [exit 0] ``` 2. Materialise it in a consumer concept. Without `--apply` it only previews: ```text title="okf sync-insert (dry run)" $ okf sync-insert recipes/soup kc-gov/ownership naming-rule --- a/recipes/soup.md +++ b/recipes/soup.md @@ -15,3 +15,9 @@ ## Hinweis Use a pot . Compare with [bread](/recipes/bread.md). + + +> [!quote] Synchronisiert aus `/kc-gov/ownership.md` — Canon-Owner, hier nicht bearbeiten. + +Names are lowercase, hyphenated, stable and unique. + would insert recipes/soup.md /kc-gov/ownership.md#naming-rule sync-insert (dry-run): re-run with --apply. [exit 0] ``` With `--apply` the same block is written and the last line reads `inserted recipes/soup.md /kc-gov/ownership.md#naming-rule`. 3. Change the owner text later, then see which copies lag. `sync-status` reads only and exits 5 on drift: ```text title="okf sync-status" $ okf sync-status synced handbook/ops/setup.md /kc-gov/ownership.md#ownership-rule source-changed kc-gov/naming.md /kc-gov/ownership.md#naming-rule synced recipes/soup.md /kc-gov/ownership.md#naming-rule sync-status: 2 synced, 1 source-changed, 0 consumer-modified, 0 both-changed, 0 source-missing, 0 malformed [exit 5] ``` 4. Refresh outdated copies. Dry run first, then `--apply`: ```text title="okf sync-update --apply" $ okf sync-update --apply --- a/kc-gov/naming.md +++ b/kc-gov/naming.md @@ -9,8 +9,8 @@ Refer to [ownership](/kc-gov/ownership.md). - + > [!quote] Synchronisiert aus `/kc-gov/ownership.md` — Canon-Owner, hier nicht bearbeiten. -Names are lowercase, hyphenated and stable. +Names are lowercase, hyphenated, stable and unique. updated kc-gov/naming.md /kc-gov/ownership.md#naming-rule sync-update: 1 updated, 0 refused, 0 unresolvable. [exit 0] ``` ### A copy edited by hand `sync-update` refuses to overwrite a consumer copy that was edited. `--force` discards the local edit. ```text title="Consumer copy modified" $ okf sync-status --source /kc-gov/ownership.md synced handbook/ops/setup.md /kc-gov/ownership.md#ownership-rule synced kc-gov/naming.md /kc-gov/ownership.md#naming-rule consumer-modified recipes/soup.md /kc-gov/ownership.md#naming-rule sync-status: 2 synced, 0 source-changed, 1 consumer-modified, 0 both-changed, 0 source-missing, 0 malformed [exit 5] $ okf sync-update --apply refused recipes/soup.md /kc-gov/ownership.md#naming-rule (consumer copy modified — reconcile into the canon owner first, or --force to discard) sync-update: 0 updated, 1 refused, 0 unresolvable. [exit 5] ``` `sync-update --apply --force` overwrites the local edit. If the edit is worth keeping, move it into the canon owner first. An unknown block id stops `sync-insert` with exit 3: ```text title="Unknown block" $ okf sync-insert --apply recipes/stew kc-gov/ownership nope no canon block `nope` in kc-gov/ownership.md [exit 3] ``` ## What you get Project concepts that carry the same canon text, and a drift report (`source-changed`, `consumer-modified`, …) whenever the owner or a copy changes. `okf check` also warns about an outdated copy (`kc/sync/stale`). Background and design: the blog post [Canon in the catalogue](/okf-cli/blog/canon-in-the-catalogue/). ## Limits - After `rename --apply` of a canon owner, the `> [!quote]` line in consumers keeps the old path and `sync-status` can still say `synced` [6peo]. Run `okf sync-update --apply`. - `sync-update` exits 5 when it refuses a block or cannot resolve one. `sync-insert` exits 3 when the concept or the block does not exist. ## Reference --- # Migrate to OKF 0.2 Source: https://rierek.github.io/okf-cli/use-cases/migrate-to-okf-0-2/ # Migrate to OKF 0.2: `okf migrate` **`okf migrate` turns `timestamp` into `generated` and maps old `status` values to the OKF 0.2 set, and it shows every change before it writes one.** The output below comes from okf 0.2.5 on a throwaway copy of the test catalogue. ## Set it up 1. Put the catalogue under git and commit. `migrate` reads author and time for `generated` from git history, so the migration also lands as one reviewable diff. 2. Optional: map author e-mail addresses to actors in `.okf.toml` under `[git_authors]`. See [Configuration](/okf-cli/reference/configuration/). 3. Run `okf migrate` without `--apply`. It is a dry run. ```text title="Dry run (first lines)" $ okf --root migrate recipes status mapping: active -> stable (1 concept(s)) obsolete -> deprecated (1 concept(s)) todo -> draft (1 concept(s)) --- a/recipes/bread.md +++ b/recipes/bread.md @@ -5,8 +5,10 @@ tags: - food - bread -timestamp: 2025-12-02T08:00:00Z -status: todo +generated: + by: human:golden + at: '2026-10-18T22:07:21+02:00' +status: draft --- # Bread ``` The dry run exits 0 and leaves the files untouched. ## Work through it 1. Read the status mapping table. It counts the concepts for each old value. 2. Look for `unresolved` lines. A status value without a recorded decision is not changed: ```text title="Concept with no decision" $ okf --root migrate handbook status mapping: deprecated -> deprecated (1 concept(s)) maybe-later -> (no decision) (1 concept(s)) stable -> stable (1 concept(s)) unresolved handbook/wiki-notes.md: no recorded decision for status value 'maybe-later' migrate: would migrate 0 file(s), 1 unresolved; dry-run: re-run with --apply ``` Edit such a file by hand to `draft`, `stable` or `deprecated`. 3. Write the changes with `--apply`, then review `git diff`. 4. Run `okf check`. The rules `okf/trust/generated` and `okf/lifecycle/status` report what is still wrong: ```text title="Remaining findings after the run" $ okf --root check ERROR okf/trust/generated handbook/wiki-notes.md (`generated` has no non-empty `by`) ERROR okf/lifecycle/status handbook/wiki-notes.md (`status` is `maybe-later`, not draft, stable or deprecated) ``` :::caution `migrate` patches frontmatter line by line. A file it cannot patch ends the whole run with exit 2 and `: `, and nothing is written, also in a dry run. Fix that file by hand and run again. See [known limitations](/okf-cli/known-limitations/). ::: For scripts, `--json` prints the envelope with `data` = `{applied, files, unresolved, status_mapping}`. ## What you get Files that carry `generated` (`by`, `at`) in place of `timestamp`, and `status` values from the set `draft`, `stable`, `deprecated`. A file you fixed by hand after an `unresolved` line passes the same two rules. ## Limits - `migrate` exits 0 on success and 2 for a file the patcher refuses. A path that is not a directory exits 3. - `okf check` can report findings unrelated to the migration. Read the rule names. ## Reference - [`okf migrate`](/okf-cli/reference/commands/okf-migrate/) - [`okf check`](/okf-cli/reference/commands/okf-check/) - [Troubleshooting](/okf-cli/overview/troubleshooting/) --- # Onboard an AI agent to a knowledge base with okf prime Source: https://rierek.github.io/okf-cli/use-cases/onboard-agents/ ## Onboard an AI agent to a knowledge base **An agent starts with the map of your catalogue and the pages that matter, in two calls.** Search terms covered: onboard AI agent to knowledge base, `okf prime`, Claude Code context, MCP knowledge base. Output below comes from okf 0.2.5 against a copy of the golden test catalogue; `` replaces the real path. ## Set it up Install okf, then connect your agents. See [Installation](/okf-cli/overview/installation/) for the install paths. ```bash title="Terminal" okf install # preview: prints a plan, changes nothing okf install --apply # installs skill, MCP server entry, completion ``` Restart the agent afterwards. The MCP server runs the `okf` binary; `OKF_BIN` overrides which one. See [Agent onboarding](/okf-cli/overview/agent-onboarding/). ## Work through it **1. Give the agent the map.** `okf prime` prints the usage guide, the command directory and the catalogue state with the bundle TOC. An agent runs it once at session start. ```text title="okf --root prime (excerpt)" ## This catalogue root: bundles: 4 · concepts: 24 fts index: absent — `find` degrades to a substring scan; run `build-index` semantic index: absent — `find` runs its lexical arm only tag vocabulary: closed — 11 tags in TAGS.md; check a new one with `tags --validate`, adopt it with `tags --register` ctx bundles: 3 — cooking · legacy · onboarding — `ctx show ` lists one root docs: SPEC.md · TYPES.md · PROPERTIES.md · TAGS.md ## Bundles [exit 0] ``` **2. Hand over one topic.** `okf ctx` lists the context bundles. `okf ctx ` prints that topic's pages in reading order, with a token estimate. ```text title="okf --root ctx onboarding" ctx onboarding — Read this first on day one required — read in this order: 1. /handbook/getting-started.md [Guide] Getting started ~158 tok First steps: accounts, tools, and where to find things 2. /handbook/glossary.md [Concept] Glossary ~62 tok Terms used here: "bundle" and "concept" 3. /handbook/ops/deploy.md [Runbook] Deploy ~150 tok How to deploy: build, ship, verify reference — query when needed: - /handbook/security.md [Gadget] Security basics ~55 tok why: Background on passwords 4 entries (3 required, 1 reference), ~370 tokens for the required set [exit 0] ``` **3. Let the agent look things up.** Through MCP it calls the tools `okf_find` and `okf_read`. In a shell it runs `okf find` and `okf read`. A section read returns only that section: ```text title="okf --root read recipes/bread --section Steps" ## Steps Mix, rise, bake. Related: [pasta](pasta.md). ``` ## What you get An agent that finds a concept with ranked search and reads one section of it, with no blind `grep` over files. Curate more topics with [Context bundles](/okf-cli/features/context-bundles/). ## Limits - The `prime` state text, like `tags --register`, names `build_tagvec.py`, which a Go install lacks [erpf]. Ignore it, or run `okf build-tagvec --produce`. See [Known limitations](/okf-cli/known-limitations/). ## Reference - [Agent onboarding](/okf-cli/overview/agent-onboarding/) - [Context bundles](/okf-cli/features/context-bundles/) - [`okf prime`](/okf-cli/reference/commands/okf-prime/) - [`okf ctx`](/okf-cli/reference/commands/okf-ctx/) - [`okf read`](/okf-cli/reference/commands/okf-read/) --- # Organize large bundles: tags, renames, links, checks Source: https://rierek.github.io/okf-cli/use-cases/organize-large-bundles/ # Organize large bundles: tag cleanup, fix broken links, okf maintenance **Tags, links and drift stay under control as a bundle grows: each upkeep command previews its change first, and `okf check` reports what is left.** The output below comes from okf 0.2.5 on a throwaway copy of the test catalogue. ## Set it up 1. Put the catalogue under git and commit. Every command below then lands as one reviewable diff. `rename` also stages its move with `git mv`. 2. Run the command without `--apply`. The preview is a unified diff plus a summary line. 3. Run it again with `--apply`, then review `git status` and `git diff`. `merge-tags`, `normalize-tags`, `rename` and `sync-update` write only with `--apply`. `fix-crosslinks` is the reverse: it writes by default and `--check` is the dry run. Read [Safe edits](/okf-cli/features/safe-edits/) before the first run. ## Work through it List tags that occur once, then check a tag against the registry: ```text title="Audit tags" $ okf tags --singletons bread 1 deploy 1 incident 1 misc 1 pasta 1 security 1 x-experimental 1 [exit 0] $ okf tags --validate misc governance UNKNOWN misc 1 UNKNOWN — register with: okf tags --register "" [exit 4] ``` Fold a stray tag into a registered one. This is the preview: ```text title="okf merge-tags misc --into governance" $ okf merge-tags misc --into governance --- a/recipes/stew.md +++ b/recipes/stew.md @@ -4,7 +4,7 @@ beschreibung: Deutscher Eintopf tags: - food - - misc + - governance superseded_by: handbook/ops/deploy --- unparseable handbook/broken-yaml.md (frontmatter does not parse: did not find expected ',' or ']' (line 3, column 9) — skipped) merge-tags: 1 file(s) would rewrite, 1 unparseable — misc -> governance [exit 0] ``` Add `--apply` to write. `okf normalize-tags` converts inline `tags: [a, b]` to the block list in the same way; it lists the files it would rewrite and ends with `normalize-tags: 3 file(s) would rewrite, 1 unparseable` on the test catalogue. A file with unparseable frontmatter is reported as `unparseable` and skipped in this run. A file the writer refuses ends a whole tag run, a dry run too [n760]. See [Known limitations](/okf-cli/known-limitations/). Details: [Tags and registry](/okf-cli/features/tags-and-registry/), [`okf merge-tags`](/okf-cli/reference/commands/okf-merge-tags/), [`okf normalize-tags`](/okf-cli/reference/commands/okf-normalize-tags/). `okf rename` moves a concept and rewrites every inbound link, ctx bundle entry and index listing. Both paths must end in `.md`. ```text title="okf rename recipes/soup.md recipes/soups.md (dry run, listing only)" $ okf rename recipes/soup.md recipes/soups.md would move recipes/soup.md -> recipes/soups.md inbound _ctx/cooking.md /recipes/soup.md -> /recipes/soups.md inbound recipes/index.md /recipes/soup.md -> /recipes/soups.md inbound recipes/overview.md soup.md -> /recipes/soups.md inbound recipes/salad.md soup.md -> /recipes/soups.md ctx cooking (pos 3) reindex recipes rename (dry-run): 4 inbound, 0 outbound link(s), 0 wikilink(s) (0 skipped-ambiguous), 1 bundle(s), 0 sync source(s). Re-run with --apply. [exit 0] ``` ```text title="With --apply" $ okf rename --apply recipes/soup.md recipes/soups.md moved (git mv) recipes/soup.md -> recipes/soups.md rewrote 4 inbound link(s) in 4 file(s), 0 outbound link(s) rewrote 0 wikilink(s) in 0 file(s) rewrote 0 sync source(s) in 0 file(s) listed in 1 ctx bundle(s): cooking (pos 3) reindexed 1 index file(s) across: recipes staged for commit — run `okf check` before committing. [exit 0] ``` Links that are not in the catalogue-absolute form `/bundle/concept.md` are a separate repair. `okf fix-crosslinks --check` previews it: ```text title="okf fix-crosslinks --check recipes (listing only)" $ okf fix-crosslinks --check recipes --- a/recipes/bread.md +++ b/recipes/bread.md @@ -13,4 +13,4 @@ ## Steps -Mix, rise, bake. Related: [pasta](pasta.md). +Mix, rise, bake. Related: [pasta](/recipes/pasta.md). unresolved recipes/drafts/ideas.md -> pie.md fix-crosslinks: 2 file(s) would rewrite, 1 unresolved link(s) [exit 0] ``` A link whose target does not exist stays `unresolved`: `fix-crosslinks` does not guess. Without `--check` the command writes. Details: [Safe edits](/okf-cli/features/safe-edits/), [`okf rename`](/okf-cli/reference/commands/okf-rename/), [`okf fix-crosslinks`](/okf-cli/reference/commands/okf-fix-crosslinks/). `okf check` reports conformance findings as `ERROR` or `WARNING` lines and a `N VIOLATIONS` total. Run it on the whole catalogue or on one bundle: ```text title="okf check recipes (listing only)" $ okf check recipes ERROR okf/type/reserved-no-type recipes/readme.md (reserved file must not carry `type`) ERROR kc/supersede/resolves recipes/salad.md (superseded_by target does not resolve: recipes/soup) ERROR kc/asset/placement recipes/shopping.txt (non-md file outside assets/) ERROR kc/supersede/resolves recipes/stew.md (superseded_by target does not resolve: handbook/ops/deploy) WARNING kc/link/crosslink-format recipes/drafts/ideas.md (non catalogue-absolute link(s) (use /bundle/…): pie.md) WARNING kc/supersede/scope recipes/salad.md (superseded_by used on non-Decision concept (type=Recipe)) WARNING kc/markup/xml-like-tags recipes/soup.md (angle-bracket placeholder(s) not in code-fence: ) WARNING kc/frontmatter/english-keys recipes/stew.md (German frontmatter key(s): beschreibung, titel) WARNING kc/supersede/scope recipes/stew.md (superseded_by used on non-Decision concept (type=Recipe)) 4 VIOLATIONS [exit 4] ``` The test catalogue carries these findings on purpose. Findings exit with 4. The rules are listed in [Conformance checks](/okf-cli/features/conformance-checks/). If the bundle holds [canon blocks](/okf-cli/features/canon-blocks/), `okf sync-status` is a read-only drift check. It exits 5 when any copy is not `synced`: ```text title="okf sync-status" $ okf sync-status synced handbook/ops/setup.md /kc-gov/ownership.md#ownership-rule source-changed kc-gov/naming.md /kc-gov/ownership.md#naming-rule sync-status: 1 synced, 1 source-changed, 0 consumer-modified, 0 both-changed, 0 source-missing, 0 malformed [exit 5] ``` Repairing drift is covered in [Define canon knowledge](/okf-cli/use-cases/define-canon-knowledge/). Details: [`okf check`](/okf-cli/reference/commands/okf-check/), [`okf sync-status`](/okf-cli/reference/commands/okf-sync-status/). ## What you get A bundle whose tags are registered and in block form, whose links use one form and point at existing concepts, and whose `okf check` run lists only the findings you decided to keep. Each step is one git diff. ## Limits - Dry run differs per command: see the caution above and [Safe edits](/okf-cli/features/safe-edits/). - One refused file ends a whole tag run [n760]. - After `rename --apply` of a canon owner, consumer copies keep the old path in the `> [!quote]` line and `sync-status` can still say `synced` [6peo]. Run `okf sync-update --apply`. ## Reference --- # Work with multiple knowledge bases: okf --root and config Source: https://rierek.github.io/okf-cli/use-cases/work-with-multiple-bundles/ ## Work with multiple knowledge bases **One binary, any number of catalogues, chosen per call.** Search terms covered: multiple knowledge bases CLI, `okf --root`, catalogue config, Obsidian vault CLI. Output below comes from okf 0.2.5 against two throwaway catalogues, `a` and `b`; `b` is a copy of `a` without the `recipes` and `kc-gov` bundles. ## Set it up okf picks the catalogue in this order: ```mermaid flowchart TD A[okf command] --> G{--global?} G -- yes --> C[root from ~/.config/okf/config.toml] G -- no --> R{--root?} R -- yes --> D[that path] R -- no --> W[nearest catalogue above the working directory] W -- none found --> C ``` 1. `--root ` uses that catalogue. 2. Otherwise okf walks up from the working directory and uses the nearest catalogue. 3. Otherwise it reads `root` from `~/.config/okf/config.toml`. 4. `--global` skips steps 1 and 2 and uses the config root. ```toml title="~/.config/okf/config.toml" root = "~/knowledge" ``` Details and the `.okf.toml` marker: [Configuration](/okf-cli/reference/configuration/). ## Work through it Run the same command against two roots: ```text title="okf --root a index" _ctx: Curated, ordered reading lists per topic — one file per ctx slug, managed by `okf ctx`. handbook: Welcome to the handbook. kc-gov: recipes: ``` ```text title="okf --root b index" _ctx: Curated, ordered reading lists per topic — one file per ctx slug, managed by `okf ctx`. handbook: Welcome to the handbook. ``` Inside a catalogue you need no flag. From `a/handbook/ops`, `okf index` prints the catalogue `a`. With `root = ""` in the config, `okf --global index` run from inside `a` prints the catalogue `b`. Catalogue-wide commands such as `find`, `list`, `check` and `tags` span all bundles of one root. Narrow to one bundle with `--where "path ~ handbook/"`. See [Where filter](/okf-cli/reference/where-filter/). ## What you get One command set across every catalogue: a private one, project ones and shared bundles. Agents use the same selection: the MCP tools take `root` and `use_global`, see [Agent onboarding](/okf-cli/overview/agent-onboarding/). ## Limits - `--root` and `--global` go before the command and exclude each other: ```text title="okf --global --root b index" ERROR: --global and --root exclude each other [exit 2] ``` - `--global` without a configured `root` exits 3. - One call uses one root. To search two catalogues, make two calls. ## Reference - [Configuration](/okf-cli/reference/configuration/) - [Concepts](/okf-cli/overview/concepts/) --- # okf commands list Source: https://rierek.github.io/okf-cli/reference/commands/ Command pages are generated from the command tree by `okf-docgen`. This page lists every visible command and every ctx verb, grouped as in `okf --help`. Open a command to see its usage and options. :::note The text of `okf --help` still describes the Python script [l2j0]. The option lists are correct. ::: ## Global options `--root` and `--global` belong to `okf` itself and go before the command. | Option | Meaning | |---|---| | `--root ROOT` | Catalogue root. Default: the nearest catalogue above the current directory. | | `--global` | Use the root in `~/.config/okf/config.toml`, even when a catalogue lies above. Not allowed together with `--root` (exit 2). | `--json` is not global [oyw7]. Each command declares it and it goes after the command: `okf find git --json`. Every command except `okf registry` accepts it; each ctx verb accepts it, `okf ctx` alone does not. `okf --json list` fails with `unrecognized arguments: --json`. See [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/). For the ctx workflow, see [Context bundles](/okf-cli/features/context-bundles/). ## Commands by purpose --- # okf build-index Source: https://rierek.github.io/okf-cli/reference/commands/okf-build-index/ ## Usage ```text usage: okf build-index [-h] [--include-topics] [--if-stale] [--json] (re)build the FTS5 lexical search index at /.fts/fts.sqlite (local only, no network) options: -h, --help show this help message and exit --include-topics widen beyond .okf.toml declared bundle to topic folders --if-stale skip the rebuild when the existing index is already current (default: always rebuild) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf build-semantic-index Source: https://rierek.github.io/okf-cli/reference/commands/okf-build-semantic-index/ ## Usage ```text usage: okf build-semantic-index [-h] [--if-stale] [--json] fill /.tagvec/tag_vectors.sqlite's concept_emb table for every concept still missing a voyage-3-large embedding (NETWORK, requires VOYAGE_API_KEY) options: -h, --help show this help message and exit --if-stale skip the run when semantic_coverage() already reports every on-disk concept embedded (default: always fill the delta) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf index Source: https://rierek.github.io/okf-cli/reference/commands/okf-index/ ## Usage ```text usage: okf index [-h] [--full] [--json] [bundle] catalogue TOC (bundle names + one-line descriptions), or one bundle's index.md positional arguments: bundle bundle to print (default: the whole catalogue TOC) options: -h, --help show this help message and exit --full catalogue TOC: print the structured per-bundle block (description, concept count, directories, types, tags) instead of the default one-line name+description listing; with a bundle arg: print the structured bundle summary instead of the raw index.md --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf reindex Source: https://rierek.github.io/okf-cli/reference/commands/okf-reindex/ ## Usage ```text usage: okf reindex [-h] [--init] [--check] [--force] [--json] target (re)generate marker-bounded index listings: index.md one level, index-map.md the whole subtree positional arguments: target or / options: -h, --help show this help message and exit --init recursively migrate marker-less flat index.md in the subtree to managed marker blocks (bespoke multi-section indexes are skipped + flagged) --check dry-run: with --init preview conversions; else report stale + uncovered. No writes --force with --init, also flatten a pure multi-section listing (prose/tables still refuse) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf reference Source: https://rierek.github.io/okf-cli/reference/ Look-up pages for the okf command reference. Each card is one page. --- # okf archive Source: https://rierek.github.io/okf-cli/reference/commands/okf-archive/ ## Usage ```text usage: okf archive [-h] [--restore] [--apply] [--force] [--json] path move a concept into _archive//, or back with --restore (dry-run default) positional arguments: path /[.md]; with --restore: _archive//[.md]. The other end is derived, never typed options: -h, --help show this help message and exit --restore move the path back out of _archive/ instead of into it --apply perform the move (default: dry-run preview only) --force overwrite an existing destination, and proceed when the new basename is already taken and inbound wikilinks would turn ambiguous --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf check Source: https://rierek.github.io/okf-cli/reference/commands/okf-check/ ## Usage ```text usage: okf check [-h] [--strict] [--ignore PATTERN] [--json] [--types TYPES] [target] conformance scan: §9 hard rules + local invariants positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --strict count warnings toward the exit code; each finding keeps its severity --ignore PATTERN suppress rules: an exact rule ID, or a prefix ending in `*` (kc/*, okf/link/*); repeatable, added to .okf.toml ignore_rules; suppressed findings neither print nor count --json print one JSON envelope {command, ok, data, warnings, error} --types TYPES explicit TYPES.md path (overrides .okf.toml types_source) ``` --- # okf ctx Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx/ ## Usage ```text usage: okf ctx [-h] VERB ... context bundles: curated, ordered reading lists per topic (`ctx` lists them, `ctx ` shows one) positional arguments: VERB ctx verb; `ctx ` is short for `ctx show ` add add one entry to a ctx bundle check validate ctx bundles create create a ctx bundle from paths and/or an import file drop remove one entry from a ctx bundle edit open a ctx bundle in $VISUAL/$EDITOR/vi and validate it list list ctx bundles with entry and required counts move reorder an entry or change its mode outbound list the links each entry points to inbound list the files that link to each entry read print the full content of every required entry remove delete a ctx bundle and list files that still link to it rename rename a ctx bundle and rewrite inbound links show list a ctx in reading order with live metadata and token estimates toc print the headings of every entry options: -h, --help show this help message and exit ``` --- # okf ctx add Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-add/ ## Usage ```text usage: okf ctx add [-h] [--pos POS] [--mode {pflicht,nachschlag}] [--comment COMMENT] [--json] slug PATH add PATH as required entry (default: last position) or as reference entry positional arguments: slug ctx slug PATH concept or file path options: -h, --help show this help message and exit --pos POS required position (clamped to 1..n+1) --mode {pflicht,nachschlag} entry mode --comment COMMENT entry comment --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx check Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-check/ ## Usage ```text usage: okf ctx check [-h] [--stale] [--json] [slug] report ctx errors and warnings in the `check` line format; exit 1 on any error positional arguments: slug ctx slug (default: all) options: -h, --help show this help message and exit --stale warn for every member modified after the ctx file --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx create Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-create/ ## Usage ```text usage: okf ctx create [-h] [--import FILE] [--description DESCRIPTION] [--json] slug [PATH ...] create _ctx/.md; every path becomes a required entry, ordered alphabetically by normalised path positional arguments: slug new ctx slug: lowercase letters, digits, hyphens PATH concept or file path to add as required entry options: -h, --help show this help message and exit --import FILE read one path per line (blank and # lines skipped, text after a TAB is the entry comment) --description DESCRIPTION one-line description of the bundle --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx drop Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-drop/ ## Usage ```text usage: okf ctx drop [-h] [--json] slug PATH remove an entry (also when its file is gone); required positions are renumbered 1..n positional arguments: slug ctx slug PATH entry path options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx edit Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-edit/ ## Usage ```text usage: okf ctx edit [-h] [--json] slug open _ctx/.md in $VISUAL, else $EDITOR, else vi; afterwards refresh the _ctx index and run `ctx check` on the file; exit 1 on errors positional arguments: slug ctx slug options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx inbound Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-inbound/ ## Usage ```text usage: okf ctx inbound [-h] [--file X] [--include-internal] [--json] slug per entry, the files linking to it; other members are excluded unless --include-internal; files under _ctx/ are always excluded positional arguments: slug ctx slug options: -h, --help show this help message and exit --file X only the entry whose title, path or stem equals X (case-insensitive) --include-internal also list links between members of the ctx --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx list Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-list/ ## Usage ```text usage: okf ctx list [-h] [--for PATH] [--json] list ctx bundles: slug, description, entry and required counts options: -h, --help show this help message and exit --for PATH only bundles whose entries contain PATH (also when the file is gone) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx move Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-move/ ## Usage ```text usage: okf ctx move [-h] [--pos POS] [--mode {pflicht,nachschlag}] [--json] slug PATH move an entry to required position N (--pos) and/or change its mode; required positions are renumbered 1..n positional arguments: slug ctx slug PATH entry path options: -h, --help show this help message and exit --pos POS required position (clamped to 1..n) --mode {pflicht,nachschlag} new mode --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx outbound Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-outbound/ ## Usage ```text usage: okf ctx outbound [-h] [--file X] [--include-internal] [--json] slug per entry, markdown link targets; missing targets are marked [missing], dead entries [DEAD]; other members are excluded unless --include-internal positional arguments: slug ctx slug options: -h, --help show this help message and exit --file X only the entry whose title, path or stem equals X (case-insensitive) --include-internal also list links between members of the ctx --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx read Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-read/ ## Usage ```text usage: okf ctx read [-h] [--json] slug print every required entry of a ctx in position order, each after a `===== i/p =====` separator; reference entries are not printed; exit 1 when a required entry is dead positional arguments: slug ctx slug options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx remove Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-remove/ ## Usage ```text usage: okf ctx remove [-h] [--json] slug delete _ctx/.md, refresh the _ctx index and list every file that still links to the removed ctx positional arguments: slug ctx slug options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx rename Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-rename/ ## Usage ```text usage: okf ctx rename [-h] [--json] old new move _ctx/.md to _ctx/.md; inbound links and the _ctx index follow positional arguments: old current ctx slug new new ctx slug: lowercase letters, digits, hyphens options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx show Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-show/ ## Usage ```text usage: okf ctx show [-h] [--json] slug list the required entries in reading order, then the reference entries; `ctx ` is short for `ctx show `; exit 1 when an entry is dead positional arguments: slug ctx slug options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf ctx toc Source: https://rierek.github.io/okf-cli/reference/commands/okf-ctx-toc/ ## Usage ```text usage: okf ctx toc [-h] [--depth N] [--json] slug print per entry an H1 with the entry path, then the member's headings demoted by one level; dead entries are marked [DEAD] positional arguments: slug ctx slug options: -h, --help show this help message and exit --depth N only headings of original level <= N (default 0 = all) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf find Source: https://rierek.github.io/okf-cli/reference/commands/okf-find/ ## Usage ```text usage: okf find [-h] [--include-topics] [--limit LIMIT] [--where EXPR] [--breakdown] [--expand-tags] [--json] query hybrid ranked search: lexical FTS5/BM25 arm plus semantic arm positional arguments: query search terms; quote multi-word queries options: -h, --help show this help message and exit --include-topics widen beyond .okf.toml declared bundle to topic folders --limit LIMIT max results to print (default 10) --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --breakdown print per-hit contributing field(s)/term(s); output is byte-identical to today without this flag --expand-tags expand the query with semantically near registered tags before lexical scoring; measured against the full catalogue and found ineffective for the vocabulary-mismatch class it was built for, so it stays off by default (Q05/D17) --json print one JSON envelope {command, ok, data, warnings, error} rank / score lines: Every hit's headline number is `rank: N` — its 1-based position in the delivered list. It is the one scale comparable across a mixed-arm result, because it IS the list order, and it claims nothing about the two arms' raw scores being on one scale (D12: fuse_conditional hands the ranking to whichever arm owns it, it never blends the two by score). Those raw, non-comparable values stay out of the default output and show only with --breakdown, one `score:` line per hit naming which arm produced it: a/100 the FTS5/BM25 lexical arm, relative to this run's own top hit. a/(6 * n) the substring fallback used when no FTS index is available: per query term 3 for a title hit, 2 for a tag hit, 1 for a body hit, capped at one per field per term — so the maximum is 6 * number of terms. cosine X.XX the semantic arm's raw cosine similarity — printed on its own line, not folded into an a/100 fraction, because that shape would invite comparing it against a lexical score (D12, same rule as above). `provenance:` names the arm that produced the line. Degradations (index absent or stale, embedding backend unavailable) are announced on stderr, one note per degraded arm. filters: --where EXPR selects the concepts the answer may hold, whichever arm owns the ranking: the lexical, substring and semantic arms all apply it before the result is cut to --limit. Without `archived` in the expression, archived concepts are hidden; the semantic arm never covers them. ``` --- # okf fix-crosslinks Source: https://rierek.github.io/okf-cli/reference/commands/okf-fix-crosslinks/ ## Usage ```text usage: okf fix-crosslinks [-h] [--check] [--include-reserved] [--json] [target] rewrite links to catalogue-root-absolute form (/bundle/subdir/concept.md) positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --check dry-run: report without writing --include-reserved also rewrite RESERVED files (index.md, log.md, readme.md, index-map.md), narrowed to the text outside the managed block, which stays byte-identical (D-fxcr-01, off by default) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf fix-xml-tags Source: https://rierek.github.io/okf-cli/reference/commands/okf-fix-xml-tags/ ## Usage ```text usage: okf fix-xml-tags [-h] [--check] [--json] [target] wrap ``/`.` placeholders in inline code positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --check dry-run: report without writing --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf info Source: https://rierek.github.io/okf-cli/reference/commands/okf-info/ ## Usage ```text usage: okf info [-h] [--json] print catalogue schema docs (TYPES.md + PROPERTIES.md) options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf install Source: https://rierek.github.io/okf-cli/reference/commands/okf-install/ ## Usage ```text usage: okf install [-h] [--apply] [--uninstall] [--only NAMES] [--json] register okf, its agent skill, MCP server and shell completion with this machine's agents and shells (dry-run default; needs no catalogue) options: -h, --help show this help message and exit --apply perform the plan (default: print the plan, change nothing) --uninstall plan, or with --apply perform, the removal of everything install created --only NAMES comma-separated subset: claude, omp, codex, opencode, kimi, openclaw, hermes (agents), zsh, bash, fish (shells). Default: install for every agent and shell found on this machine; uninstall covers all of them --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf list Source: https://rierek.github.io/okf-cli/reference/commands/okf-list/ ## Usage ```text usage: okf list [-h] [--where EXPR] [--json] list the complete, unranked set of concepts a required --where expression selects options: -h, --help show this help message and exit --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf merge-tags Source: https://rierek.github.io/okf-cli/reference/commands/okf-merge-tags/ ## Usage ```text usage: okf merge-tags [-h] --into TAG [--where EXPR] [--apply] [--json] tags [tags ...] merge/rename one or more tags into a target tag across concept frontmatter positional arguments: tags source tag(s) to merge, e.g. workflow workflows options: -h, --help show this help message and exit --into TAG destination tag name --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --apply write changes (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf migrate Source: https://rierek.github.io/okf-cli/reference/commands/okf-migrate/ ## Usage ```text usage: okf migrate [-h] [--apply] [--json] [target] migrate frontmatter to OKF 0.2: `timestamp` to `generated` (git), `status` per D37 (dry-run default) positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --apply write the previewed changes (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf normalize-tags Source: https://rierek.github.io/okf-cli/reference/commands/okf-normalize-tags/ ## Usage ```text usage: okf normalize-tags [-h] [--where EXPR] [--apply] [--json] convert inline `tags: [...]` fields to YAML block-list form (local convention) options: -h, --help show this help message and exit --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --apply write changes (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf prime Source: https://rierek.github.io/okf-cli/reference/commands/okf-prime/ ## Usage ```text usage: okf prime [-h] [--full] [--compact] [--check-semantic] [--json] one-shot agent onboarding: usage guide, command directory, catalogue state and bundle TOC options: -h, --help show this help message and exit --full add types+tags to each bundle block --compact list bundles as name+count one-liners instead of the `index` blocks (description+directories) --check-semantic run the exact content-hash semantic coverage check (~2s on a large catalogue) instead of reporting only whether the semantic index is present --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf read Source: https://rierek.github.io/okf-cli/reference/commands/okf-read/ ## Usage ```text usage: okf read [-h] [--section HEADING | --frontmatter-only | --body-only] [--follow N] [--inbound] [--max-tokens K] [--json] path print one concept page positional arguments: path / of the page to print options: -h, --help show this help message and exit --section HEADING print only this section: heading text (case-insensitive, exact) up to the next heading of equal or higher level, sub-sections included; 'A > B' resolves B inside A --frontmatter-only print only the frontmatter block, delimiters included --body-only print everything after the frontmatter block --follow N also print the concepts the page links to, up to N hops deep, as one block (BFS, each page once); combines with --section (narrows the root), not with --frontmatter-only or --body-only --inbound with --follow: the pages linking to a page count as its neighbours too --max-tokens K with --follow: keep the block within K tokens (chars/4); the root always prints in full, from the first page that does not fit on, pages show title and description only --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf recent Source: https://rierek.github.io/okf-cli/reference/commands/okf-recent/ ## Usage ```text usage: okf recent [-h] [--limit LIMIT] [--where EXPR] [--json] list the most recently created concepts, newest first options: -h, --help show this help message and exit --limit LIMIT max results to print (default 10) --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf refs Source: https://rierek.github.io/okf-cli/reference/commands/okf-refs/ ## Usage ```text usage: okf refs [-h] [--scan-root SCAN_ROOT] [--in | --out] [--json] path list inbound and outbound markdown links of a file positional arguments: path target file (abs, or relative to --root) options: -h, --help show this help message and exit --scan-root SCAN_ROOT dir to scan (default: --root); pass repo root for repo-wide --in only inbound links --out only outbound links --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf registry Source: https://rierek.github.io/okf-cli/reference/commands/okf-registry/ ## Usage ```text usage: okf registry [-h] VERB ... reformat registry entries of TAGS.md or TYPES.md (table or block form); prose stays positional arguments: VERB registry verb convert rewrite every entry of one registry file in the target form options: -h, --help show this help message and exit ``` --- # okf registry convert Source: https://rierek.github.io/okf-cli/reference/commands/okf-registry-convert/ ## Usage ```text usage: okf registry convert [-h] --to {table,block} [--apply] [--json] {TAGS.md,TYPES.md} rewrite every entry of /TAGS.md or /TYPES.md in table or block form; text outside the entries stays byte for byte; previews a unified diff unless --apply positional arguments: {TAGS.md,TYPES.md} registry file under the catalogue root (types_source is not followed) options: -h, --help show this help message and exit --to {table,block} target form --apply write changes (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf remove-tags Source: https://rierek.github.io/okf-cli/reference/commands/okf-remove-tags/ ## Usage ```text usage: okf remove-tags [-h] [--where EXPR] [--apply] [--json] tags [tags ...] remove one or more tags from concept frontmatter (no merge target) positional arguments: tags tag(s) to remove, e.g. stale draft options: -h, --help show this help message and exit --where EXPR select concepts by an expression: `field op value` with op one of = != < <= > >= ~ (contains), or `field in (a, b)`; combine with and, or, not and parentheses. Fields: type, tag, path (/), archived (true or false), tier (unverified, machine-confirmed or human-reviewed), stale (true or false), status (an absent status is stable) and any frontmatter key (nested: a.b; through lists: verified.by, sources.resource). Timestamp keys (generated.at, verified.at, stale_after, usage_window.from/to, sources.last_modified, sources.usage_window.from/to) compare as instants and take an ISO 8601 value with an explicit offset: stale_after < 2026-10-01T00:00:00Z. Archived concepts are hidden unless the expression names `archived`. Quote a value holding spaces, + or other characters: 'a b'. Example: (type = Runbook and tag != draft) and path ~ home-lab/ --apply write changes (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf rename Source: https://rierek.github.io/okf-cli/reference/commands/okf-rename/ ## Usage ```text usage: okf rename [-h] [--apply] [--force] [--json] old new move a concept + rewrite every inbound/outbound link + reindex (dry-run default) positional arguments: old source concept path (/.md, abs or --root-relative) new destination path (same or different bundle) options: -h, --help show this help message and exit --apply perform the move (default: dry-run preview only) --force overwrite an existing destination, and proceed when the new basename is already taken and inbound wikilinks would turn ambiguous --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf rewrite-path Source: https://rierek.github.io/okf-cli/reference/commands/okf-rewrite-path/ ## Usage ```text usage: okf rewrite-path [-h] [--scan-root SCAN_ROOT] [--check] [--json] old new literal path-string rewrite across text files positional arguments: old literal path string to replace new replacement path string options: -h, --help show this help message and exit --scan-root SCAN_ROOT dir to scan (default: --root) --check dry-run: report, write nothing --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf sync-export Source: https://rierek.github.io/okf-cli/reference/commands/okf-sync-export/ ## Usage ```text usage: okf sync-export [-h] [--json] concept block-id print a canonical block's payload for materialisation elsewhere positional arguments: concept canonical concept (/) block-id canon block id declared in that concept options: -h, --help show this help message and exit --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf sync-insert Source: https://rierek.github.io/okf-cli/reference/commands/okf-sync-insert/ ## Usage ```text usage: okf sync-insert [-h] [--apply] [--json] consumer concept block-id materialise a canonical block inside a consumer concept (dry-run default) positional arguments: consumer consumer concept to insert into (/) concept canonical owner concept (/) block-id canon block id to materialise options: -h, --help show this help message and exit --apply write the block (default: dry-run preview only) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf sync-status Source: https://rierek.github.io/okf-cli/reference/commands/okf-sync-status/ ## Usage ```text usage: okf sync-status [-h] [--source SOURCE] [--block BLOCK_ID] [--json] [target] drift report for every materialised canon block positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --source SOURCE only blocks whose canonical source is this path (/bundle/concept.md) --block BLOCK_ID only blocks with this id --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf sync-update Source: https://rierek.github.io/okf-cli/reference/commands/okf-sync-update/ ## Usage ```text usage: okf sync-update [-h] [--source SOURCE] [--block BLOCK_ID] [--apply] [--force] [--json] [target] re-materialise outdated consumer blocks from their canon owner (dry-run default) positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --source SOURCE only blocks whose canonical source is this path (/bundle/concept.md) --block BLOCK_ID only blocks with this id --apply write changes (default: dry-run preview only) --force also overwrite a consumer copy that was modified locally (discards it) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf tags Source: https://rierek.github.io/okf-cli/reference/commands/okf-tags/ ## Usage ```text usage: okf tags [-h] [--limit LIMIT] [--singletons] [--register TAG DESCRIPTION] [--validate TAG [TAG ...]] [--json] [query ...] list all tags with usage counts, or fuzzy-search candidate tags positional arguments: query candidate tag(s) to fuzzy-search against the tag universe; omit for full audit dump options: -h, --help show this help message and exit --limit LIMIT max matches per candidate (default 8) --singletons full-dump mode only: show tags used exactly once --register TAG DESCRIPTION add a tag to the TAGS.md registry (closed-vocabulary allow-list); DESCRIPTION is required, max 150 chars; repeat the option for several tags --validate TAG [TAG ...] check tag(s) against TAGS.md; exit 1 if any are unregistered --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf toc Source: https://rierek.github.io/okf-cli/reference/commands/okf-toc/ ## Usage ```text usage: okf toc [-h] [--level LEVEL] [--json] path print a concept's headings (default: h2 only) positional arguments: path / to list headings for options: -h, --help show this help message and exit --level LEVEL heading level to list (default 2 = h2); 0 = full indented tree --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf tui Source: https://rierek.github.io/okf-cli/reference/commands/okf-tui/ ## Usage ```text usage: okf tui [-h] keyboard-first terminal UI: search the catalogue, screen hits options: -h, --help show this help message and exit settings ($XDG_CONFIG_HOME/okf/config.toml, default ~/.config/okf/config.toml): [tui] editor editor command; empty: $VISUAL, then $EDITOR, then vi theme latte, frappe, macchiato or mocha (default: mocha) accent #rrggbb; empty: the built-in accent [tui.tracker] where the `t` key sends maintenance tasks kind "command" or "file" command kind command: shell command line, run as `sh -c`; the task arrives in OKF_TASK_TITLE, OKF_TASK_BODY, OKF_TASK_PATH, OKF_TASK_REF and OKF_TASK_ROOT (quote them in the command) file kind file: absolute path, ~ allowed; an Obsidian Tasks line is appended state: $XDG_STATE_HOME/okf/tui.json (default ~/.local/state/okf/tui.json) environment: VOYAGE_API_KEY marks the semantic index as usable (shown by its dot) HERDR_ACTIVE_PANE_ID, HERDR_PANE_ID pane for the ctrl+s hand-off to an agent (herdr) Press ? in the TUI for the key help. Docs: https://rierek.github.io/okf-cli/features/tui/ ``` --- # okf verify Source: https://rierek.github.io/okf-cli/reference/commands/okf-verify/ ## Usage ```text usage: okf verify [-h] --by ACTOR [--json] concept record that a concept was checked against its sources positional arguments: concept / of the concept options: -h, --help show this help message and exit --by ACTOR who verified it: `human:`, `process:` or `/` --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf visualize Source: https://rierek.github.io/okf-cli/reference/commands/okf-visualize/ ## Usage ```text usage: okf visualize [-h] [--out OUT] [--json] bundle write a viz.html concept graph for a bundle (embeds every concept body; needs network for cytoscape/marked) positional arguments: bundle bundle to render the graph for options: -h, --help show this help message and exit --out OUT output path (default: /assets/viz.html) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf wikilinks Source: https://rierek.github.io/okf-cli/reference/commands/okf-wikilinks/ ## Usage ```text usage: okf wikilinks [-h] [--unresolved] [--json] [target] report Obsidian wikilinks ([[…]]) and whether each resolves to a concept positional arguments: target or / (default: whole catalogue) options: -h, --help show this help message and exit --unresolved list only unresolved and ambiguous occurrences (summary stays complete) --json print one JSON envelope {command, ok, data, warnings, error} ``` --- # okf tui: terminal UI Source: https://rierek.github.io/okf-cli/reference/commands/tui/ # `okf tui` `okf tui` opens a keyboard-first terminal UI on the catalogue. Look and keys follow `lif tui`. `okf tui --help` names the settings, the state file and the environment variables the TUI reads. :::note This page describes the search screen, the filters, the editor, the index dots, the session buffer, the hand-off of the buffer and maintenance tasks. ::: See [Terminal UI](/okf-cli/features/tui/) for a tour with recordings that show every key. ## Start ```text okf [--root ROOT | --global] tui ``` `okf tui` takes the catalogue root exactly as `okf find` does, see [Configuration](/okf-cli/reference/configuration/): `--root`, then `--global`, then the nearest catalogue above the current directory, then `root` of `~/.config/okf/config.toml`. - `okf tui` without `--root` opens the root `okf find` would use from the same directory. - `okf --root tui` opens ``. - With no root at all, `okf tui` prints the message of `okf find` and exits with code 3. The TUI does not start. The TUI starts on the search screen. The header shows the active root. The query field has the focus: it sits in a rounded frame titled `Query`, teal while it has the focus. Every pane shows its title on its top border: `Query`, `Results`, `Preview`, `Buffer`, `Note`. ## Search Type a query and press `enter`. The search runs only on `enter`, never while you type. It is `okf find --limit 50` on the active root, run in the same process: the list shows the same hits in the same order, at most 50, and scrolls. The semantic arm and its embedding call run as for `okf find`. - The search runs in the background. The TUI keeps reacting to keys and shows a spinner and `searching` at the right end of the query row until the answer arrives. - After `enter` the focus moves to the result list. Press `/` to edit the query again and `enter` to search again. A new search replaces the list. The answer of an older search never replaces a newer one. - The warnings `okf find` prints as `note:` lines on stderr (an absent or stale index, a skipped semantic arm) show as a toast, each text once per session. The note that the semantic arm owns the ranking does not show; the provenance row of each hit says it. - A search without a hit says `No hits for "".` in the list area. The result list shows title, type and bundle of every hit. The detail pane at the right shows the focused hit: - type, bundle, tags (from the frontmatter), provenance (`lexical` or `semantic`), trust tier and stale flag; - the absolute path `/.md`, which identifies the hit; - description and snippet; - the table of contents: the H2 and H3 headings in document order, the headings of `okf toc --level 0` at levels 2 and 3. `v` cycles the body between list and detail side by side, the list alone and the detail alone; `V` cycles back. `ctrl+d` and `ctrl+u` scroll a detail pane that is taller than the screen. ## Filters Below the query field a bar of six boxes narrows the search: **Root**, **Bundle**, **Type**, **Tag**, **Status** and **Tier**. You never write `--where` syntax. An empty box shows `(any)`. A box with values shows them (` +N` when several), so the active filters are always on screen. Press `f` to focus the bar, or step into it with `tab`. `tab` and `shift+tab` walk through the fields and leave the bar behind the last or before the first field; `←`/`→` do the same without leaving it. `enter` opens the menu of the focused field. `esc` leaves the bar. ### Value menus The menus of Bundle, Type, Tag, Status and Tier are multi-select. `↑`/`↓` move the cursor, `space`, `x` or `enter` select or deselect a value, `X` clears all filters, and `esc` closes the menu. A search that already ran starts again on `esc` when the choice changed, so the list always shows the chosen filters. Before the first search the choice waits and applies to it. The box is as wide as its longest value, up to the terminal width, so no value wraps; a value that is wider than the terminal ends in `…`. Press `/` to type a query: a query line appears and the menu lists only the values that match it (the letters of the query in order, case ignored), so long Bundle and Tag lists shrink to what you look for. While you type, every printable key is text. `enter` keeps the query and returns to the keys above; they now act on the filtered list. `esc` clears the query, a second `esc` closes the menu. The hint line of each menu names `enter`, `space` and `/`. The menus list only values that exist in the active root: - Bundle: the bundles `okf find` searches (the one named by `bundle` in `.okf.toml`, if set). - Type: the types registered in the root's `TYPES.md`. - Tag: the tags the root's concepts use. - Status: `draft`, `stable`, `deprecated`. Tier: `unverified`, `machine-confirmed`, `human-reviewed`. These values are the same in every root. ### How the choice becomes a filter The TUI builds a `--where` expression and compiles it with the compiler of [`--where`](/okf-cli/reference/where-filter/): - A bundle `B` is `path ~ B/`. - Values of one field are joined with `or`; different fields are joined with `and`. Bundle `handbook` and type `Runbook` give the hits of `okf find --where "path ~ handbook/ and type = Runbook"`. The types `Recipe` and `Runbook` give `(type = Recipe or type = Runbook)`. `X` outside the query field clears all values of all fields at once and keeps the root. ### Root The header and the Root box show the active root. `enter` on the Root box opens the root menu. It lists the active root, the root of `~/.config/okf/config.toml` and every root you used in earlier sessions, most recent first. Choose one with `↑`/`↓` and `enter` or `space`, or move below the list and type a path (`~` and relative paths work as for `--root`; there `space` and `/` are text). `/` narrows the root list as in the value menus. - The TUI runs the current query again on the new root and resets the index dots. The list of the old root is dropped. - Filter values the new root does not hold are dropped. A message names them. - A path that is not a directory, or holds no catalogue (`.okf.toml`, or `SPEC.md` and `TYPES.md`), is refused. The root stays, the menu stays open and a red message shows the reason. The TUI remembers a root when it starts on it or switches to it. The list is in `tui.json` in `$XDG_STATE_HOME/okf/` (`~/.local/state/okf/` when the variable is unset), at most 20 roots. It is a state file of the TUI, outside every catalogue and outside the user config, which the TUI never writes. ## Open a hit in the editor `e` on a focused hit opens its file in the editor and suspends the TUI. When the editor exits, the TUI comes back with the same root, query, hit list, order and focus, and reads that one hit again from disk: the detail pane shows the changed type, description, tags, snippet and headings. If the editor exits with an error, the TUI shows a red message and keeps the session. If the file is gone afterwards, the row stays and a red message says so. The editor is the first of these that is set: 1. the `editor` key of `[tui]` in the user config; 2. `$VISUAL`; 3. `$EDITOR`; 4. `vi`. A value may carry arguments, for example `code --wait`. The file name is appended as the last argument. ## Session buffer The session buffer collects hits across searches and roots, so that you can carry a curated selection through the session. It lives only as long as the TUI runs. - `space` on the focused hit adds it to the buffer; a second `space` removes it. A buffered hit carries a `◆` in the result list. The header shows the number of entries as `buffer N`. - A file is one entry. The identity is the absolute path with symlinks resolved, so the same file found by a later search, or under another spelling of the root (a symlink, a trailing slash), shows as buffered and is never added twice. - The buffer keeps its entries across new searches, and across a change of the root. Every entry keeps a snapshot of title, absolute path, type, bundle, tags, description, snippet, provenance, tier and stale flag, so it survives a root change. Editing a buffered hit with `e` refreshes its snapshot. - `n` opens a text area for the note of the focused hit. A hit that is not buffered yet joins the buffer when you save a note that is not empty. `ctrl+s` saves, `esc` cancels, `ctrl+e` opens the text in the editor and puts the edited text back into the text area when the editor exits. Enter starts a new line. Saving an empty note removes the note and keeps the entry. - `b` opens the buffer view. It lists the entries in the order they were added, each with title, type, bundle and a `✎` marker when the entry has a note. At the right, the `Preview` pane shows the entry under the cursor as the search screen shows a hit: type, bundle, tags, provenance, tier, stale flag, the absolute path, description, snippet and the table of contents. Below, the `Note` pane shows only the note of that entry, or `No note, press n to write one.` when it has none. The path does not appear in the `Note` pane. - In the buffer view: `j`/`k` move (the preview scrolls back to the top), `ctrl+d` and `ctrl+u` scroll a preview that is taller than its pane, `x` removes the entry under the cursor, `n` writes its note, `c` clears the whole buffer after a red confirm dialog (`enter` or `y` clears, `esc` or `n` keeps the buffer), and `b` or `esc` goes back to the search. ## Hand-off `ctrl+s` hands the buffer to an agent, `y` copies it to the clipboard. Both work on the search screen (outside the query field for `y`, which types there) and in the buffer view. In the note text area `ctrl+s` still saves the note. An empty buffer sends and copies nothing; a message says so. The text is Markdown, one block per entry in buffer order, under the header `Fundstellen aus okf ()`: ```markdown Fundstellen aus okf (2) ## Deploy runbook @/home/me/Knowledge-Catalogue/handbook/ops/deploy.md type: runbook · bundle: handbook · tags: ops, deploy Steps to roll out a release. ## Notes check the rollback step first ## Incident review @/home/me/Knowledge-Catalogue/handbook/ops/incident.md type: note · bundle: handbook ``` - The line under the path holds type, bundle and tags; a field the entry lacks is left out, so is the description line. - The note stands verbatim under a `## Notes` heading after a blank line. An entry without a note has no Notes heading. - The path is absolute (`@`), so an agent opens the file from any directory. `ctrl+s` picks the target from what herdr reports: 1. The pane under the popup (`HERDR_ACTIVE_PANE_ID`) runs an agent: the text goes into that pane, **without Enter**, the pane gets the focus, and the TUI exits. A `working` agent gets the text too. 2. The pane under the popup runs no agent: a picker lists the agents of `herdr agent list` with agent, status, title and directory; the agents of the current workspace come first. `↑`/`↓` choose, `enter` sends (text, focus, exit as above), `esc` closes the picker. Agents in state `blocked` wait at an approval or question dialog: the picker shows them grey and `enter` on one sends nothing. The same holds for a `blocked` agent under the popup: it is not sent to, and the picker opens. 3. herdr lists no agent, or the TUI runs outside herdr (no `HERDR_ACTIVE_PANE_ID` and no `HERDR_PANE_ID`): the text goes to the clipboard, the message `no agent found – copied to clipboard` shows, and the TUI stays open. When herdr fails to take the text, an error message shows and the TUI stays open with its buffer. The clipboard is set by an OSC 52 escape sequence (also inside tmux and screen) and, where `pbcopy`, `wl-copy`, `xclip` or `xsel` exists, by that tool. ## Maintenance tasks Press `t` on a focused hit in the result list to record a maintenance task about that concept in your own tracker, without leaving the TUI. A text area opens: the first line is the title, the rest is the body. `ctrl+s` saves, `esc` cancels, `ctrl+e` edits the text in the editor. Enter starts a new line. A text without a title is refused with a message and the box stays open. The tracker is the `[tui.tracker]` table of the user config; the section `[tui.tracker]` under Settings below describes the two kinds (`command` and `file`), the `OKF_TASK_*` variables and examples. Without a valid `[tui.tracker]`, `t` opens no text area and says what is wrong and which config file to change. - Saving runs in the background; the TUI keeps reacting to keys. A blue message reports the result: the first stdout line of the command, or the file that got the line. - Root, query, result list, focus and buffer stay as they were, and you are on the same hit afterwards. A task never enters the session buffer. - The `file` kind appends one Obsidian Tasks line, then the body as lines indented by two blanks; blank body lines are dropped: `- [ ] [<bundle>/<id>](<absolute path>) ➕ <YYYY-MM-DD>`. A body line that starts like a task (`- [ ]`, `* [x]`, `1. [ ]`) gets its `[` escaped (`- \[ ]`), so one save is one task. A path with a blank or a parenthesis stands in `<...>` in the link. - If the command exits non-zero, a red message shows its stderr (shortened to 400 characters) and the text area opens again with the text you wrote, so `ctrl+s` retries. `esc` drops the text. If another overlay is open when the failure arrives (a note box, the help, a second task box), it stays as it is; the red message names the failed task and you write it again. ## Keys Inside the query field the letter keys type, so `q` and `?` are text there; the header names `ctrl+k` for the command center, where `help` is one of the entries. Press `esc` to leave the field, then the global keys work. | Key | Action | |---|---| | `ctrl+k` | Command center, from every view, also inside the query field. See below. | | `?` | Help overlay, generated from the key map. Outside the query field; the command center has `help` everywhere. | | `q` | Quit. A dialog asks first: `enter` or `y` quits, `esc` or `n` cancels. | | `ctrl+c` | Quit at once, without a dialog, from every state. | | `tab`, `shift+tab` | Move the focus to the next or previous part of the screen. | | `enter` | In the query field: search. | | `j`, `k`, `↓`, `↑` | In the result list: focus the next or previous hit. | | `g`, `G` | In the result list: first or last hit. | | `e` | In the result list: open the focused hit in the editor. | | `t` | In the result list: record a maintenance task on the focused hit in the tracker. | | `space` | In the result list: add the focused hit to the buffer, or remove it. | | `n` | In the result list and in the buffer view: write the note of the focused hit or entry. | | `b` | In the result list: open the buffer view. In the buffer view: back to the search. | | `ctrl+s` | In the result list and in the buffer view: hand the buffer to an agent pane, else to the clipboard. | | `y` | In the result list and in the buffer view: copy the buffer to the clipboard. | | `x`, `c` | In the buffer view: remove the entry, or clear the buffer after a confirm dialog. | | `ctrl+s`, `ctrl+e` | In the note text area: save, or edit the text in the editor. `esc` cancels. | | `v`, `V` | Next or previous layout: list and detail, list, detail. | | `ctrl+d`, `ctrl+u` | Scroll the detail pane, or in the buffer view the preview pane, down or up. | | `/` | Focus the query field. | | `f` | Focus the filter bar, on its root field. Outside the query field only. | | `←`/`h`, `→`/`l` | In the filter bar: previous or next field. | | `enter` | In the filter bar: open the menu of the focused field. | | `space`, `x` | In a value menu: select or deselect the value under the cursor. | | `/` | In a value menu or the root menu: type a query that narrows the list. | | `X` | Clear every filter but the root. Outside the query field only. | | `r` | Refresh an index: a dialog offers the lexical or the semantic index. Outside the query field only. | | `esc` | Leave the query field. Outside it, dismiss a sticky message. Close an overlay. | A sticky message shows `esc: dismiss` and goes away by itself after 15 seconds. One overlay is open at a time and takes every key until it closes. The help overlay scrolls with `↑`/`↓`/`pgup`/`pgdn`, and `/` searches it. ## Command center `ctrl+k` opens a floating list of every action of the key map and every view, named `<verb>: <object>` (`add: note`, `add: task`, `view: buffer`, `view: types`, `view: tags`, `refresh: index`, `copy: buffer`, `send: buffer`, `help`, `quit`, ...). The key of an action stands at the right of its row. Type to filter fuzzily (the letters of the filter must appear in the name in order), `↑`/`↓` select, `enter` runs the entry, `esc` or `ctrl+k` closes the list. An open overlay keeps `ctrl+k`: no second overlay opens on top of it. An entry runs what its key runs, wherever the focus was: `add: note` opens the note box of the focused hit, `help` opens the help. An entry that needs a hit says so in a message when there is none; the buffer-view entries `remove: entry` and `clear: buffer` say that they work in the buffer view. The two views `view: types` and `view: tags` open only from the command center. `view: types` lists the types of the catalogue's `TYPES.md` with their descriptions, read as `okf info` reads the file. `view: tags` lists the tags with their usage counts, most used first, as `okf tags` lists them. Each view is one pane, titled `Types` or `Tags`, in the same frame as the other panes. `j`/`k` move, `g`/`G` jump, `esc` goes back to the search. ## Index dots and refresh The right part of the header shows two labelled dots, one for the lexical (FTS) index and one for the semantic index. The TUI checks both in the background when it starts and when the root changes; the dots appear within moments and never block input. | Dot | Lexical index | Semantic index | |---|---|---| | green | current | every concept embedded | | yellow | stale: the catalogue changed since the build | partial: some concepts have no current embedding | | red | absent | absent | | grey | not checked yet | unavailable: `VOYAGE_API_KEY` is not set | A half-filled dot (`◐`) means a check or a refresh of that index is running; it keeps its colour until the result is in. The dots are half filled from the start until the first check returns. Choosing a refresh while the check or another refresh of the same index is running starts nothing and shows a message; press `r` again when the dot is full. Press `r` outside the query field and choose: - `l` rebuilds the lexical index at once, as `okf build-index` does. A new index keeps the choice of the current one (with or without topics). - `s` embeds the concepts without a current embedding, as `okf build-semantic-index` does. A dialog names the number of concepts first and sends nothing to Voyage AI until you confirm with `enter` or `y`. Without `VOYAGE_API_KEY` the choice shows a message that names the key and nothing runs. Both refreshes run in the background. A message reports the end and the dot is checked again, so it turns green on success. If a refresh fails, the dot keeps its colour and a red message shows the error; `esc` outside the query field dismisses it. ## Settings: `[tui]` The TUI reads the `[tui]` table of `~/.config/okf/config.toml`, the same file as `root`: ```toml title="~/.config/okf/config.toml" [tui] editor = "nvim" # editor command; empty: $VISUAL, then $EDITOR, then vi theme = "mocha" # latte, frappe, macchiato or mocha (default mocha) accent = "#cba6f7" # #rrggbb for cursor and header; empty: the theme's accent ``` An invalid section does not stop the TUI. The TUI starts with the default settings and shows a red message that names the config file and the reason, for example an unknown theme, an accent that is not `#rrggbb`, or a value of the wrong type. Press `esc` outside the query field to dismiss it. A popup closes when the program exits, so an error on stderr would vanish before you read it; this is why the message is part of the screen. A terminal with 256 colours shows every coloured tone of the theme in its own hue: red stays red. Grey tones (text, borders, backgrounds) may turn grey. A root path that is too long for the header shows its end behind `…`, so the header stays one line. The root menu lists a directory once, even if the config and the state file spell it differently (for example `/tmp/x` and `/private/tmp/x`). ### `[tui.tracker]`: where maintenance tasks go The sub-table `[tui.tracker]` names the tracker that receives maintenance tasks (see Maintenance tasks above). The TUI has no code for a specific tracker. There are two kinds: ```toml title="~/.config/okf/config.toml" [tui.tracker] kind = "command" # "command" or "file" command = "lif create -t task -- \"$OKF_TASK_TITLE\"" # kind "command": a shell command line # file = "~/vault/Tasks/okf.md" # kind "file": absolute path, a leading ~ is expanded ``` - `kind = "command"` needs `command`. The TUI runs it as `sh -c <command>` with the task in environment variables and does not change the string. It never puts task text into the command line; quote the variables in the command (`"$OKF_TASK_TITLE"`), so the shell expands them as plain values. - `kind = "file"` needs `file`: an absolute path, or a path that starts with `~` or `~name` (the home directory of that account). Other path forms are refused, because the TUI's working directory is not a stable base. The TUI creates the file and its folder when they are absent. The TUI reads `[tui.tracker]` when you press `t`, so you can change it without a restart. A missing table, a missing or unknown `kind`, an empty `command` or `file`, a relative `file` and a config file that does not parse each give a red message that names the problem and the config path. The TUI opens no text area and records nothing then. | Variable | Value | |---|---| | `OKF_TASK_TITLE` | The first line of the text. | | `OKF_TASK_BODY` | The rest of the text, surrounding blank lines removed; may be empty. | | `OKF_TASK_PATH` | The absolute path of the hit's file. | | `OKF_TASK_REF` | The hit reference `<bundle>/<id>`, without `.md`. | | `OKF_TASK_ROOT` | The active root. | The environment of the TUI passes on as well. The command runs without a terminal on stdin; it must not ask questions. Its first stdout line shows in the toast. A non-zero exit shows stderr (the exit status when stderr is empty) in a red message, and the text area opens again. Examples. The tests cover both kinds with fake commands and files. The examples for lif, the Todoist CLI and Memos call real tools that the tests do not run; they are examples, untested here. Adapt them to your setup. ```toml title="lif (example, not run by the tests)" # Needs a lif store in the catalogue (`lif init` there). `cd` finds it from any working directory. [tui.tracker] kind = "command" command = '''cd "$OKF_TASK_ROOT" && lif create -t task --tag maintenance -d "$(printf '%s\n\nConcept: %s\n%s' "$OKF_TASK_BODY" "$OKF_TASK_REF" "$OKF_TASK_PATH")" -- "$OKF_TASK_TITLE"''' ``` ```toml title="Todoist CLI (example, not run by the tests)" # sachaos/todoist; other Todoist CLIs take different arguments. [tui.tracker] kind = "command" command = '''todoist add "$OKF_TASK_TITLE ($OKF_TASK_REF)"''' ``` ```toml title="Memos with curl (example, not run by the tests)" # Memos API v1; set MEMOS_URL and MEMOS_TOKEN in the environment that starts okf tui. The JSON # body is built by jq, so quotes and newlines in the task stay data. [tui.tracker] kind = "command" command = '''jq -n --arg t "$OKF_TASK_TITLE" --arg b "$OKF_TASK_BODY" --arg r "$OKF_TASK_REF" '{content: ("#maintenance " + $t + "\n\n" + $b + "\n\n" + $r)}' | curl -fsS -X POST "$MEMOS_URL/api/v1/memos" -H "Authorization: Bearer $MEMOS_TOKEN" -H 'Content-Type: application/json' -d @-''' ``` ```toml title="Obsidian Tasks (kind file)" [tui.tracker] kind = "file" file = "~/Obsidian/Tasks/okf-maintenance.md" ``` ## herdr popup herdr runs a command in a popup over the focused pane. Add an entry to `~/.config/herdr/config.toml`: ```toml title="~/.config/herdr/config.toml" # okf tui for the catalogue above the focused pane's cwd [[keys.command]] key = "prefix+shift+o" type = "popup" description = "okf tui" width = "100%" height = "100%" command = """ cd "${HERDR_ACTIVE_PANE_CWD:-$HOME}" || exit okf tui || { echo "okf tui exited with $?"; read -r _; } """ ``` The popup starts in the directory of the focused pane, so `okf tui` finds the catalogue above it, or the `root` of the user config. Pick any free key. Without a root `okf tui` prints its message and exits with code 3; the last line of the command keeps the popup open until you press enter, so you can read the message. --- # Configuration: root resolution, .okf.toml and config.toml Source: https://rierek.github.io/okf-cli/reference/configuration/ # okf configuration: `.okf.toml` and `config.toml` okf has two optional config files and a short list of environment variables. Both files are TOML. For terms such as *root* and *bundle* see the [Glossary](/okf-cli/reference/glossary/). ## Root resolution order Every command needs a catalogue root. okf picks it in this order and stops at the first hit: ```mermaid flowchart TD A[okf command] --> G{--global?} G -- yes --> U[root from ~/.config/okf/config.toml] G -- no --> R{--root PATH?} R -- yes --> P[PATH, made absolute] R -- no --> W[walk up from cwd: marker in the directory or its docs/ child] W -- found --> F[that directory] W -- nothing --> U U -- not set --> E[exit 3: no catalogue found] ``` 1. `--global`: use `root` from the user config, whatever lies above the working directory. `--global` together with `--root` is an error (exit 2: `--global and --root exclude each other`). Without a configured `root` the exit code is 3. 2. `--root PATH`: a leading `~` is expanded and the path is made absolute. A directory that does not exist is exit 3 (`root not found`). 3. The nearest catalogue above the working directory. In each directory okf tests the directory itself and its `docs/` child for a marker. A marker is `.okf.toml`, or `SPEC.md` together with `TYPES.md`. The `docs/` child lets you run okf from a repository whose catalogue lives in `<workspace>/docs`. 4. `root` in the user config. When nothing is found, okf exits 3: ```text $ (no user config, cwd=/tmp) okf list ... [stderr] ERROR: no catalogue found in cwd or its ancestors and no root in /tmp/okf-facts/home/.config/okf/config.toml; pass --root <path> [exit 3] ``` :::note `--root` and `--global` are options of `okf` itself. Put them before the command: `okf --root /path find git`. ::: ## Config files | File | Scope | Keys | |---|---|---| | `<root>/.okf.toml` | one catalogue | `bundle`, `types_source`, `ignore_rules`, `registry_format`, `wikilink_checked_bundles`, `[git_authors]` | | `~/.config/okf/config.toml` | the user | `root`, `[tui]` | ### `.okf.toml` | Key | Type | Effect | |---|---|---| | `bundle` | string | Names the one real bundle among scaffolding directories. Narrows index and search to it. | | `types_source` | path | Location of `TYPES.md`. `okf check --types` overrides it. | | `ignore_rules` | list | Rule ids that `check` skips. An entry ending in `*` is a prefix. | | `registry_format` | `"table"` or `"block"` | Form that `okf tags --register` writes. Any other value is exit 2: `registry_format must be "table" or "block"`. | | `wikilink_checked_bundles` | list | Bundles in which wikilinks are checked. | | `[git_authors]` | table | Maps an e-mail address to an actor (`human:<id>`). [`okf migrate`](/okf-cli/reference/commands/okf-migrate/) uses it. | Example: ```toml title=".okf.toml" registry_format = "table" wikilink_checked_bundles = ["handbook"] ignore_rules = ["kc/asset/entry-file-description"] [git_authors] "golden@example.org" = "human:golden" ``` ### `~/.config/okf/config.toml` The key `root` is an absolute path or a path that starts with `~/`. The table `[tui]` holds the settings of [`okf tui`](/okf-cli/reference/commands/tui/). ```toml title="~/.config/okf/config.toml" root = "~/knowledge" ``` A relative value is exit 2: ```text ERROR: /tmp/okf-facts/home/.config/okf/config.toml: root must be an absolute path or start with ~/ [exit 2] ``` With this file set, a command run from any directory finds the catalogue (step 4 above), and `--global` forces it even inside another catalogue. ## Environment variables | Variable | Read by | Effect | |---|---|---| | `XDG_CONFIG_HOME` | okf | Moves the user config to `$XDG_CONFIG_HOME/okf/config.toml`. A relative value is ignored. Default: `~/.config`. | | `VOYAGE_API_KEY` | `okf build-semantic-index` | API key for the Voyage embeddings (`voyage-3-large`). Without it the command exits 0 and writes nothing. A CI job cannot detect a missing key from the exit code. | | `COLUMNS` | `okf --help` and command help | Width for wrapping help text, when it is a positive integer. | | `OKF_BIN` | the MCP server | Path of the `okf` binary the server runs. Any non-empty value is used as is. Otherwise the server tries `<package>/bin/okf`, then `<package>/go/bin/okf`, then `okf` on `PATH`. | | `OKF_MCP_MAX_CHARS` | the MCP server | Output ceiling, default `120000`. A longer result is an error that asks the caller to narrow the call. | Embeddings come from two places today: the Voyage API (`voyage-3-large`, needs `VOYAGE_API_KEY`) and a local Ollama server with `bge-m3` at `http://localhost:11434/api/embed` (1024 dimensions). The endpoint, model and key are not configurable. ## Files okf writes | Path | Written by | Content | |---|---|---| | `<root>/.fts/fts.sqlite` | `okf build-index` | Lexical search index (SQLite FTS5). Git shows it as untracked unless you ignore it. | | `<root>/.tagvec/tag_vectors.sqlite` | `okf build-semantic-index`, `okf tags --register` | Tag and concept vectors for the semantic arm. | | `<root>/_ctx/*.md` | `okf ctx create` and the other ctx verbs | One ctx bundle per file. | | `index.md`, `index-map.md` in a bundle | `okf reindex` | Listing between `<!-- okf:index -->` markers. Text outside the markers is kept. | `okf install` is a dry run until you pass `--apply`. With `--apply` it writes under `$HOME`: - the agent skill: `~/.claude/skills/okf` (Claude), `~/.agents/skills/okf` (Codex and omp), `~/.hermes/skills/okf`, `~/.openclaw/skills/okf`; - an MCP registration for each detected agent (for Claude through `claude mcp add`, for omp in `~/.omp/agent/mcp.json`); - shell completion: `~/.zsh/completions/_okf` plus a managed block in `.zshrc`; for bash `${XDG_DATA_HOME:-~/.local/share}/bash-completion/completions/okf`; for fish `$XDG_CONFIG_HOME/fish/completions/okf.fish`; - a wrapper `~/.local/bin/okf`, only for a source checkout when `okf` is not on `PATH`. `okf install --uninstall --apply` removes what install created. See [`okf install`](/okf-cli/reference/commands/okf-install/). --- # Frontmatter and types: OKF fields and provenance Source: https://rierek.github.io/okf-cli/reference/frontmatter-and-types/ # OKF frontmatter and Open Knowledge Format types A concept is a Markdown file that starts with a YAML block. This page lists the keys okf reads and writes, the type vocabulary, and the provenance fields (`generated`, `sources`, `verified`, `stale_after`). Terms are defined in the [Glossary](/okf-cli/reference/glossary/). The format itself is specified by the [Open Knowledge Format](https://github.com/GoogleCloudPlatform/open-knowledge-format). A concept as okf reads it (output of [`okf read`](/okf-cli/reference/commands/okf-read/) on the test catalogue): ```yaml title="handbook/ops/deploy.md" --- type: Runbook title: Deploy description: "How to deploy: build, ship, verify" tags: - ops - deploy generated: by: human:golden at: 2026-01-02T10:00:00Z verified: - by: human:golden at: 2026-01-03T10:00:00Z sources: - resource: https://example.org/deploy-docs stale_after: 2099-01-01T00:00:00Z --- ``` ## Frontmatter fields [`okf info`](/okf-cli/reference/commands/okf-info/) prints the `PROPERTIES.md` of your catalogue. The fields below are the ones `okf check` knows. | Field | Required | Content | Check rule | |---|---|---|---| | `type` | yes | A type registered in `TYPES.md`. Must not be empty. | `okf/frontmatter/type-nonempty`, `kc/type/registered` | | `title` | no | Display title. | | | `description` | no | One-line summary. | | | `tags` | no | Tags registered in `TAGS.md`, written as a block list. | `kc/tag/registered`, `kc/frontmatter/tags-block-form` | | `status` | no | `draft`, `stable` or `deprecated`. Absent means `stable`. | `okf/lifecycle/status` | | `generated` | no | `by` and `at`: who or what produced the text. | `okf/trust/generated` | | `sources` | no | List of `{resource, ...}`. | `okf/provenance/*` | | `verified` | no | List of `{by, at}`. | `okf/trust/verified` | | `stale_after` | no | Instant after which the concept is stale. | `okf/lifecycle/stale-after` | | `superseded_by` | no | Concept that replaces this one. Used on Decision concepts. | `kc/supersede/resolves`, `kc/supersede/scope` | Reserved files (`index.md`, `log.md`, `readme.md`, `index-map.md`) are not concepts and carry no `type` (rule `okf/type/reserved-no-type`). An actor (`by`) is `human:<id>`, `process:<id>` or `<producer>/<version>`. An instant is ISO 8601 with an explicit offset (rule `okf/format/timestamp`). ## Types `TYPES.md` in the catalogue root is the type vocabulary: a table `| Type | Meaning |`. The test catalogue registers: | Type | Meaning | |---|---| | `Concept` | An explained idea | | `Context` | A curated reading list | | `Guide` | Step-by-step instructions | | `Runbook` | Operational procedure | | `Rule` | A binding statement | | `Recipe` | Food preparation | | `Note` | Loose observation | [`okf check`](/okf-cli/reference/commands/okf-check/) reports a concept whose `type` is missing from this table (`kc/type/registered`). Set `types_source` in [`.okf.toml`](/okf-cli/reference/configuration/#okftoml), or pass `okf check --types`, to read the table from another file. `okf registry convert TYPES.md --to block` switches the file between table and block form. ## Provenance OKF v0.2 adds fields that say where a concept came from and how far to trust it. - `generated`: `by` (required, non-empty) and `at`. Rule `okf/trust/generated` demands a `by`. - `sources`: a list of entries, each with a `resource`. `sources.last_modified` and `sources.usage_window.from` / `.to` are timestamp keys you can filter on with [`--where`](/okf-cli/reference/where-filter/). - `verified`: a list of `{by, at}`. [`okf verify`](/okf-cli/reference/commands/okf-verify/) writes an entry. - `stale_after`: an instant. A concept is **stale** once that instant has passed. okf derives a **trust tier** from `verified` only: | Tier | Meaning | |---|---| | `unverified` | No entry names a `by`. | | `human-reviewed` | At least one entry has a `human:<id>` actor. | | `machine-confirmed` | Any other verified entry. | `--json` output of `list`, `find`, `read` and `recent` shows the tier as `tier` and the stale state as `stale`. The v0.2 rules of `check` run in bundles whose root `index.md` declares `okf_version: "0.2"`. [`okf migrate`](/okf-cli/reference/commands/okf-migrate/) moves a catalogue to 0.2. See [Migrate to OKF 0.2](/okf-cli/use-cases/migrate-to-okf-0-2/). ## Editing rules Commands that change frontmatter (`merge-tags`, `normalize-tags`, `remove-tags`) patch it line by line, so the existing formatting is kept. It refuses shapes it cannot edit safely: - a lone CR line ending; - a flow-mapping root; - anchors or aliases on the edited value; - a comment inside a multi-line flow list; - some list layouts. A refused file ends the whole run with exit 2 and the message `<path>: <reason>`. okf writes nothing, also in a dry run. Fix the file by hand, then run again. :::note `okf check` reports invalid frontmatter as `okf/frontmatter/parseable`. ::: Read next: [Conformance checks](/okf-cli/features/conformance-checks/), [Safe edits](/okf-cli/features/safe-edits/). --- # Glossary: catalogue, bundle, concept and other okf terms Source: https://rierek.github.io/okf-cli/reference/glossary/ # OKF glossary: catalogue, bundle, concept Every other page of this documentation uses these terms in exactly this meaning. For the file format itself see [Frontmatter and types](/okf-cli/reference/frontmatter-and-types/). For the settings that select a catalogue see [Configuration](/okf-cli/reference/configuration/). ## Terms ### Catalogue A directory that holds knowledge as Markdown files. It is the *root* that okf works on. A directory counts as a catalogue when it contains `.okf.toml`, or `SPEC.md` together with `TYPES.md`. The catalogue-level files are `TYPES.md`, `PROPERTIES.md`, `TAGS.md`, `SPEC.md` and `.okf.toml`. ### Bundle An immediate subdirectory of the catalogue whose name does not start with `.` or `_`. `_ctx/` is the one `_` directory that counts as a bundle (it holds the ctx bundles). `_archive/` holds archived concepts as `_archive/<bundle>/...`; search and list hide it unless you name `archived`. Not to be confused with a *ctx bundle*. ### Concept A `.md` file in a bundle that has YAML frontmatter. Its *concept-id* is its path below the bundle without `.md`. The file `handbook/ops/deploy.md` is the concept `ops/deploy` in the bundle `handbook`. Output shows `path` as `<bundle>/<concept-id>`. The files `index.md`, `log.md`, `readme.md` and `index-map.md` are reserved: they are not concepts and never carry `type`. ### Type The `type` frontmatter value of a concept, for example `Rule` or `Guide`. A type must be registered in `TYPES.md`. See [Types](/okf-cli/reference/frontmatter-and-types/#types). ### Frontmatter The YAML block at the top of a concept, between two `---` lines. It carries `type`, `title`, `description`, `tags` and the provenance fields. See [Frontmatter fields](/okf-cli/reference/frontmatter-and-types/#frontmatter-fields). ctx bundle : A curated reading list in `_ctx/<slug>.md`: a table of entries in reading order. Each entry has a position and a mode, `pflicht` (read first) or `nachschlag` (look up when needed). Titles and types of the entries are read live from the concept files. Commands: [`okf ctx`](/okf-cli/reference/commands/okf-ctx/). See [Context bundles](/okf-cli/features/context-bundles/). ### Canon block Text with exactly one owner. The owner concept declares it between `<!-- okf:canon id=<block-id> -->` and `<!-- /okf:canon -->`. A consumer concept holds a *sync block*, a managed copy of the payload that carries `source=` and a hash. Do not edit a sync block by hand. Commands: `sync-export`, `sync-insert`, `sync-status`, `sync-update`. See [Canon blocks](/okf-cli/features/canon-blocks/). ### Registry A closed vocabulary kept in a catalogue file. `TAGS.md` is the tag registry, `TYPES.md` is the type registry. Both are tables (`| Tag | Description |`, `| Type | Meaning |`), or block form. [`okf registry convert`](/okf-cli/reference/commands/okf-registry-convert/) switches between the two forms. See [Tags and the registry](/okf-cli/features/tags-and-registry/). ### Envelope The JSON object that every command prints with `--json`: `{command, ok, data, warnings, error}`. See [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/). ### Root The catalogue directory a command works on. okf resolves it from `--root`, from the nearest catalogue above the working directory, or from the user config. See [Root resolution order](/okf-cli/reference/configuration/#root-resolution-order). ### FTS index The lexical search index `.fts/fts.sqlite` (SQLite FTS5, ranked with bm25: title weighs 3.0, tags 2.0, body 1.0). [`okf build-index`](/okf-cli/reference/commands/okf-build-index/) writes it. ### Semantic arm The embedding-based half of search. It uses vectors in `.tagvec/tag_vectors.sqlite`, written by [`okf build-semantic-index`](/okf-cli/reference/commands/okf-build-semantic-index/). Models: `voyage-3-large` (Voyage API, needs `VOYAGE_API_KEY`) and `bge-m3` (local server at `http://localhost:11434`). See [Search](/okf-cli/features/search/). ### Dry run The default of most commands that change files, for example `install`, `migrate`, `merge-tags` and `sync-update`. okf prints what it would change and writes nothing. Not every changing command has a dry run: `tags --register` writes at once. ### Apply Running a command with `--apply` so that okf writes the change. The flag list on each command page states whether a command has it. --- # JSON output and exit codes (machine-readable okf) Source: https://rierek.github.io/okf-cli/reference/json-output-and-exit-codes/ Scripts, CI jobs and agents consume okf through the `--json` envelope and the exit code. Both are the same on every command. Runs on this page were measured with okf 0.2.5 on the golden test catalogue. ## Envelope Every command that takes `--json` prints exactly one line of JSON on stdout. The key order is fixed. ```json {"command": "find", "ok": true, "data": {"hits": ["..."], "nearest_tags": []}, "warnings": ["lexical index absent — falling back to substring scan; run `okf build-index` for BM25 ranking"], "error": null} ``` | Key | Content | |---|---| | `command` | the command as typed, verb included (`find`, `ctx show`) | | `ok` | `true` on success | | `data` | the result, or `null` | | `warnings` | the notes that text mode prints on stderr as `note: ...` | | `error` | `null`, or `{"kind", "code", "message"}` | What `data` holds: | Command family | `data` | |---|---| | `find` | `{"hits": [...], "nearest_tags": [...]}`; a hit has `rank`, `title`, `type`, `path`, `description`, `snippet`, `provenance`, `tier`, `stale` | | `list` | array of `{title, type, path, description, tier, stale}` | | `read` | `{"path", "text"}` | | `toc` | `{"path", "title", "type", "headings": [{"level", "text"}]}` | | `tags` | `{"tags": [{"tag", "count"}]}` | | `check` | `{"findings": [{"rule", "severity", "path", "message"}]}` | | `sync-status` | `{"blocks": [...], "totals": {...}}` | Other commands use their own shape; see the page of each command. Usage errors from the argument parser are not wrapped: they print usage on stderr and exit 2 without an envelope. ## Exit-code classes Every command ends with exactly one class. An error without a class exits 1. | Code | `error.kind` | Meaning | Returned by | |---|---|---|---| | 0 | none | success | every command; also `find` with a degraded arm and `wikilinks` with unresolved links | | 1 | `internal` | unexpected error, command not ported, blocked install step | `install` (blocked step); panics | | 2 | `invalid_input` | usage error, malformed `--where`, refused frontmatter write, `rename` without `.md` paths, `recent` outside a git repository, `--global` together with `--root` | all commands | | 3 | `not_found` | catalogue root, bundle, concept, file, ctx slug or block not found | navigation, search, ctx, tags, sync-*, move, check target | | 4 | `non_conformant` | data does not conform | `check` (errors; with `--strict` also warnings); `tags --validate` with an unregistered tag; `ctx show`, `ctx read`, `ctx check`, `ctx edit` with a dead entry; `sync-insert` consumer not valid UTF-8 | | 5 | `drift` | something is out of date | `sync-status` (any block not `synced`); `sync-update` (refused or unresolvable blocks); `reindex --check` (stale or uncovered index) | | 120 | none | stdout could not be written (a broken pipe is no failure) | any command | Observed runs of the error classes on commands that mutate or check a catalogue (run without `--apply`): | Command | Input | Exit | |---|---|---| | `archive` | missing concept | 3 | | `ctx add`, `ctx drop`, `ctx move`, `ctx remove`, `ctx rename` | unknown slug | 3 | | `ctx create` | slug already exists | 2 | | `fix-crosslinks`, `migrate`, `sync-update` | target is not a directory | 3 | | `merge-tags`, `normalize-tags`, `remove-tags` | malformed `--where` | 2 | | `registry convert` | file other than `TAGS.md` or `TYPES.md` | 2 (usage error) | | `rename` | paths without `.md` | 2 | | `sync-insert` | block not found | 3 | | `rewrite-path` | `old` matches nothing | 0, empty `files` | Known deviations from the help text: - `tags --validate` and the ctx verbs `show`, `read`, `check`, `edit` exit 4 where the help says 1 [irxe] [vf2r]. - A blocked `install` plan exits 1 with a generic message [fs8c]. ## Failures A failure prints the same envelope with `ok: false`. When the failure carries a result, it stays in `data`; otherwise `data` is `null`. The exit code equals `error.code`. Failure without data: ```text $ okf read handbook/nope --json {"command": "read", "ok": false, "data": null, "warnings": [], "error": {"kind": "not_found", "code": 3, "message": "not found: handbook/nope"}} [exit 3] ``` Failure with data (`check`, shortened): ```text $ okf check recipes --json {"command": "check", "ok": false, "data": {"findings": [{"rule": "okf/type/reserved-no-type", "severity": "error", "path": "recipes/readme.md", "message": "reserved file must not carry `type`"}, ...]}, ...} [exit 4] ``` ```text $ okf tags --validate nosuchtag --json {"command": "tags", "ok": false, "data": {"closed_vocabulary": true, "unknown": ["nosuchtag"]}, "warnings": [], "error": {"kind": "non_conformant", "code": 4, "message": "non_conformant"}} [exit 4] $ okf sync-status --json {"command": "sync-status", "ok": false, "data": {"blocks": [...], "totals": {"synced": 1, "source-changed": 1, ...}}, ...} [exit 5] ``` In text mode the error message goes to stderr, for example `--where: unknown operator '==' at position 6`. An unclassified error or a panic becomes `kind: internal`, exit 1; its text also goes to stderr. --- # Where filter: the okf --where expression Source: https://rierek.github.io/okf-cli/reference/where-filter/ The `--where` option is the one filter language of okf. It selects concepts (Markdown files with frontmatter) by `type`, `tag`, `path`, status fields and any frontmatter key. This page is the reference for the okf where filter. Every `okf find` and `okf list` run on this page was measured with okf 0.2.5 on the golden test catalogue. ## Grammar An expression is a boolean combination of comparisons. ```text expr := or-expr or-expr := and-expr ("or" and-expr)* and-expr := not-expr ("and" not-expr)* not-expr := "not" not-expr | atom atom := "(" expr ")" | field op value | field "in" "(" value ("," value)* ")" ``` | Operator | Meaning | |---|---| | `=` `!=` | equal, not equal | | `<` `<=` `>` `>=` | ordered comparison (timestamps compare as instants) | | `~` | contains | | `in (a, b)` | equals one of the listed values | | `and` `or` `not` | combine terms; `not` binds tighter than `and`, `and` tighter than `or` | Precedence changes the result. Without parentheses `and` is applied first: ```text $ okf list --where 'type = Guide or type = Runbook and tag = nosuch' ------ title: Getting started type: Guide path: handbook/getting-started ... list: 1 concept(s) $ okf list --where '(type = Guide or type = Runbook) and tag = nosuch' (no matches) ``` Quoting rules: - The characters `!<>=~` never belong to a bare word, so `path~a/b` and `x!=y` need no spaces. - A bare word may hold letters, digits and `-_./:`. - Quote every other value with `'...'` or `"..."`, for example `title = "Getting started"`. :::note Archived concepts are hidden unless the expression names `archived`. ::: ## Fields | Field | Matches | |---|---| | `type` | the concept type | | `tag` | one of the concept tags | | `path` | `<bundle>/<concept-id>` | | `archived` | `true` or `false` | | `tier` | `unverified`, `machine-confirmed` or `human-reviewed` | | `stale` | `true` or `false` | | `status` | the status; an absent status counts as `stable` | | any frontmatter key | a top-level key such as `title`; nested with `a.b`; through lists with `verified.by` or `sources.resource` | An unknown frontmatter key matches nothing. It is not an error: `color = red` returns no concepts and exits 0. Timestamp keys compare as instants: `generated.at`, `verified.at`, `stale_after`, `usage_window.from`, `usage_window.to`, `sources.last_modified`, `sources.usage_window.from`, `sources.usage_window.to`. The value must be ISO 8601 with an explicit offset, for example `stale_after < 2026-10-01T00:00:00Z`. Quote a value that holds `+`. ## Examples Select by type, then by a list of tags (`--json` output is shortened to the paths): ```text $ okf list --where 'type = Runbook' --json {"command": "list", "ok": true, "data": [{"title": "Deploy", "type": "Runbook", "path": "handbook/ops/deploy", "description": "How to deploy: build, ship, verify", "tier": "human-reviewed", "stale": false}, {"title": "Setup", "type": "Runbook", "path": "handbook/ops/setup", "description": "Local setup", "tier": "unverified", "stale": false}], "warnings": [], "error": null} $ okf list --where 'tag in (deploy, glossary)' ... list: 3 concept(s) ``` More runs, result as paths: | Expression | Result | |---|---| | `type = Guide or type = Runbook` | handbook/ops/deploy, handbook/getting-started, handbook/ops/setup | | `path ~ ops/ and not tag = deploy` | handbook/ops/incident-2026, handbook/ops/retired-tool, handbook/ops/setup | | `status = deprecated` | handbook/ops/retired-tool | | `stale = true` | handbook/ops/incident-2026 | | `archived = true` | _archive/handbook/old-process | | `title = "Getting started"` | handbook/getting-started | | `verified.by = human:golden` | handbook/ops/deploy | | `generated.at < 2026-01-01T00:00:00Z` | no concepts | ## Errors A malformed filter is exit 2, class `invalid_input` (see [JSON output and exit codes](/okf-cli/reference/json-output-and-exit-codes/)). The message names the problem and the position. ```text $ okf list --where 'type ==' ; echo "[exit $?]" --where: unknown operator '==' at position 6 [exit 2] $ okf list --where 'type =' --where: expected a value, found end of expression at position 7 $ okf list --where 'tag = a b' --where: expected 'and', 'or' or end of expression, found 'b' at position 9 $ okf list --where 'stale_after < 2026-10-01' --where: stale_after compares instants: = != < <= > >= or in, with an ISO 8601 datetime and an explicit offset, e.g. 2026-09-23T00:00:00Z (quote a value that holds +) ``` With `--json` the same failure is an error envelope: ```text $ okf list --where 'type == Note' --json {"command": "list", "ok": false, "data": null, "warnings": [], "error": {"kind": "invalid_input", "code": 2, "message": "--where: unknown operator '==' at position 6"}} ``` The parser accepts about 247 nested parentheses or 989 `not` in a row. ## Commands that take --where The same expression language works on six commands: - [`okf find`](/okf-cli/reference/commands/okf-find/): narrows the ranked hits. - [`okf list`](/okf-cli/reference/commands/okf-list/): the complete set the expression selects. - [`okf recent`](/okf-cli/reference/commands/okf-recent/) - [`okf merge-tags`](/okf-cli/reference/commands/okf-merge-tags/) - [`okf normalize-tags`](/okf-cli/reference/commands/okf-normalize-tags/) - [`okf remove-tags`](/okf-cli/reference/commands/okf-remove-tags/) --- # okf blog Source: https://rierek.github.io/okf-cli/blog/ **Use cases show what okf makes possible; a blog post is how it works for us.** On conflict the [reference](/okf-cli/reference/) wins. ## Posts Newest first. - [Context management with work-in-progress](/okf-cli/blog/context-management-with-work-in-progress/) (2026-10-18): hand an agent a small `ctx` bundle, keep lasting knowledge in the catalogue. - [Tag hygiene](/okf-cli/blog/tag-hygiene/) (2026-10-18): register tags, validate them, block unregistered tags in CI. - [Canon in the catalogue](/okf-cli/blog/canon-in-the-catalogue/) (2026-10-18): keep shared knowledge in one place. --- # Canon in the catalogue Source: https://rierek.github.io/okf-cli/blog/canon-in-the-catalogue/ **One truth, in one place. Everything else points at it.** A shared canon in the catalogue makes knowledge shareable, checkable and current. ## The problem The same rule lives in many projects. The copies drift apart, and nobody knows which one holds. ## The idea Keep one shared bundle in the catalogue, for example an architecture canon: layers, naming, the ADR pattern. Projects refer to it. They do not copy it. ## What it gives you - One source of truth. - New projects start with a frame of reference. - Agents load the canon through `ctx` and get the same rules every time. - Reviews measure against a fixed yardstick. ## How okf does it A canon block has an owner. [`okf sync-insert`](/okf-cli/reference/commands/okf-sync-insert/) materialises it in the consumer, so the consumer stays readable even without the catalogue. [`okf sync-status`](/okf-cli/reference/commands/okf-sync-status/) shows drift. [`okf sync-update`](/okf-cli/reference/commands/okf-sync-update/) pulls outdated blocks up to date. [`okf sync-export`](/okf-cli/reference/commands/okf-sync-export/) prints a block's payload. Both writing commands print a diff first and write only with `--apply`. ```text okf sync-status okf sync-update --apply ``` ## Limits - A canon needs a keeper. - Too much canon sets hard. Only what is stable belongs in it. - Project-specific rules stay local. ## In short The canon makes knowledge shareable, checkable and current. It is the base for [context management](/okf-cli/blog/context-management-with-work-in-progress/) and for a [controlled tag vocabulary](/okf-cli/blog/tag-hygiene/). See also [Canon blocks](/okf-cli/features/canon-blocks/) and [Define canon knowledge](/okf-cli/use-cases/define-canon-knowledge/). --- # Context management with work-in-progress Source: https://rierek.github.io/okf-cli/blog/context-management-with-work-in-progress/ **An agent needs a small, targeted context. Never a full dump.** Keep the work in a topic folder, keep the lasting knowledge in the catalogue, and hand the agent a `ctx` bundle. ## The problem An agent session starts empty. Load everything and the useful lines drown in noise. Load nothing and the agent guesses. It gets worse when notes, plans and lasting knowledge sit in the same place: nobody can tell what is still true. ## One topic, one folder Give each topic its own folder: `work-in-progress/<topic-slug>/`. It holds the thinking and planning chain, drafts and intermediate results. It is short-lived, but fully traceable. Every file in it carries OKF frontmatter, and `type` is never empty. That keeps the folder readable for `okf` and for agents. ## Promote what lasts Ask one question of each statement: is it lasting and reusable? - **Yes:** it moves into the catalogue. - **No:** it stays in the topic, or you discard it. Each claim gets exactly one home. After the move, the topic points at the catalogue instead of copying it. Two copies drift apart. ## Agents read through `ctx` A `ctx` bundle is a curated reading list in reading order. [`okf ctx show`](/okf-cli/reference/commands/okf-ctx-show/) prints it with the title and type of every entry, read live from the files, so the list does not go stale. Each entry has a mode: | Mode | Meaning | |---|---| | `pflicht` | The agent must read it, in the given position. | | `nachschlag` | Reference: look it up when needed. | An example: load the Git canon before a hotfix, and nothing else. Put the bundle in the prompt instead of a list of paths. ```text okf ctx show onboarding ``` ## In practice 1. Create the topic folder. 2. Work in it. 3. Move what lasts into the catalogue. 4. Archive the topic. Hand over context with `ctx`, not with path lists in the prompt. ## In short - The topic folder is the workbench. - The catalogue is the memory. - `ctx` is the interface to the agent. See also [Context bundles](/okf-cli/features/context-bundles/) and [Onboard agents](/okf-cli/use-cases/onboard-agents/). --- # Tag hygiene Source: https://rierek.github.io/okf-cli/blog/tag-hygiene/ **Tags only help while their number stays small and under control.** Register each tag, validate the catalogue, and let CI refuse tags nobody approved. ## The problem Tags grow wild: `auth`, `authn`, `authentication`, `Auth`. Search fragments, and a filter returns random hits. ## The rules - Keep tags few and give them on purpose. - Register every tag in `TAGS.md`, each with a description. - A description has at most 150 characters, one line and no `|`. `okf tags --register` refuses anything else. ```text okf tags --register operations "Running and operating services" ``` ## Check `okf tags --validate` reports tags that are not in the registry and exits with a non-zero code, so a script can act on it. Test for non-zero rather than for one value: the exit code and the help text disagree today ([irxe], see [Known limitations](/okf-cli/known-limitations/)). ## A CI gate Let CI run `okf tags --validate` and fail on non-zero. A new, unregistered tag then blocks the merge. Whoever wants the tag adds it to `TAGS.md`, and the review shows that change on purpose. ## Clean up | Command | Does | |---|---| | [`okf merge-tags`](/okf-cli/reference/commands/okf-merge-tags/) | Merge duplicates and synonyms into one tag. | | [`okf remove-tags`](/okf-cli/reference/commands/okf-remove-tags/) | Remove a tag. | | [`okf normalize-tags`](/okf-cli/reference/commands/okf-normalize-tags/) | Unify the spelling and list form. | All three change many files at once. They print a diff first and write only with `--apply`. Read the diff, apply, then commit. ```text okf merge-tags --into operations ops okf merge-tags --into operations ops --apply ``` ## In short - A small, registered vocabulary. - A CI gate keeps it clean. - The tools repair what accumulated before. See also [Tags and registry](/okf-cli/features/tags-and-registry/). --- # Releases Source: https://rierek.github.io/okf-cli/releases/ Major and minor of okf-cli follow the OKF spec version (spec 0.2, so okf-cli 0.2.x). Only the patch number counts CLI releases. --- # okf 0.2.5: okf as a single Go binary Source: https://rierek.github.io/okf-cli/releases/v0-2-5/ ## Why `okf` started in Python and needed `uv` and a Python interpreter at every start: about 47 ms for `okf --help` and 146 ms for `okf find` (median, Linux amd64). This release replaces the CLI with one Go binary. The commands, options, output formats, `--json` envelope and exit codes stay. ## Highlights - **The `okf` binary.** One static executable, pure Go, no Python and no `uv`. Release assets: `okf-darwin-arm64`, `okf-darwin-amd64`, `okf-linux-amd64`, `okf-linux-arm64`, next to the wheel, the sdist and the provenance file. Homebrew builds it from source. - **Faster.** Median of 30 runs on Linux amd64: `okf --help` 1.3 ms against 47 ms (about 35 times), `okf find git` 17 ms against 146 ms (about 9 times). Peak memory of `find`: 55 MiB against 212 MiB. - **Same commands.** All 33 commands, the 14 `ctx` verbs and `--json` behave as before. The golden suite of 314 cases matches the Python CLI byte for byte in 293 cases; the other 21 differ only in the wording of a YAML parse error. - **Shell completion** for zsh, bash and fish is answered by the binary. `okf install` writes the scripts embedded in it. - **MCP server** (still run through `uv`) now calls the binary: `$OKF_BIN`, else `bin/okf` or `go/bin/okf` next to `mcp/`, else `okf` on `PATH`. - **Frontmatter edits keep your formatting.** The Python CLI re-serialised the frontmatter; the binary patches only the edited lines. The Python CLI is still published (wheel, sdist, `uv run --script cli/okf-cli.py`). ## What is in this release {/* generated:start */} Port the okf CLI to a single Go binary without Python runtime; the golden suite is the acceptance basis, frontmatter writes patch lines minimally, Homebrew, install and completion keep working. MCP server follows later. ## Changes - [OKFT-qg0o] The repo formula installs the binary without Python resources, the release workflow builds it, CI runs the golden suite against it, the MCP server calls it, and zsh completion works end to end; the Python CLI stays deliverable, no tap publication. - [OKFT-agws] The Go binary runs check (rule IDs, severity, suppression, spec checks) and verify with the same findings as the Python CLI and passes their golden cases. - [OKFT-daut] A Go module builds an okf binary with the shared core (root and config, sorted walk, frontmatter read, --where filters, spec, git history, link scan, SQLite and index paths, JSON envelope, exit classes, parser with usage errors) and answers info and --help. - [OKFT-82n3] The Go binary supports every ctx verb on _ctx/<slug>.md bundles, including per-membership attributes, and passes the ctx golden cases. - [OKFT-5lq2] Frontmatter writes in Go patch only the intended lines on yaml.v3 node positions (set, append list item, new key), keep comments, quotes and styles byte-equal, reject unpatchable frontmatter, and provide dry-run diffs and the ctx file model to all writers. - [OKFT-cm5o] The Go binary moves and archives concepts, rewrites paths and cross-links, fixes XML-like tags and regenerates index blocks with minimal diffs, passing the golden cases of these commands. - [OKFT-jsa8] okf install in Go previews and, with --apply, wires agents and shells exactly like the Python installer, verified in a throwaway HOME. - [OKFT-se5f] The Go binary serves index, prime, read, toc, list, recent, visualize and refs and passes their golden cases. - [OKFT-y1zl] The Go binary ranks find via FTS5 and the voyage arm, owns the tagvec write half with both embed backends and the tag-vector producer command, builds all indexes, degrades like Python, passes its golden cases and starts find no slower than Python. - [OKFT-50ly] The Go binary emits zsh, bash and fish completion scripts and provides the completion protocol plus the generic completers (scopes, concepts, concept files, bundles, dirs, files, tags, ctx slugs) that every command package wires to its arguments, respecting --root. - [OKFT-dkdu] The Go binary registers, merges, normalizes and removes tags, edits the registry, migrates catalogues and materialises sync blocks, passing the golden cases of these commands. - [OKFT-ca1y] The Go binary resolves wikilinks and parses sync blocks and the canon index, serves wikilinks, sync-export and sync-status, and passes their golden cases; check, rename and sync writes reuse this model. - [OKFT-ks1t] The golden harness blocks network access including localhost for any implementation, and no expected output carries Python-specific error text, so the Go binary can be judged by the same suite. - [OKFT-8dnw] The full golden run, the documented deviation list, the minimal-diff corpus run, the three production catalogues and the startup measurement prove the port ready for PO acceptance. {/* generated:end */} ## Known limitations Deviations from the Python CLI and other open findings are listed on the [Known limitations](/okf-cli/known-limitations/) page, for example `[cwyx]` (frontmatter shapes the writer refuses) and `[n760]` (one refused file ends a whole run). ## Upgrade notes 1. Install the new binary: `brew upgrade okf-cli`, or download `okf-<os>-<arch>` from the release. 2. Make sure the new binary is the first `okf` on `PATH`: `type -a okf` and `file "$(command -v okf)"`. Remove an old Python launcher (a Homebrew 0.2.x keg, a pipx or `uv tool` install, a wrapper that runs `uv run --script …`). 3. Run `okf install --apply` again, so skills, the MCP entry and the completion scripts point at the new version. A release binary on its own installs completion only; use Homebrew or a clone for the skill and the MCP server. 4. Restart your agent sessions. 5. Existing indexes (`.fts/fts.sqlite`, `.tagvec/tag_vectors.sqlite`) built by the Python CLI keep working; no rebuild is needed. --- # okf 0.2.6: okf tui Source: https://rierek.github.io/okf-cli/releases/v0-2-6/ ## Why `okf` serves agents well and people less so. A person must know the `--where` syntax, every search is a new call, and collecting the hits that matter for an agent is copy work. `okf tui` is a terminal UI for that research flow, entirely on the keyboard. ## Highlights - **Search.** Type a query and the ranked hits appear next to a detail pane with metadata and the table of contents. - **Filters.** A filter bar narrows by root, bundle, type, tag, status and tier through multi-select menus. Press `/` to search inside a menu. The TUI builds the `--where` expression for you. - **Editor.** Open a hit in your editor and return to the same search. - **Session buffer with notes.** Keep hits in a buffer, add a note to each and see the file preview next to it. - **Hand-off.** `ctrl+s` sends the buffer as Markdown to a herdr agent, or to the clipboard when no agent is there. `y` copies it. - **Maintenance tasks.** Press `t` on a hit to record a task in the tracker you configure under `[tui.tracker]`. - **Index dots and refresh.** Two dots show the state of the lexical and the semantic index. `r` opens a dialog: `l` rebuilds the lexical index, `s` embeds the missing concepts for the semantic index. - **Command center.** `ctrl+k` lists every action and view with a fuzzy filter, also inside the query field. - **Themes.** The four Catppuccin themes latte, frappe, macchiato and mocha, with an optional accent colour. `okf tui --help` documents the settings, the state file and the environment variables. See the feature page [Terminal UI: okf tui](/okf-cli/features/tui/) for recordings of every key and the [`okf tui` reference](/okf-cli/reference/commands/tui/) for the key map and the exact hand-off text. Versioning: major and minor of okf-cli follow the OKF spec version (spec 0.2, so okf-cli 0.2.x). Only the patch number counts CLI releases. ## What is in this release {/* generated:start */} Keyboard-first terminal UI `okf tui`, opened as a herdr popup: search the catalogue with interactive filters, inspect hits, collect them with notes in a session buffer, hand them to the agent pane, see and refresh stale indexes, and record maintenance tasks in any configured tracker. ## Changes - [OKFT-07x9] The buffer export puts each note under a `## Notes` heading instead of a quote line. - [OKFT-9x7l] Error messages no longer stay on screen until `esc`; they expire like other messages. - [OKFT-ejgy] Filter menus fit long names, search with `/` (also in tags) and select with space as well as enter. - [OKFT-t64h] The command center on `ctrl+k` lists every action and view with a fuzzy filter; types and tags have their own views; help stays on `?`. - [OKFT-mpfi] The documentation site has a page for `okf tui` with recordings that show every pressed key. - [OKFT-lg82] A filter bar on the search screen: root (path input, remembered roots) plus bundle, type, tag, status and tier as multi-select menus. The TUI builds the `--where` expression; you never type it. - [OKFT-ms7g] Exports the buffer as Markdown (header, one block per hit: title, @absolute path, type, bundle, tags, description, note). `ctrl+s` sends it without Enter to the agent under the popup, else via a picker of herdr agents, else to the clipboard; `y` copies it. - [OKFT-qagm] Two dots on the search screen show the state of the lexical (FTS) and the semantic index: green current, yellow stale or partial, red absent, grey unknown/unavailable. A key or palette action rebuilds the stale index in the background. - [OKFT-wjns] The query field has a rounded border, each pane shows its title on the border, and the buffer view shows the file preview of the focused entry with its note below. - [OKFT-6vmf] `t` on a hit opens a prompt; the task (text plus hit reference) goes to the tracker configured in [tui.tracker] of the user config: kind `command` runs a user command with the task in environment variables, kind `file` appends an Obsidian-Tasks line. - [OKFT-0kg6] `okf tui --help` lists the settings, the state file and the environment variables, and `tui` appears in `okf --help` and in `prime`. - [OKFT-qhmz] Adds the `okf tui` command: it opens the configured root on an empty search screen and works as a herdr popup. - [OKFT-u1b5] `e` opens the focused hit's file in the resolved editor (TUI setting, $VISUAL, $EDITOR, vi) with the terminal handed over; afterwards the session is unchanged and the hit's details reload from disk. Provides the editor helper that text inputs use for `ctrl+e`. - [OKFT-brlv] Searches no longer show the ranking note as a message; any other search warning appears at most once per session. - [OKFT-12lf] The query field ranks hits like `okf find` and shows them as a list with a detail pane (type, bundle, description, snippet, tags, provenance, tier, stale). Search warnings show as messages. - [OKFT-krar] `space` adds or removes the focused hit to a session buffer, marked in the list; the buffer view lists entries in order of adding, removes single entries, clears all after confirmation; `n` attaches a free-text note to a buffered hit. ## Fixes - [OKFT-ledw] Red tones keep their colour on 256-colour terminals, a long root path no longer wraps the header, and the root menu lists a directory once. {/* generated:end */} ## Upgrade notes 1. Trust the tap: `brew trust rierek/tap`. 2. Run `brew reinstall okf-cli`. The formula now builds the Go binary from source and needs no Python. 3. Check that the new binary is the first `okf` on `PATH`: `type -a okf`. Remove an old Python launcher (a Homebrew 0.2.4 keg, a pipx or `uv tool` install). 4. Run `okf install --apply` again, so skills, the MCP entry and the completion scripts point at the new version. 5. Run `okf tui --help`: the settings epilog confirms the new binary. --- # Known limitations Source: https://rierek.github.io/okf-cli/known-limitations/ Check an unexpected result against this list first. Each row names one open defect or limit, its effect and, where one exists, a workaround. Measured on okf 0.2.5, 2026-10-18. ## How to read an ID `[abcd]` is the short ID of an item in the project tracker. The same ID appears in square brackets on the page that is affected. The list is maintained by hand. ## Open limitations - [irxe] `okf tags --validate` exits 4, help says 1. Workaround: test for non-zero. - [jh7t] zsh completion script comments name argcomplete and `okf-cli.py`; no behaviour change. - [71rn] Homebrew formula is head-only and builds from source. Workaround: `brew install --HEAD okf-cli`, or the release binary. - [i2ws] No `--version` flag. Workaround: file hash or `brew info`. - [dqq5] Release binaries are not run before upload. Workaround: run the downloaded asset. - [c0mc] No statement whether macOS binaries are signed or notarised. - [vf2r] `ctx show`, `read`, `check`, `edit` exit 4 for a dead entry, help says 1. Workaround: test for non-zero. - [erpf] `tags --register` and `prime` name `build_tagvec.py`, which a Go install lacks. Workaround: ignore, or `okf build-tagvec --produce`. - [l2j0] `okf --help` describes the Python script. Workaround: ignore. - [fs8c] A blocked `install` plan exits 1 with a generic message. Workaround: read the plan, or use `--json`. - [zs6t] The README, `pyproject.toml` and the Homebrew formula still name the old GitHub owner `xRiErOS`; the repo and this site live under `rierek`. Workaround: use `rierek/okf-cli`; check the tap and release URLs before you copy a command. - [cwyx] The frontmatter writer refuses shapes it cannot patch (lone CR, flow-mapping or null root, anchor/alias/tag on the edited value, comment in a flow list, other shapes, ctx edits, huge ctx positions, non-scalar keys); Python wrote them. Exit 2 with `<path>: <reason>`, file untouched. Workaround: edit the file by hand. - [n760] One refused file ends the whole run of `merge-tags`, `remove-tags`, `normalize-tags` and `migrate`, a dry run too. Workaround: repair or move the file, then rerun. - [xhfi] The parser refuses frontmatter that Python reads (`\/` escape, unusual anchor names, empty keys, U+0085, U+2028, U+2029). Workaround: replace the character or form. - [4752] Frontmatter is validated less strictly than in Python (equal duplicate keys, invalid tag values, merge-key misuse, impossible dates such as `2026-02-30`). Workaround: `okf check` and an external YAML linter. - [6xot] A TOML integer above int64 in a config file aborts every command that reads the config. Workaround: remove the number. - [lazz] `okf recent` prints UTC when `TZ` is a POSIX rule string. Workaround: set `TZ` to an IANA name. - [3hfd] Cherokee text: case folding is not idempotent, so a heading in the other case is not found. Workaround: type the stored case. - [qo6x] Timeouts limit the whole request, not each socket wait as in Python. Workaround: retry. - [6peo] After `rename --apply` of a canon owner, consumers keep the old path in the `> [!quote]` line and `sync-status` still says `synced`. Workaround: run `sync-update --apply`. - [gnnc] The substring fallback of `find` prints `provenance: lexical`, help says `substring`. Workaround: none needed, cosmetic. - [oyw7] `okf --help` does not say that `--json` is a per-command flag. Workaround: see the command pages. ## Fixed Empty until the first fix release. --- # Contribute to okf: build from source and run the tests Source: https://rierek.github.io/okf-cli/contribute/ ## Contribute to okf: report bugs, build from source, run the tests Issues and pull requests are welcome. ## Report bugs and requests 1. Read [Known limitations](/okf-cli/known-limitations/). The problem may be listed with a workaround. 2. Open an issue in [`rierek/okf-cli`](https://github.com/rierek/okf-cli/issues). Give the command, the catalogue layout (or a minimal one) and the output. ## Build from source The Go module is in `go/`. The binary is written to `go/bin/okf`. ```bash title="Build" just go-build ``` ## Test Run `just test` before a pull request. It must be green. Tests never write into a real catalogue and use their own temporary directory. | Recipe | What it runs | |---|---| | `just test` | builds the binary, then runs the Python unit tests (needs `uv`) | | `just golden` | compares command output with the golden files, using the Python script | | `just golden-go` | the same comparison against the Go binary | | `just go-completion-scripts --check` | checks the generated completion scripts | | `just completion-check` | checks shell completion against the Go binary | Commit titles use `<type>(<scope>): <subject>`, at most 72 bytes. ## Docs site Content is in `wiki/`, the site is in `site/`. The command pages are generated from the binary and are not checked in. | Recipe | What it does | |---|---| | `just docs-gen` | generates the command pages | | `just docs-build` | builds the site into `site/dist` | | `just docs-dev` | starts the dev server | ## License and credit okf is licensed under [Apache-2.0](https://github.com/rierek/okf-cli/blob/main/LICENSE). It is inspired by Cole Medin's single-bundle `okf-cli.py`. --- # About okf: Open Knowledge Format tooling Source: https://rierek.github.io/okf-cli/about/ ## About okf, an Open Knowledge Format tool **okf is a command line tool and MCP server for catalogues of Open Knowledge Format (OKF) v0.2 bundles.** It searches, navigates, validates and refactors markdown files with YAML frontmatter, for people and for AI agents. It is a single Go binary. ## Specification tool :::note[Specification first] okf exists to work with the OKF specification. okf follows the specification and is adapted when the specification changes. There is no release date and no release per specification version. ::: ## Origin okf is a catalogue-aware fork inspired by Cole Medin's single-bundle `okf-cli.py`. A Go port replaced the Python run time. The Go binary needs no Python. The [Known limitations](/okf-cli/known-limitations/) page lists where the port differs from the Python original. ## The OKF format The normative text is the [OKF specification](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md). [okf.md](https://okf.md/) is a community guide. For the terms okf uses, see the [glossary](/okf-cli/reference/glossary/). For a short tour of the model, see [Concepts](/okf-cli/overview/concepts/). ## Scope okf leaves version control to git. Git gives clean diffs for markdown, a commit log per concept, and branches and pull requests for proposed changes. okf reads and writes the files and never commits. ## Where to go next - [Install okf](/okf-cli/overview/installation/) - [First steps](/okf-cli/overview/first-steps/) - [Contribute](/okf-cli/contribute/)