okf registry
Name a bundle once, use it from anywhere.
When to use it
- You work across more than one bundle. Register each once and every verb reaches it by name:
okf lint @handbookworks from any directory, no path required. Many bundles, one registry walks the setup end to end. - You want the names to travel with the repo.
okf registry initdrops a project-local.okf-registry.jsonyou commit, so a teammate who clones gets the same@slugs and a bareokf serverworks with no global setup on the machine. - You want one name for a set of bundles.
okf registry group backend @orders @billing @sharedgives the set a single slug you search and serve as a unit. - You want one graph page over all of them. A bare
okf serverhosts every registered bundle behind one hub, andCmd/Ctrl-Kswitches between them;okf serverhas the details. - You ask cross-bundle questions.
okf search @all <term>ranks matches across every registered bundle in one query, each row labeled with the bundle that answered;okf searchcovers it.
How it works
okf registry manages a plain JSON file at $OKF_HOME/registry.json, with $OKF_HOME defaulting to ~/.okf. It stores references, never content: a path, a slug, and a title per bundle. The bundles stay where they are on disk, owned by the repos they document, so nothing is copied and nothing can go stale except the path itself. The file is meant to be read, grepped, and hand-edited.
okf registry init # create a project-local .okf-registry.json here
okf registry list # every entry; * marks the default
okf registry set <dir> [--as SLUG] # add or update (identity is the path)
okf registry del <dir|@slug> # remove the entry (or a group); the bundle stays on disk
okf registry default @slug # move an entry to the front
okf registry rename <slug> <new> # change the name, keep the position
okf registry group <slug> <@member…> # name a set of bundles or groups; nests
okf registry ungroup <slug> <@member…> # remove members; emptying deletes the group
The slug is the bundle's name everywhere. It is minted from the directory basename unless you choose one with --as. The two paths differ on collision, deliberately: a minted name that is taken gets a suffix (docs becomes docs-2), because you never asked for it, while a name you chose with --as or rename is refused instead, because silently serving a different one would be a lie. all is reserved on every path in, since @all already means every registered bundle.
The list is ordered, and the first entry still on disk is the default: the bundle a bare okf server opens at /, and the one a bare @ names. registry default @slug moves an entry to the front, and registry set --default registers straight to it. Nothing else is stored, so nothing else can drift: a rename keeps its position, a del promotes whatever is next, and the file cannot name a default that is not in it.
A registry can live in the repo
okf registry init creates a project-local .okf-registry.json in the current directory. Once it exists, okf discovers it by walking up from wherever you are, and every registry operation and every @slug resolves through it instead of the global $OKF_HOME one, so a bare okf server inside a repo serves that repo's bundles with no global setup on the machine. The nearest registry on the path wins, since nested ones resolve nearest-first; okf registry list names the local file it found, and OKF_NO_DISCOVERY=1 forces the global one, the escape hatch for a fixed-directory caller such as CI.
A project-local registry stores portable paths. A bundle inside the registry's own tree is written relative to the .okf-registry.json, so the file can be committed and a checkout on another machine, or a container that mounts it, resolves the same bundles unchanged. A bundle outside the tree keeps an absolute path, because it cannot travel. Paths still read back absolute everywhere the CLI reports them, the relative form lives only on disk, and an existing absolute local entry migrates to relative on its next write. The global $OKF_HOME registry is unchanged and stores absolute paths as before.
Groups: one slug for a set of bundles
A group is a registry slug that names a list of members, and a member can be a bundle or another group, so groups nest; it resolves recursively and path-deduped down to its bundle leaves. okf registry group <slug> <@member…> creates one or adds to it, ungroup removes members (and emptying a group deletes it), and del and rename reach a group slug too, so one rename cascades across every member list and one del drops the slug and deletes any group it empties. okf search @backend merges the members into one ranking and okf server @backend mounts each of them, the first at /, both skipping a vanished member with a note exactly as @all does. Every single-bundle verb refuses a group and exits 2, the same rule that refuses a second bundle.
@slug, wherever a directory goes
Registering gives a bundle a name the whole CLI understands. Wherever a verb takes a <dir>, @slug names a registered bundle and a bare @ names the default:
okf lint @handbook # from anywhere, no path
okf render @ -o graph.html # the default bundle, exported
okf search @all rate limit # every registered bundle, ranked together
okf server @handbook @wiki # a hub of exactly these two
The registry-editing verbs (del, default, rename) take the slug bare or with the @; the reading verbs need the @, since a bare word there is a path.
Try it
okf registry set ./docs --as handbook
registered handbook → /Users/you/work/billing/docs (37 concepts)
okf registry list
* handbook billing/docs (/Users/you/work/billing/docs)
wiki team/wiki (/Users/you/work/team/wiki)
From here okf server with no arguments serves both behind one hub, and any verb takes @handbook in place of the path.
Pitfalls
- A running server does not follow the file. The hub reads its bundles at boot, so registering, renaming, or deleting one needs a server restart to show.
- A discovered registry outranks
$OKF_HOME. When a.okf-registry.jsonsits at or above your working directory, it wins and every verb resolves through it;$OKF_HOMEnames the global registry, used only when no local one is found or whenOKF_NO_DISCOVERY=1forces it.$OKF_HOMEstill names exactly one registry, with no fallback to~/.okfbehind it, and an empty value counts as unset. - A single-bundle verb refuses a group.
okf lint @backendexits2when@backendis a group, the same as pointing it at two bundles;searchandserverare the verbs that take a set. - Committing a local registry rewrites its own paths once. An existing absolute local entry migrates to relative on its next write, so a
.okf-registry.jsonyou already had can rewrite its paths the first time you touch it. In-tree bundles go relative so they travel; out-of-tree bundles stay absolute. - Deleting from the registry deletes nothing on disk.
delremoves the reference; the bundle stays where it always was. The reverse also holds: deleting a directory does not prune its entry.registry listmarks itmissingand leaves the decision to you, and the default quietly skips it, since the star must name a bundle/can actually open. - A path argument names a location, only a location.
registry del ./notesmatches an entry by that path and never falls through to the slugnotes, so you cannot remove a bundle that merely shares a name with a local directory.