import { kody } from 'kody:runtime' export const DEFAULT_INTEGRATION = 'notion' export type NotionAccountParams = { account?: string integration?: string } export type NotionAccountSummary = { account: string | null integration: string default: boolean useWhen: string } function normalizeIntegrationName(name: string): string { return name.trim().toLowerCase() } /** * Resolve which OAuth integration to use. * * - `integration` wins when set (exact integration name, e.g. `notion-work`) * - `account: 'work'` → `notion-work` (Kody `-` convention) * - `account: 'default'` / `'notion'` / omitted → `notion` * - bare `account` values that already start with `notion-` are used as-is */ export function resolveIntegrationName(params: NotionAccountParams = {}): string { if (typeof params.integration === 'string' && params.integration.trim()) { return normalizeIntegrationName(params.integration) } if (typeof params.account === 'string' && params.account.trim()) { const account = normalizeIntegrationName(params.account) if (account === 'default' || account === 'notion') return DEFAULT_INTEGRATION if (account === DEFAULT_INTEGRATION || account.startsWith('notion-')) return account return 'notion-' + account } return DEFAULT_INTEGRATION } export function accountAliasForIntegration(integrationName: string): string | null { const name = normalizeIntegrationName(integrationName) if (name === DEFAULT_INTEGRATION) return null if (name.startsWith('notion-')) return name.slice('notion-'.length) return name } /** * List connected Notion OAuth integrations (`notion` and `notion-*`). * Discovered from the signed-in user's saved integrations — not a hard-coded alias table. */ export async function listNotionAccounts(): Promise<{ accounts: NotionAccountSummary[] defaultIntegration: string }> { const listed = await kody.integrationList({}) const items = Array.isArray(listed) ? listed : Array.isArray((listed as { integrations?: unknown[] })?.integrations) ? (listed as { integrations: unknown[] }).integrations : Array.isArray((listed as { items?: unknown[] })?.items) ? (listed as { items: unknown[] }).items : [] const accounts = items .map((item) => { const name = item && typeof item === 'object' && typeof (item as { name?: unknown }).name === 'string' ? normalizeIntegrationName((item as { name: string }).name) : '' if (!name || (name !== DEFAULT_INTEGRATION && !name.startsWith('notion-'))) return null const alias = accountAliasForIntegration(name) return { account: alias, integration: name, default: name === DEFAULT_INTEGRATION, useWhen: name === DEFAULT_INTEGRATION ? 'Default Notion workspace. Use when the user does not name a specific workspace.' : 'Use when the user asks for the ' + alias + ' Notion workspace (integration ' + name + ').', } satisfies NotionAccountSummary }) .filter((entry): entry is NotionAccountSummary => entry !== null) .sort( (a, b) => Number(b.default) - Number(a.default) || a.integration.localeCompare(b.integration), ) return { accounts, defaultIntegration: DEFAULT_INTEGRATION } } /** * List connected Notion accounts (default export for `kody:@kentcdodds/notion/accounts`). * @example * import listNotionAccounts from 'kody:@kentcdodds/notion/accounts' * const { accounts } = await listNotionAccounts() * // => { accounts: [{ account: null, integration: 'notion', default: true }, { account: 'work', integration: 'notion-work', default: false }] } */ export default async function listNotionAccountsEntrypoint() { return await listNotionAccounts() }