Quickstart — Your First Source Adapter
This guide walks you through building a working source adapter from scratch. By the end, documents from a real upstream will appear in your Trove knowledge base — searchable from Claude on desktop, web, and mobile.
We’ll build a Hacker News Upvotes source: it reads the stories you’ve upvoted on Hacker News and indexes each one as a document. The upstream is a public JSON API (the Algolia HN search API), so there’s nothing to log into — perfect for a first source adapter. The same shape applies to any RSS feed, JSON API, or any page you sync.
Prerequisites
Section titled “Prerequisites”- Trove CLI installed (
npm install -g @ontrove/cliorbun add -g @ontrove/cli) - Logged in:
trove login - Node 20+ or Bun 1.1+
- A Hacker News username (any account that has upvoted a few stories)
Step 1 — Scaffold the source adapter
Section titled “Step 1 — Scaffold the source adapter”trove source init hacker-newscd hacker-newsThis creates a folder with the two-file structure:
hacker-news/ manifest.json ← what the source is (id, name, schedule, config) index.ts ← your sync(ctx) functionStep 2 — Write the manifest
Section titled “Step 2 — Write the manifest”Open manifest.json and make it match:
{ "id": "hacker-news", "name": "Hacker News Upvotes", "description": "Index stories you've upvoted on Hacker News.", "version": "1.0.0", "category": "reading", "schedule": "every 6 hours", "config": { "username": { "label": "HN Username", "type": "text", "placeholder": "pg" } }, "needs_browser": false}A few things to note:
configdeclares the one field the user fills in during setup — their HN username. It becomesctx.config.usernamein your code. Config holds preferences only, never credentials (this source needs no auth at all).scheduleis human-readable. The Mac app parses it and runs yoursyncon that cadence.needs_browser: falsemeans this source adapter makes plain HTTP requests — no Playwright, no browser session.
See the manifest.json Reference for every field.
Step 3 — Write sync(ctx)
Section titled “Step 3 — Write sync(ctx)”Open index.ts and replace the scaffold with:
import type { TroveSource } from "@ontrove/sdk";
interface HnStory { objectID: string; title: string; url: string | null; story_text: string | null; author: string; created_at: string;}
export default { async sync(ctx) { const username = ctx.config.username as string;
// The Algolia HN API exposes a public "upvoted_<user>" tag. const res = await ctx.fetch( `https://hn.algolia.com/api/v1/search?tags=upvoted_${username}&hitsPerPage=100` );
if (!res.ok) { throw new Error(`HN API returned ${res.status}`); }
const data = (await res.json()) as { hits: HnStory[] }; ctx.log(`fetched ${data.hits.length} upvoted stories for ${username}`);
return data.hits.map((story) => ({ // A stable id is the dedup key — re-running sync never duplicates a story. id: story.objectID, title: story.title, text: `${story.title}\n\n${story.story_text ?? story.url ?? ""}`, url: `https://news.ycombinator.com/item?id=${story.objectID}`, author: story.author, date: story.created_at, })); },} satisfies TroveSource;The whole contract is: return an array of documents. Each document needs a stable id and some text; title, url, author, and date are optional metadata. See the document shape for every field.
Step 4 — Test it locally
Section titled “Step 4 — Test it locally”Run the source adapter against your machine without touching the cloud. trove source dev executes sync(ctx) once and prints the documents it would push. The --config flag takes a path to a JSON file of preference values, which becomes ctx.config:
echo '{"username":"pg"}' > config.jsontrove source dev --config config.json loading manifest: hacker-news v1.0.0 (1 config field) running sync(ctx) … fetched 37 upvoted stories for pg✓ sync returned 37 documents
# id title 1 8863 My YC app: Dropbox - Throw away your USB drive 2 9224 Stripe: Atlas … (no documents pushed — dev mode is read-only)trove source dev runs sync locally and validates the document shape, but pushes nothing. Use it to iterate on your fetch and mapping logic. To run sync against a fixtures file offline — canned responses keyed by request URL — and assert the document shape with no live fetch, use trove source test. To validate just the manifest (shape plus a credential-key lint), use trove source validate:
trove source test # run sync against fixtures, assert document shape (offline)trove source validate # validate manifest.jsonStep 5 — Run it for real
Section titled “Step 5 — Run it for real”When the output looks right, run a real sync. This creates the source and its default feed in your knowledge base (if they don’t exist yet) and pushes the documents via ingestDocuments:
trove source sync --create --config config.json ensuring source hacker-news … created (src_a1b2c3) ensuring feed "default" … created (feed_d4e5f6) reading cursor … (none — first sync) running sync(ctx) … 37 documents pushing to ingestDocuments (batch 1/1) …✓ indexed 37, skipped 0, cursor → (unchanged)Under the hood the Mac app does exactly what your own code could do via the Sync API: ensure a source, ensure a feed, then call ingestDocuments(sourceId, feedId, documents, cursor). Trove stores the full text, embeds it, and indexes it for search.
Run it again and watch dedup work — every story is skipped because its id already exists:
✓ indexed 0, skipped 37, cursor → (unchanged)Step 6 — See your documents in Claude
Section titled “Step 6 — See your documents in Claude”The documents are now in your knowledge base. Ask Claude on your Trove connection:
Search my Trove for what I’ve upvoted about startups.
Claude calls trove_search, and your freshly-indexed HN stories come back with [doc:ID] handles. You can also confirm the source exists:
List my Trove sources.
trove_list_sources shows Hacker News Upvotes with its document count.
What’s next
Section titled “What’s next”- SDK Reference — the full
ctxobject, the document shape, audio transcription, and error handling - manifest.json Reference — all manifest fields and their effect
- Cursors and Feeds — incremental sync, watermarks, and multi-feed sources
- Ship it: package the folder into the source-adapter catalog so others can add it