← Public packages
@charlesmatte/granola
Search and read Granola meeting notes, summaries and transcripts from any agent; manage webhooks and legal holds safely.
src/notes/get.ts
69 lines · 3.2 KB · TypeScriptimport { 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-<account>`). 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<TranscriptItem> | undefined
let note: Record<string, any>
if (p.includeTranscript) {
const res = await granolaRequest<Record<string, any>>(p.acct, { path, query: { include: 'transcript' }, throwOnError: false })
if (res.status === 413) {
note = (await granolaRequest<Record<string, any>>(p.acct, { path })).data ?? {}
const t = await listEndpoint<TranscriptItem>(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<Record<string, any>>(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<Record<string, any>>(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 } : {}),
}
: {}),
}
}