skip to main content

kb — CLI and TUI — Design

Status (2026-07-27): Decisions confirmed. See cli-tui-plan.md for the phased implementation plan.

References: - module-extraction-design.md/-plan.md — this is the natural next step after the extraction: henry/antennaApp and other tools/harnesses can now depend on github.com/rsdoiel/knowledge, but the only executable interface today is cmd/kbmerge (a two-database merge tool only). This design adds the general-purpose interface. - Confirmed in conversation (2026-07-27): binary name kb; human text by default, --json for machine consumption; cmd/kbmerge folds into kb as a verb; TUI is the same binary launched with no verb, read-mostly to start; TUI built on bubbletea rather than termlib.

Motivation

github.com/rsdoiel/knowledge exposes a full typed Go API (projects, observations, concepts, sources, search, linking, retraction-checking), but the only way to drive it today is: write Go code against the package directly, or use cmd/kbmerge (merge only). There’s no general command-line or interactive way to read or write a knowledge base — henry/antennaApp are still limited to raw sqlite3 CLI inserts for anything beyond what a bespoke Go program provides, and no other tool or LLM harness can drive this data store without writing Go.

kb closes that gap: a single binary, <TOOL> <VERB> <PARAMETERS> (matching git/go’s own model, per your stated preference), general enough for scripts, other language-model harnesses, and a human at a terminal — plus a read-mostly interactive browser when launched bare.

Audit — full current API surface (2026-07-27)

Every exported method/function in github.com/rsdoiel/knowledge today:

Area Methods
Lifecycle Open(dbPath), DefaultPath(root), (*KnowledgeBase).Close(), .Path()
Projects AddProject, Projects, ProjectByName, ProjectConcepts
Observations AddObservation, AddObservationWithSource, Observations, ObservationByID
Concepts AddConcept, AddConceptWithIdentifier, Concepts
Links LinkObservationConcept, LinkProjectConcept
Sources AddSource, ListSources, ShowSource, RemoveSource, RetractSource, LinkObservationSource, ObservationSources, FindOrCreateSource, CheckRetractions
Search/format Search, Summary, FormatMarkdown
Cross-machine merge CollisionReport, ReconcileCollisions, MergeKnowledgeBases (currently wrapped by cmd/kbmerge)

One real gap found while auditing for this design, not related to CLI work directly but relevant to it: Open sets PRAGMA journal_mode = WAL but never sets a busy timeout. A single interactive harvey session opening the db for a whole run is unlikely to hit this, but the explicit goal here — “other language model harnesses or systems can use the knowledge base” — means multiple independent processes (this CLI, harvey, some other future tool) may now genuinely open and write to the same file concurrently. Without a busy timeout, SQLite returns SQLITE_BUSY immediately on any write contention instead of waiting briefly for the other writer to finish — exactly the failure mode this whole feature exists to make more likely. Proposed fix (in knowledge.go, not just the CLI): add PRAGMA busy_timeout = 5000 to the schema applied in Open. Benefits every consumer (harvey included), not just kb.

Decisions

1. Verb tree

kb project add NAME [DESCRIPTION]
kb project list
kb project show NAME
kb project concepts NAME

kb observation add --project NAME KIND BODY [--source-doi DOI]
kb observation list --project NAME
kb observation show ID
kb observation sources ID

kb concept add NAME [DESCRIPTION] [--identifier-type T --identifier-value V]
kb concept list

kb link project PROJECT_NAME CONCEPT_NAME
kb link observation OBS_ID CONCEPT_NAME

kb source add TITLE [--doi D] [--url U] [--authors A] [--published DATE] [--publisher P] [--rights R] [--version V]
kb source list
kb source show ID
kb source remove ID
kb source retract ID NOTE
kb source link OBS_ID SOURCE_ID [--relationship R]
kb source check-retractions

kb search TERM
kb summary
kb format --project NAME        # was FormatMarkdown; "inject" (harvey's name)
                                 # is harvey-specific framing (injects into a
                                 # chat), not appropriate for a general CLI

kb merge -a PATH -b PATH -out PATH [-force]   # cmd/kbmerge folded in verbatim

kb with no verb at all launches the TUI (decision 4). kb help/kb -h prints usage; kb VERB -h prints that verb’s flags (standard flag package behavior).

FindOrCreateSource and LinkProjectConcept/LinkObservationConcept (as a single unified verb vs. two) — covered by kb link project/ kb link observation above; FindOrCreateSource is not exposed as its own verb since kb source add already does find-or-create semantics via AddSource’s existing conflict handling — no separate CLI surface needed for it.

kb concept link was considered as an alternative spelling to kb link project/kb link observation, but concepts are the object being linked in both cases (a project-to-concept link and an observation-to-concept link), so grouping under link SUBJECT_TYPE reads more like git remote add/git branch delete (verb-first, unambiguous) than kb concept link --project|--observation, which would need a flag to disambiguate what’s on the other end of the link.

2. Global flags: --db PATH, --json

kb [--db PATH] [--json] VERB [verb-specific flags/args...]

3. No persistent “current project” state

Unlike harvey’s interactive /kb project use ID (which sets Config.Memory.CurrentProjectID for the rest of the session), kb is stateless across invocations — every verb that needs a project takes an explicit --project NAME flag. No hidden state file recording “the current project” between runs. This is deliberate for the stated “other harnesses” use case: concurrent or scripted callers must not have their behavior depend on some other process’s last use call.

4. TUI: same binary, bare kb, read-mostly to start, built on bubbletea

kb with no arguments launches an interactive terminal browser: list projects → drill into a project’s observations/concepts → search. No add/edit/link/retract in this first version — that’s an explicit, separate future increment, not part of this plan. Built on github.com/charmbracelet/bubbletea (+ bubbles for its ready-made list component) rather than termlib — confirmed in conversation: termlib only provides primitives (box-drawing, raw mode, line editing), not a scrollable/selectable list widget, and building that from scratch on top of termlib is real, avoidable work for a browser UI whose whole point is lists. This is the first use of a charmbracelet dependency anywhere in this Laboratory (checked — no other experiment currently imports bubbletea/bubbles) — worth knowing since it sets a precedent, not because it’s a problem.

5a. Help text follows harvey’s helptext.go pattern, generates real man pages

Confirmed by checking harvey/helptext.go, harvey/help_dispatch.go, and harvey/Makefile: harvey’s help text isn’t ad-hoc fmt.Errorf strings — it’s a set of Go string constants written as Pandoc-Markdown man pages (%{app_name}(1) user manual | version {version} {release_hash} title block, then # NAME/# SYNOPSIS/# DESCRIPTION/# OPTIONS sections), with {app_name}/{version}/{release_date}/{release_hash} tokens substituted via FmtHelp (already generated into this module’s own version.go by cmt, unused until now). The Makefile then does ./bin/TOOL -help > TOOL.1.md, i.e. the binary’s own -help output is the checked-in Markdown man page source, and pandoc TOOL.1.md --from markdown --to man -s > man/man1/TOOL.1 produces the real troff man page.

kb adopts the same pattern, one level deeper — matching git’s own convention of one man page per subcommand (git-commit(1), git-log(1)), which also matches the <TOOL> <VERB> model this whole CLI is built around:

5. busy_timeout fix lands in knowledge.go, not just the CLI

Per the audit finding above: add PRAGMA busy_timeout = 5000 to the schema applied in Open. Small, low-risk, benefits every existing consumer.

6. Command dispatch: small hand-rolled dispatch table, no CLI framework

cmd/kb/main.go gets an unexported dispatch(args []string) error that maps verb name to handler function; each verb owns its own flag.FlagSet. Global --db/--json parsed once, up front, before dispatching. No cobra/urfave-cli/etc. dependency — matches cmd/kbmerge’s existing stdlib-flag precedent; this is just enough shared plumbing for one binary’s main.go, not a reusable package.

7. show/list verbs return bare rows; kb format is the assembled view

kb project show/kb project list (and the equivalent for observations/ concepts/sources) return exactly what the underlying Projects()/ ProjectByName()/etc. methods already return — no inline assembly of linked concepts or recent observations. kb format --project NAME (→ FormatMarkdown) is the existing, already-built place to get the fully-assembled Markdown view. Fast, predictable, and avoids two different “shapes” of project data depending on which verb produced it.

What this does not cover