import { listEndpoint, pageSpec, parseWithAccount } from '../lib/api.ts' type Folder = { id: string; object: 'folder'; name: string; parent_folder_id: string | null } type FolderNode = Folder & { path: string; depth: number; children: Array } /** Attach `path` ("Parent / Child") and `depth` to each folder and build the tree (roots first). */ export function folderTree(folders: Array) { const byId = new Map(folders.map((f) => [f.id, { ...f, path: f.name, depth: 0, children: [] }])) const roots: Array = [] for (const node of byId.values()) { const parent = node.parent_folder_id ? byId.get(node.parent_folder_id) : undefined if (parent) parent.children.push(node) else roots.push(node) } const walk = (node: FolderNode, prefix: string, depth: number) => { node.path = prefix ? `${prefix} / ${node.name}` : node.name node.depth = depth for (const child of node.children) walk(child, node.path, depth + 1) } for (const root of roots) walk(root, '', 0) return { roots, flat: [...byId.values()].map(({ children, ...f }) => ({ ...f, childCount: children.length })) } } /** * List the folders the API key can access (`GET /v1/folders`, alphabetical), following cursors. * Each folder gets a readable `path` and `depth` computed from `parent_folder_id`; `tree: true` * also returns the nested tree. Use a folder `id` with ./notes/list `folderId`. * * @param input.account - Account name (secret `granola-`). Required. @param input.secretName - Secret override. * @param input.tree - Also return `tree` (nested roots → children). Default false. * @param input.limit - Max folders (1-5000, default 5000). @param input.paginate - Follow cursors (default true). @param input.cursor - Resume. * @returns `{ account, count, folders: [{ id, name, parent_folder_id, path, depth, childCount }], tree?, hasMore, cursor }` * * @example * import listFolders from 'kody:@charlesmatte/granola/folders/list' * * const { folders } = await listFolders({ account: 'work' }) */ export default async function listGranolaFolders(input: unknown) { const p = parseWithAccount<{ tree?: boolean; limit?: number; cursor?: string; paginate?: boolean }>( input, { tree: { type: 'boolean' }, ...pageSpec(5000) }, 'folders/list', ) const r = await listEndpoint(p.acct, { path: '/v1/folders', itemsKey: 'folders', limit: p.limit ?? 5000, maxPageSize: 30, cursor: p.cursor, paginate: p.paginate ?? true }) const t = folderTree(r.items) return { account: p.acct.account, count: r.count, folders: t.flat, ...(p.tree ? { tree: t.roots } : {}), hasMore: r.hasMore, cursor: r.cursor, pages: r.pages } }