@karmaniverous/jeeves-watcher-sdk is a typed client for the watcher's HTTP API. Since 0.3.0 it is jeeves's catalog client over the watcher's endpoint catalog (WATCHER_CATALOG in @karmaniverous/jeeves-watcher-core): every route is a method, inputs and responses are checked against the route's Zod schemas, and every failure is a WatcherError.
import { createWatcherClient } from '@karmaniverous/jeeves-watcher-sdk';
const watcher = createWatcherClient({
baseUrl: 'http://watcher.internal.example:1936',
apiKey: process.env.WATCHER_API_KEY, // the service's api.apiKey; never sent to status
timeoutMs: 10_000, // default
});
baseUrl may carry a path prefix but no query string or fragment. Pass fetch to use a custom agent or a test double; it must return real Response objects. A bad option throws WatcherError invalid_options.
The client has one method per catalog entry, named by the entry (D77): each name is also the route's OpenAPI operationId and, as watcher_<name in snake_case>, its plugin tool. The two source-document entries form the documents group.
| Method | Route | Kind |
|---|---|---|
status() |
GET /status (no key) |
read |
config({ path? }) |
GET /config |
read |
configApply({ patch, replace? }) |
POST /config/apply |
write |
getConfigSchema() |
GET /config/schema |
read |
matchConfig({ paths }) |
POST /config/match |
read |
validateConfig({ config?, testPaths? }) |
POST /config/validate |
read |
search({ query, ... }) |
POST /search |
read |
scan({ filter, ... }) |
POST /scan |
read |
listFacets({ rules? }) |
GET /search/facets |
read |
countFacets({ fields, filter?, rules?, limit? }) |
POST /search/facets/counts |
read |
render({ path } | { source }) |
POST /render |
read |
walk({ globs }) |
POST /walk |
read |
listIssues() |
GET /issues |
read |
setMetadata({ path | source, metadata }) |
POST /metadata |
write |
reindex({ scope?, path?, dryRun? }) |
POST /reindex |
write |
registerRules({ source, rules }) |
POST /rules/register |
write |
reapplyRules({ globs }) |
POST /rules/reapply |
write |
rebuildMetadata({}) |
POST /rebuild-metadata |
write |
unregisterRules({ source }) |
DELETE /rules/unregister |
destructive |
unregisterRulesBySource({ source }) |
DELETE /rules/unregister/:source |
destructive |
deletePoints({ filter }) |
POST /points/delete |
destructive |
getVcsStatus() |
GET /vcs/status |
read |
listVcsHistory({ glob, since?, until?, limit? }) |
GET /vcs/history |
read |
showVcsFile({ path, commit }) |
GET /vcs/show (text) |
read |
diffVcs({ glob, commit, commitEnd? }) |
GET /vcs/diff (text) |
read |
checkVcsExclusion({ path }) |
GET /vcs/check-exclusion |
read |
revertVcs({ glob, commit, existingOnly? }) |
POST /vcs/revert |
destructive |
excludeVcsPath({ glob, root?, remove?, confirm? }) |
POST /vcs/exclude |
destructive |
documents.notify(documents, { wait? }) |
POST /documents |
write |
documents.delete({ sources } | { filter }) |
POST /documents/delete |
destructive |
Request and response types are inferred from the catalog's schemas, so they always match the service the catalog describes. A method whose input has no required field can be called without one (watcher.listIssues(), watcher.reindex()).
Inputs are in their wire form. Query fields are strings, as they travel: listVcsHistory({ glob: '/data/docs/**', limit: '20' }). Bodies are JSON values.
Responses are parsed by the entry's response schema; fields a newer service adds are dropped. A new enum value from a newer service fails validation (invalid_response) until the SDK is upgraded: upgrade the SDK with the service. showVcsFile and diffVcs resolve with the raw text.
The VCS methods answer not_found while version control is off (vcs.enabled: false).
Types of inputs and outputs follow from the client: Parameters<WatcherClient['excludeVcsPath']>[0] is the input of excludeVcsPath, Awaited<ReturnType<WatcherClient['excludeVcsPath']>> its result.
await watcher.documents.notify([
{ source: 'app://chat/thread/123', rule: 'chat-thread' },
]);
notify resolves with the 202 body: the accepted references and the notifications the server rejected synchronously (a malformed reference, an unknown rule). The watcher then retrieves each document through its source callback; processing failures show up in listIssues().
notify(documents, { wait: true }) processes synchronously and resolves with one result per document. It is not bounded on the server: it returns when every document has been processed, which can take minutes behind document locks and embedding rate limits (D137). Use it for backfill and tests, with a timeoutMs sized for the batch.
documents.delete({ sources }) or documents.delete({ filter }) removes source documents (never file points). A filter without a condition is refused before sending.
Every method throws WatcherError (isWatcherError(e) narrows):
code |
When | status |
|---|---|---|
the server's code (invalid_request, not_found, conflict, confirm_required, not_available, unauthorized, ...) |
a non-2xx reply carrying one | the HTTP status |
http_<status> |
a non-2xx reply without a code (an older server, a proxy) | the HTTP status |
invalid_request |
the input failed the entry's request schema; nothing was sent | none |
invalid_response |
a 2xx reply that is not JSON or fails the response schema | the HTTP status when parsed |
timeout |
no reply within timeoutMs |
none |
network_error |
the request could not be sent or the connection failed | none |
invalid_options |
createWatcherClient options are invalid |
none |
message is the server's error text (with its message when different) or the SDK's description; details carries the server's details or the Zod issues.
retryAfterMs is the delay the server asked for (Retry-After, sent with 503 not_available when a root's git lock or a document lock stays busy), in milliseconds, clamped to 0..MAX_RETRY_AFTER_MS (3,600,000, exported). The SDK never retries: wait at least that long before calling again.
The default timeout is 10 s. Some calls wait inside the service longer than that: excludeVcsPath and revertVcs wait up to 30 s for the root's git lock, and notify(..., { wait: true }) processes the whole batch. Raise timeoutMs for them (the CLI uses 60 s).
A timeout does not cancel the operation (D119). The service keeps running a state-changing call after the client gives up, so a timed-out excludeVcsPath, revertVcs, setMetadata or documents.notify may still complete. Check before retrying: checkVcsExclusion for an exclusion, listVcsHistory or getVcsStatus for a revert, listIssues or a search for documents.
f builds Qdrant filters for search, scan, deletePoints and documents.delete:
import { f } from '@karmaniverous/jeeves-watcher-sdk';
await watcher.scan({
filter: f.and(f.eq('type', 'chat_thread'), f.eq('user_id', userId)),
});