v0.1.0 Latest

Knowledge base

Collections from outside sources, imports and connectors, runbooks with stable step ids, the public site, grants, and the /kb API.

A knowledge base article is never a document and never belongs to a company. Collections hold reference articles from outside sources: a vendor’s help center read by its own structure, a website crawled by sitemap or from a starting page, a wiki or another system’s export brought in as a zip or a folder, with their pictures and the PDFs and Word documents they link.

Bringing documentation in

Imports upsert on (collection, source key), so a newer export updates what changed and nothing else. Every converter and every stored body is held to one standard: an imported article must read as its source did.

  • Numbered steps keep their numbers and keep counting. A picture, a note, or a caption between two steps belongs to the step above it. A list restarts only where the source restarts.
  • A source site’s own navigation is never kept: breadcrumbs, “Contents” lists of anchor links, print and share buttons. Trove KB builds its own outline from the headings.
  • A section title the source numbers is a heading, not an empty list item.
  • images/ and files/ folders in an archive are attachments; a crawled page’s pictures are kept by their address; the original document is kept and offered.
  • Categories come from the source’s structure, and a folder named on import overrides them.

Check a new source by importing it and reading three articles with steps against their originals before importing the rest.

Admin → Knowledge base drives imports from the browser in pieces, resumable. An archive already on the server can be imported without the browser:

npx tsx --conditions react-server scripts/kb-import.ts "<collection>" <file.zip>

Run it where STORAGE_PATH is the deployment’s own uploads volume: the pictures in the archive are written there.

Runbooks

A runbook is an article whose lists are a procedure. Its steps are read from the body on every save: the items of every top-level list that is a procedure, in order across headings, each as { id, text, note?, canned? }.

The id is stable: write it yourself at the end of the item as {#my-id} ([a-z0-9-]{1,40}), or let Trove KB mint one on first save, after which it is in the stored body and kept across edits. A system that tracks progress through a runbook keys its state by id and keeps that state on its own side; Trove KB stores none. note is whatever was nested under the item; canned is the name in the first @canned:[Name] token of the step. Two steps with one id are refused.

The public site

/pub/kb is for readers who have not signed in. It belongs on a hostname of its own, such as kb.yourdomain.com, behind a proxy that passes /pub/kb, /_next/static/, the logo, and the favicon, and refuses everything else. See Cloudflare for the nginx block.

  • Each collection has Show on the public site, off by default. An article can be held back from its own page.
  • Keyword rules (phrases or regular expressions, by title, category, or file type) and per-category switches keep the rest off, and apply to what arrives later.
  • Under Admin → Settings → Public knowledge base, choose who is admitted: anyone who can reach it, or only visitors from listed addresses. Pair it with a Cloudflare Access Bypass policy for the same ranges.
  • Behind Access, name the team and the application’s audience tag and readers keep favorites and votes: Trove KB checks the token Access adds to each request against the team’s published keys and knows the reader by a hash, with no account of its own.

Grants

A person granted a collection reads it, and with can_write writes to it in the app, whatever companies it is kept to; administrators need no grant. An account can be made ahead of a person’s first sign-in so the grant is waiting for them. Under Admin → Knowledge base → API access, an administrator sets one key’s access to every collection at once: D (only what its companies allow), R, RW, and whether the key may keep reactions there.

API

GET    /kb/collections?writable=true
GET    /kb/collections/:id?kind=
GET    /kb/search?q=&collection_id=&category=&kind=&limit=&cursor=
GET    /kb/articles?collection_id=&category=&subcategory=&kind=&updated_since=&sort=&dir=&limit=&cursor=
GET    /kb/articles/:id                                    full body, kind, steps, external_id, source_url, public_url
PUT    /kb/collections/:id/articles/:external_id           upsert   (write scope + write grant)
DELETE /kb/collections/:id/articles/:external_id           archive  (write scope + write grant)

PUT    /kb/collections/:id/grants/users/:userId      { can_write? }
DELETE /kb/collections/:id/grants/users/:userId
PUT    /kb/collections/:id/grants/api-keys/:keyId    { can_write?, reactions? }
DELETE /kb/collections/:id/grants/api-keys/:keyId

Every article item carries kind, source_type (md, html, pdf, docx, txt), public, favorites, and helpfulness. audience=public narrows a read to what the public site shows. A PUT answers 201 when it created the article and 200 when it replaced it.

Favorites and votes for a named reader

With the reactions scope and the header X-Trove-Reader: <email> (never stored; a key is derived from it, the same one the public site uses behind Access):

GET    /kb/articles/:id/reactions   -> { favorites, helpful_up, helpful_down, helpfulness, mine }
PUT    /kb/articles/:id/favorite    -> 204          DELETE -> 204
PUT    /kb/articles/:id/vote        { helpful: true|false } -> 204     DELETE -> 204
GET    /kb/favorites?limit=&cursor=

Webhooks kb.article.upserted and kb.article.archived are sent for writes through the API, MCP, or the in-app editor.