import { listEndpoint, parseWithAccount } from '../lib/api.ts' import { granolaRequest } from '../lib/client.ts' import { transcriptToText, type TranscriptItem } from '../lib/transcript.ts' import { NOTE_ID_PATTERN } from '../lib/validate.ts' /** * Get one meeting note (`GET /v1/notes/{note_id}`): title, owner, calendar event, attendees, * folder membership, AI summary (text + Markdown), web URL and — when the key belongs to the note's * owner — private notes. With `includeTranscript: true` the transcript is added; if Granola answers * `413 TRANSCRIPT_TOO_LARGE`, the note is re-fetched without it and the transcript is paged in * from `/transcript` automatically. * * @param input.account - Account name (secret `granola-`). Required. @param input.secretName - Secret override. * @param input.noteId - `not_…` id (from ./notes/list or a webhook payload). Required. * @param input.includeTranscript - Add the transcript (default false). * @param input.transcriptFormat - `'text'` (default), `'items'` or `'both'` when the transcript is included. * @returns The Granola note object (`summary_markdown`, `attendees`, `calendar_event`, …) plus `transcript_text` / `transcript` as requested and `transcriptSource` (`inline` | `paged`). * * @example * import getNote from 'kody:@charlesmatte/granola/notes/get' * * const note = await getNote({ account: 'work', noteId: 'not_1d3tmYTlCICgjy', includeTranscript: true }) */ export default async function getGranolaNote(input: unknown) { const p = parseWithAccount<{ noteId: string; includeTranscript?: boolean; transcriptFormat?: 'text' | 'items' | 'both' }>( input, { noteId: { type: 'string', required: true, pattern: NOTE_ID_PATTERN }, includeTranscript: { type: 'boolean' }, transcriptFormat: { type: 'enum', values: ['text', 'items', 'both'] }, }, 'notes/get', ) const path = `/v1/notes/${p.noteId}` let transcriptSource: 'inline' | 'paged' | undefined let items: Array | undefined let note: Record if (p.includeTranscript) { const res = await granolaRequest>(p.acct, { path, query: { include: 'transcript' }, throwOnError: false }) if (res.status === 413) { note = (await granolaRequest>(p.acct, { path })).data ?? {} const t = await listEndpoint(p.acct, { path: `${path}/transcript`, itemsKey: 'transcript', limit: 50000, maxPageSize: 100, paginate: true }) items = t.items transcriptSource = 'paged' } else if (!res.ok) { note = (await granolaRequest>(p.acct, { path, query: { include: 'transcript' } })).data ?? {} // rethrows with a helpful message } else { note = res.data ?? {} items = Array.isArray(note.transcript) ? note.transcript : [] transcriptSource = 'inline' } } else { note = (await granolaRequest>(p.acct, { path })).data ?? {} } const { transcript: _drop, ...rest } = note const format = p.transcriptFormat ?? 'text' return { account: p.acct.account, ...rest, ...(items ? { transcriptSource, transcriptItemCount: items.length, ...(format !== 'items' ? { transcript_text: transcriptToText(items) } : {}), ...(format !== 'text' ? { transcript: items } : {}), } : {}), } }