Skip to content
The source-adapter SDK (@ontrove/sdk) powers personal-source ingestion, which ships with the Trove Mac app — coming soon. Browse the API below now; for what is live today, see the official toolkits.

@ontrove/sdk

@ontrove/sdk — the thin standard library for authoring Trove connectors. You write a sync(ctx) that fetches from a source and returns documents; the SDK owns the document shape (which maps 1:1 onto IngestDocumentInput), the typed ctx capability object, the watermark/cursor model, the local-run harness the CLI drives, and manifest validation.

It is the symmetric sibling of @ontrove/mcp: a connector returns documents to be stored (defineConnector + sync); an MCP server returns tool results to be read live (defineMcpServer).

import { defineConnector } from '@ontrove/sdk';
export default defineConnector({
async sync(ctx) {
const res = await ctx.fetch('https://hn.algolia.com/api/v1/search?tags=front_page');
const { hits } = await res.json();
return hits.map((hit) => ({
id: hit.objectID,
title: hit.title,
text: hit.story_text ?? hit.title,
url: hit.url,
author: hit.author,
date: new Date(hit.created_at_i * 1000).toISOString(),
contentType: 'bookmark',
}));
},
});

The single argument to sync — a capability object: everything a connector reaches the outside world through is on ctx. There is no ambient authority.

ctx.credentials (Keychain-resolved auth material) and ctx.browser (a Playwright browser for needs_browser connectors) are PROPOSED, not part of the shipped contract, and so are intentionally absent here.

Type ParameterDefault type
CRecord<string, unknown>
PropertyModifierTypeDescription
configreadonlyCThe user’s preference values, keyed by the field names declared in manifest.json config. Preferences only — never credentials. Feed URLs, usernames, section lists, and filters live here; auth material lives in the macOS Keychain, surfaced (PROPOSED) via ctx.credentials, never here.
cursorreadonlyWatermarkThe source’s current watermark (the position from the previous run), or { type: 'none' } on the first sync. Read-only — advance the cursor by returning a new Watermark from sync, not by mutating this.
fetchpublicFetchLikeBehaves like the standard fetch. Prefer ctx.fetch over global fetch — in the Mac app it routes through per-connector timeouts, retry, and rate-limit handling, surfacing failures in the connector’s error log.
log(...args: unknown[]): void;

Structured log entry, surfaced in the Mac app’s connector logs and the createSyncRun audit record. Useful for reporting counts and progress.

ParameterType
argsunknown[]

void

now(): Date;

The current wall-clock time as a Date. Injected (rather than read from a global) so syncs are deterministic under test and the local-run harness.

Date


A single document a connector returns from sync. The fields map 1:1 onto the GraphQL IngestDocumentInput the Mac app pushes via ingestDocuments:

SDK fieldIngestDocumentInput field
idexternalId (required)
titletitle
texttext
audioUrlaudioUrl
urlurl
authorauthor
datedate (ISO 8601 DateTime)
tagstags
metadatametadata (JSON)
contentTypecontentType (ContentType)

Dedup is keyed on (source, id): returning the same id twice is safe and is skipped on the server, so sync can be idempotent and retried freely. At least one of text or audioUrl must be present — an audio-only document triggers transcription (Whisper), and the transcript becomes the document text.

PropertyTypeDescription
audioUrl?stringURL to an audio file. Maps to IngestDocumentInput.audioUrl. Trove downloads and transcribes it; the transcript becomes the document text and the document is indexed as contentType: transcript. Provide instead of (or alongside) text.
author?stringContent author. Maps to IngestDocumentInput.author. For podcasts, set this to the show name (Trove convention).
contentType?ConnectorContentTypeOne of text, transcript, highlight, bookmark. Maps to IngestDocumentInput.contentType. Defaults to text (transcribed audio becomes transcript automatically).
date?stringOriginal creation date as an ISO 8601 string (e.g. "2026-06-14T09:00:00Z"). Maps to the DateTime IngestDocumentInput.date. Used for recency ranking and the “published” display.
idstringThe stable external identifier from the source system — the dedup key within the source. Maps to IngestDocumentInput.externalId. Use the native id (HN objectID, RSS guid, Notion page id), never a value that changes between runs.
metadata?Record<string, unknown>Arbitrary connector-specific JSON. Maps to IngestDocumentInput.metadata. Never put credentials or auth headers here.
tags?string[]Array of string tags. Maps to IngestDocumentInput.tags.
text?stringFull plain-text content. Maps to IngestDocumentInput.text. Strip HTML/markup for best search quality. Required unless audioUrl is set.
title?stringDocument title. Maps to IngestDocumentInput.title.
url?stringCanonical link back to the original. Maps to IngestDocumentInput.url.

A connector manifest.json — declares what the connector is, how often it runs, and which preference fields the user fills in during setup (connectors/manifest reference).

PropertyTypeDescription
author?stringWho wrote the connector.
category?stringDirectory grouping — reading, social, finance, dev, media, etc.
config?Record<string, ManifestConfigField>The preference fields shown in the setup wizard. Preferences only — no credentials.
description?stringOne-line directory description.
document_semantics?ConnectorContentTypeDefault content type for documents this connector produces.
icon?stringA single emoji or an HTTPS URL to a square icon.
idstringStable connector-type id; pattern ^[a-z0-9-]+$. Required.
kind?stringWhat kind of source this is — feed, account, files, api.
namestringHuman-readable display name. Required.
needs_browser?booleanWhether the connector requires a Playwright browser. Default false.
schedule?string | nullHuman-readable sync cadence — "every 6 hours", "daily", "on demand", or null for a live-only connector. Optional (default: on demand).
transport?stringHow the connector reaches the source — http, browser, fs.
versionstringSemver version string. Required.

The result of a connector sync: the documents fetched this run and, optionally, the watermark the source should advance to. A connector may also return a bare ConnectorDocument[] for convenience — runConnector normalizes that to { documents } with no cursor change.

PropertyTypeDescription
cursor?WatermarkThe watermark to advance the source cursor to. Omit (or return { type: 'none' }) to leave the cursor unchanged. The cloud only advances if the new value is monotonically ahead (compare-and-swap).
documentsConnectorDocument[]The documents fetched this run, mapping 1:1 onto IngestDocumentInput.

A single field descriptor inside a manifest config object — describes one preference input shown in the connector’s setup wizard.

PropertyTypeDescription
label?stringDisplay label in the setup UI.
placeholder?stringOptional example text shown in the input.
type?stringInput type — text, text[], url, url[], path, number, boolean.

The outcome of validateConnectorManifest.

PropertyTypeDescription
errorsstring[]Human-readable error messages; empty when valid is true.
validbooleanTrue when no errors were found.

The options runConnector builds a ctx from. The CLI passes the source’s stored config and current cursor; tests inject fetch, log, and now for determinism.

Type ParameterDefault typeDescription
CRecord<string, unknown>The shape of the connector’s typed ctx.config preferences.
PropertyTypeDescription
config?CThe source’s preference values (no credentials). Defaults to {}.
cursor?WatermarkThe source’s current watermark. Defaults to { type: 'none' }.
fetchImpl?FetchLikeThe fetch implementation to expose as ctx.fetch. Defaults to global fetch.
logSink?(args: unknown[]) => voidA sink for ctx.log(...) calls. Defaults to collecting into the returned logs.
now?() => DateThe clock backing ctx.now(). Defaults to () => new Date().

The outcome of runConnector: the validated, deduped documents, the resolved cursor, and the captured log lines. Mirrors enough of what the cloud ingest reports for trove connector test to print a useful summary.

PropertyTypeDescription
cursorWatermarkThe cursor sync returned, or { type: 'none' } if it returned none.
documentsConnectorDocument[]The validated, deduped documents sync returned.
duplicatesSkippednumberCount of documents dropped as duplicates of an earlier id.
logsunknown[][]Captured ctx.log(...) lines (when no custom logSink was supplied).

The type a connector’s default export satisfies. A connector is an object with a sync method that fetches from its source and returns documents to index.

import { defineConnector } from '@ontrove/sdk';
export default defineConnector({
async sync(ctx) {
const res = await ctx.fetch(ctx.config.feedUrl as string);
return parse(await res.text());
},
});
Type ParameterDefault typeDescription
CRecord<string, unknown>The shape of the connector’s typed ctx.config preferences.
sync(ctx: ConnectorContext<C>): Promise<
| ConnectorSyncResult
| ConnectorDocument[]>;

The batch entry point. Fetches from the source and returns documents to index, optionally with a new cursor. May return a bare ConnectorDocument[] for convenience. Throw a plain Error to fail the run (the Mac app records the error and retries next tick).

ParameterType
ctxConnectorContext<C>

Promise< | ConnectorSyncResult | ConnectorDocument[]>

type ConnectorContentType = "text" | "transcript" | "highlight" | "bookmark";

The default content type Trove assigns a document when it omits contentType. Mirrors the GraphQL ContentType enum surfaced on IngestDocumentInput.


type FetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;

The standard fetch signature the SDK exposes on ConnectorContext.fetch. Matches the platform fetch so existing code ports unchanged.

ParameterType
urlstring | URL
init?RequestInit

Promise<Response>


type Watermark =
| {
type: "date";
value: string;
}
| {
max?: string;
type: "idSet";
values: readonly string[];
}
| {
type: "none";
};

A typed watermark describing how a source resumes between syncs. The opaque Source.cursor string is parsed into one of these (see the cursors-and-sources guide, watermark types).

  • date — the source is time-ordered and supports “since <date>” filtering (RSS, most APIs); the newest value you push becomes the next cursor.
  • idSet — the source has monotonic ids but no reliable date filter; advance to the highest id seen (max), optionally tracking a recent id set.
  • none — re-fetch everything each run and rely purely on (source, id) dedup. Always correct, just less efficient.
function defineConnector<C>(connector: TroveConnector<C>): TroveConnector<C>;

Validate and return a connector definition unchanged.

Using defineConnector(...) (rather than a bare satisfies TroveConnector) gives an eager runtime check: the CLI calls this when loading a connector module so a malformed export is rejected before any sync runs. The returned value is the same object, with full inferred types preserved.

Type ParameterDefault typeDescription
CRecord<string, unknown>The shape of the connector’s typed ctx.config preferences.
ParameterTypeDescription
connectorTroveConnector<C>The connector object to validate.

TroveConnector<C>

The same connector object.

If connector is not an object with a sync function.

export default defineConnector({
async sync(ctx) {
const res = await ctx.fetch(ctx.config.feedUrl as string);
return parseRss(await res.text());
},
});

function defineSync<C>(sync: (ctx: ConnectorContext<C>) => Promise<
| ConnectorSyncResult
| ConnectorDocument[]>): TroveConnector<C>;

A connector authored inline as a single sync function, for the common case where the connector has no other members. Equivalent to defineConnector({ sync }).

Type ParameterDefault typeDescription
CRecord<string, unknown>The shape of the connector’s typed ctx.config preferences.
ParameterTypeDescription
sync(ctx: ConnectorContext<C>) => Promise< | ConnectorSyncResult | ConnectorDocument[]>The connector’s sync(ctx) implementation.

TroveConnector<C>

A validated TroveConnector wrapping sync.


function isCredentialConfigKey(key: string): boolean;

Whether a single config key looks like a credential and must be rejected. Mirrors the cloud’s isSensitiveConfigKey.

ParameterTypeDescription
keystringThe config key to test.

boolean

True if the key matches a credential pattern.


function runConnector<C>(connector: TroveConnector<C>, options?: RunOptions<C>): Promise<RunResult>;

Run a connector’s sync against a built ctx and collect/validate the result.

This is the harness the CLI drives for trove connector dev / trove connector test: it builds the ConnectorContext from { config, cursor }, runs sync(ctx), normalizes a bare-array return, validates every document’s required fields (clear, indexed errors), and dedups by id. Errors thrown by sync itself propagate unchanged so the CLI can report a failed run exactly as the Mac app would.

Type ParameterDefault typeDescription
CRecord<string, unknown>The shape of the connector’s typed ctx.config preferences.
ParameterTypeDescription
connectorTroveConnector<C>The connector whose sync to run.
optionsRunOptions<C>The { config, cursor } spec plus test injection points.

Promise<RunResult>

The validated, deduped RunResult.

If sync throws, returns a malformed value, or a document fails validation.


function validateConnectorManifest(manifest: unknown): ManifestValidationResult;

Validate a connector manifest.json for trove connector validate.

Checks required fields and the id/version patterns, validates the config descriptors, and runs the credential-key lint (rejecting credential-shaped config keys — same spirit as the server-side validateConfig). Returns a structured result rather than throwing, so the CLI can print all errors at once.

ParameterTypeDescription
manifestunknownThe parsed manifest object to validate.

ManifestValidationResult

{ valid, errors }valid: true with an empty errors array on success.