jeeves-watcher
    Preparing search index...

    SDK Guide

    @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)),
    });