import { WearableConversation, WearableConnectorFactoryOptions, WearableSourceConnector, WearableConnectorRegistration } from '@remnic/core'; import { ConnectorApiError } from '@remnic/core/http-retry'; /** * Minimal Granola public API client (raw fetch, no SDK). * * API verified against the official OpenAPI at * https://docs.granola.ai/api-reference/list-notes and * https://docs.granola.ai/api-reference/get-note (fetched 2026-07-21): * - Base URL: https://public-api.granola.ai * - Auth: `Authorization: Bearer grn_` * - `GET /v1/notes?created_after=&created_before=&cursor=&page_size=` → * `{ notes: NoteSummary[], hasMore, cursor }` (page_size 1..30, default 10); * `created_after`/`created_before` accept date or date-time. The list only * returns notes that already have an AI summary + transcript. * - `GET /v1/notes/{id}?include=transcript` → full Note with `calendar_event` * (`scheduled_start_time`/`scheduled_end_time`), `attendees`, `summary_text`, * `summary_markdown`, and `transcript` items * `{ speaker: { source, diarization_label? }, text, start_time, end_time }`. * - Rate limits: 5 req/s sustained, 25 burst → 429 on excess. * * A non-2xx or network failure throws GranolaApiError (a backend failure); an * empty `notes` array is a real empty result, never conflated (AGENTS.md §22). */ declare const GRANOLA_DEFAULT_BASE_URL = "https://public-api.granola.ai"; /** Hard API maximum for `page_size` on the notes list. */ declare const NOTES_MAX_PAGE_SIZE = 30; interface GranolaSpeaker { source?: string | null; diarization_label?: string | null; } interface GranolaTranscriptItem { speaker?: GranolaSpeaker | null; text?: string | null; start_time?: string | null; end_time?: string | null; } interface GranolaCalendarEvent { event_title?: string | null; organiser?: string | null; scheduled_start_time?: string | null; scheduled_end_time?: string | null; } interface GranolaUser { name?: string | null; email?: string | null; } interface GranolaNote { id: string; title?: string | null; created_at?: string | null; updated_at?: string | null; calendar_event?: GranolaCalendarEvent | null; attendees?: GranolaUser[] | null; summary_text?: string | null; summary_markdown?: string | null; transcript?: GranolaTranscriptItem[] | null; } interface NotesPage { notes: GranolaNote[]; nextCursor: string | null; } interface GranolaClientOptions { apiKey: string; baseUrl?: string; fetchImpl?: typeof fetch; timeoutMs?: number; sleep?: (ms: number) => Promise; } declare class GranolaApiError extends ConnectorApiError { constructor(message: string, status?: number); } declare class GranolaClient { private readonly apiKey; private readonly baseUrl; private readonly fetchImpl; private readonly timeoutMs; private readonly sleep; constructor(options: GranolaClientOptions); /** One page of note summaries in the half-open [createdAfter, createdBefore) window. */ listNotes(params: { createdAfter: string; createdBefore: string; cursor?: string | null; signal?: AbortSignal; }): Promise; /** A single note with its transcript, calendar event, summary, and attendees. */ getNote(id: string, signal?: AbortSignal): Promise; /** Cheap auth probe. */ verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string; }>; private requestJson; } /** * Normalize Granola notes into Remnic's provider-agnostic * `WearableConversation` shape, plus the timezone-aware day-window helper the * Granola `created_after`/`created_before` filters need. * * Granola transcript items carry absolute `start_time`/`end_time` (ISO) and a * `speaker.source` of `microphone` (the wearer's own captured audio) or * `speaker` (other meeting audio); iOS adds a `diarization_label` * (`Speaker A/B/...`). The wearables speaker registry owns final naming. * Meeting timing prefers the linked calendar event; notes with a summary but no * transcript degrade to a single `note` segment. */ declare const GRANOLA_SOURCE_ID = "granola"; /** * Half-open [createdAfter, createdBefore) UTC ISO bounds of a local day — the * window the Granola notes list filters on. Field names map to the Granola * API; the DST-aware window math is core's `activityDayWindow`. */ declare function granolaDayWindow(date: string, timezone: string): { createdAfter: string; createdBefore: string; }; declare function noteToConversation(note: GranolaNote): WearableConversation; /** * @remnic/connector-granola — Granola meeting-notes connector. * * À-la-carte optional companion of @remnic/core: installing core alone * never pulls this in; core discovers it at runtime via a * computed-specifier dynamic import (see wearables/registry.ts) or via * a direct import of this module, which self-registers idempotently. * * API key: `wearables.sources.granola.apiKey`, else the * `REMNIC_GRANOLA_API_KEY` / `GRANOLA_API_KEY` environment variables * (checked in that order). Create a key under Settings → Connectors → * API keys in the Granola app (Business/Enterprise plans). */ declare function resolveGranolaApiKey(configuredKey: string | undefined, env?: NodeJS.ProcessEnv): string | undefined; declare function createGranolaConnector(options: WearableConnectorFactoryOptions): WearableSourceConnector; declare const wearableConnectorRegistration: WearableConnectorRegistration; /** * Idempotently register the connector with the core registry. Importing * this module registers it as a side effect; calling this again is safe * (returns false when already registered). */ declare const ensureGranolaConnectorRegistered: () => boolean; export { GRANOLA_DEFAULT_BASE_URL, GRANOLA_SOURCE_ID, GranolaApiError, type GranolaCalendarEvent, GranolaClient, type GranolaClientOptions, type GranolaNote, type GranolaSpeaker, type GranolaTranscriptItem, type GranolaUser, NOTES_MAX_PAGE_SIZE, type NotesPage, createGranolaConnector, ensureGranolaConnectorRegistered, granolaDayWindow, noteToConversation, resolveGranolaApiKey, wearableConnectorRegistration };