Skip to content
← Public packages

@charlesmatte/granola

Search and read Granola meeting notes, summaries and transcripts from any agent; manage webhooks and legal holds safely.

src/index.ts

63 lines · 4.2 KB · TypeScript
Raw
import { GRANOLA_HOST, GRANOLA_ORIGIN, SECRET_PREFIX } from './lib/account.ts'
import { discoverAccounts, userSecrets } from './lib/secrets.ts'
import { DOCS } from './lib/urls.ts'
import { parseInput } from './lib/validate.ts'

const EXPORTS = {
	'./accounts': 'Configured accounts (secrets granola-<account>) and their Kody host approval',
	'./verify': 'Prove a key works and see which endpoints it can reach (read-only probes)',
	'./notes/list': 'List notes (date/folder filters, local title/owner filter, auto-pagination)',
	'./notes/get': 'One note: summary (Markdown), attendees, calendar event, folders, private notes, optional transcript',
	'./notes/search': 'Keyword search (words, "phrases", -exclude) over titles, summaries, attendees, private notes and optionally transcripts; ranked with snippets',
	'./notes/transcript': 'Full transcript in pages, as readable text and/or raw items',
	'./folders/list': 'Folders with computed paths and optional tree',
	'./audit/list': 'Workspace audit events (needs an Audit API key, Enterprise)',
	'./webhooks/list': 'Webhook endpoints this key manages',
	'./webhooks/create': 'WRITE (confirm): register an HTTPS endpoint for note events',
	'./webhooks/update': 'WRITE (confirm): change URL/scopes/events/folders/enabled',
	'./webhooks/delete': 'WRITE (confirm): delete an endpoint',
	'./legal-holds/list': 'Legal holds (Legal hold API key, Enterprise)',
	'./legal-holds/get': 'One legal hold',
	'./legal-holds/create': 'WRITE (confirm): place a hold',
	'./legal-holds/update': 'WRITE (confirm): rename / change workspace coverage',
	'./legal-holds/release': 'WRITE (confirm): release a hold (terminal)',
	'./legal-holds/custodians/list': 'People named on a hold',
	'./legal-holds/custodians/add': 'WRITE (confirm): name people on a hold',
	'./legal-holds/custodians/remove': 'WRITE (confirm): remove a custodian',
	'./mcp/tools': 'Optional MCP lane: connection status and tools of Granola’s official MCP server',
	'./mcp/connect': 'WRITE (confirm): add Granola’s MCP server to Kody (browser OAuth sign-in)',
	'./mcp/query': 'Semantic search: ask Granola a question about your meetings (MCP query_granola_meetings), answer with citations',
	'./mcp/call': 'Call any read-only Granola MCP tool',
}

/**
 * Overview of the Granola package: how accounts and keys work, every export, and the accounts
 * already configured in this Kody account (secret metadata only; no Granola request).
 * Start here, then ./verify({ account }) and ./notes/list({ account }).
 *
 * @returns `{ api, auth, accounts, exports, docs, safety }`
 *
 * @example
 * import granola from 'kody:@charlesmatte/granola'
 *
 * const overview = await granola({})
 */
export default async function granolaOverview(input: unknown) {
	parseInput(input, {}, 'granola')
	const accounts = discoverAccounts(await userSecrets())
	return {
		api: { baseUrl: GRANOLA_ORIGIN, plans: 'Personal/workspace API keys: Business or Enterprise (notes, search, folders, webhooks). Audit log: Enterprise + separate Audit API key. Legal holds: Enterprise + separate Legal hold API key. MCP lane: every plan.', rateLimit: 'burst 25 requests, then 5 requests/second (429 is retried with backoff)' },
		auth: {
			convention: `Kody user secret ${SECRET_PREFIX}<account> = a Granola API key (grn_…), allowed host ${GRANOLA_HOST}. Pass { account } on every call; there is no default.`,
			createKey: `Granola desktop app → Settings → Connectors → API keys → Create new key (${DOCS.apiKeys})`,
		},
		accounts: accounts?.map((a) => ({ account: a.account, ready: a.ready, hostApprovalUrl: a.hostApprovalUrl })) ?? 'not listable here; see ./accounts',
		exports: EXPORTS,
		search: {
			keyword: './notes/search({ account, query }) — API key; scans notes (default 100 newest, throttled to 5 req/s), exact words/phrases, snippets.',
			semantic: './mcp/query({ question }) — Granola MCP; natural-language answer with citations. One-time setup at https://kody.codes/account/mcp-servers (server "granola", URL https://mcp.granola.ai/mcp, then sign in to Granola).',
		},
		docs: DOCS,
		safety: 'Reads are free. Every write previews by default and runs only with confirm: true (dryRun: true always wins). Keys never enter the isolate: Kody injects them for the approved host only.',
	}
}