import { kody } from 'kody:runtime'
import { DEFAULT_INTEGRATION, connectUrls } from './domain.ts'
import type { ResolvedXOAuth, XAccountParams, XAccountSummary } from './types.ts'
function normalizeIntegrationName(name: string): string {
return name.trim().toLowerCase()
}
/**
* Resolve which OAuth integration to use.
*
* - `integration` wins when set (exact name, for example `x-work`)
* - `account: 'work'` → `x-work`
* - `account: 'default'` / `'x'` / omitted → `x`
* - values that already start with `x-` are used as-is
*/
export function resolveIntegrationName(params: XAccountParams = {}): 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 === 'x') return DEFAULT_INTEGRATION
if (account.startsWith('x-')) return account
return `x-${account}`
}
return DEFAULT_INTEGRATION
}
export function accountAliasForIntegration(integrationName: string): string | null {
const name = normalizeIntegrationName(integrationName)
if (name === DEFAULT_INTEGRATION) return null
if (name.startsWith('x-')) return name.slice(2)
return name
}
export async function resolveOAuthAccount(
params: XAccountParams = {},
): Promise<ResolvedXOAuth> {
const integrationName = resolveIntegrationName(params)
const got = await kody.integration_get({ name: integrationName })
const cfg = (
got && typeof got === 'object' && 'integration' in got
? (got as { integration: Record<string, unknown> | null }).integration
: got
) as Record<string, unknown> | null
if (!cfg || typeof cfg !== 'object') {
const urls = connectUrls(integrationName)
throw new Error(
`No X OAuth integration named "${integrationName}". Connect it at ${urls.firstTime}`,
)
}
const accessTokenSecretName =
typeof cfg.accessTokenSecretName === 'string' ? cfg.accessTokenSecretName : ''
const refreshTokenSecretName =
typeof cfg.refreshTokenSecretName === 'string' ? cfg.refreshTokenSecretName : null
const clientId = typeof cfg.clientId === 'string' ? cfg.clientId : null
if (!accessTokenSecretName) {
throw new Error(
`X integration "${integrationName}" is missing an access token secret name. Reconnect at ${connectUrls(integrationName).reconnect}`,
)
}
return {
integrationName,
account: accountAliasForIntegration(integrationName),
accessTokenSecretName,
refreshTokenSecretName,
clientId,
}
}
export async function listXAccounts(): Promise<{
accounts: XAccountSummary[]
defaultIntegration: string
connect: ReturnType<typeof connectUrls>
}> {
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('x-'))) return null
const accessTokenSecretName =
item &&
typeof item === 'object' &&
typeof (item as { accessTokenSecretName?: unknown }).accessTokenSecretName === 'string'
? (item as { accessTokenSecretName: string }).accessTokenSecretName
: null
const alias = accountAliasForIntegration(name)
return {
account: alias,
integration: name,
default: name === DEFAULT_INTEGRATION,
accessTokenSecretName,
useWhen:
name === DEFAULT_INTEGRATION
? 'Default X account. Use when the caller does not name another connected account.'
: `Use when the caller asks for the ${alias} X account (integration ${name}).`,
} satisfies XAccountSummary
})
.filter((entry): entry is XAccountSummary => entry !== null)
.sort(
(a, b) =>
Number(b.default) - Number(a.default) || a.integration.localeCompare(b.integration),
)
return {
accounts,
defaultIntegration: DEFAULT_INTEGRATION,
connect: connectUrls(),
}
}
/**
* List connected `x` / `x-*` OAuth integrations.
* @example
* import listXAccounts from 'kody:@kody/x/accounts'
* const { accounts } = await listXAccounts()
*/
export default async function listXAccountsEntrypoint() {
return await listXAccounts()
}