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 `<provider>-<purpose>` 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.integration_list({})
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()
}