import { kody } from 'kody:runtime' import { accountFromSecretName, GRANOLA_HOST } from './account.ts' import { hostApprovalUrl, secretSetupUrl } from './urls.ts' type SecretMeta = { name?: string; scope?: string; description?: string; allowed_hosts?: Array } /** User-scoped secret metadata (names, hosts — never values). Null when Kody cannot list them here. */ export async function userSecrets(): Promise | null> { try { const listed = (await kody.secretList({ scope: 'user' })) as unknown const entries: Array = listed && typeof listed === 'object' && Array.isArray((listed as { secrets?: unknown }).secrets) ? (listed as { secrets: Array }).secrets : Array.isArray(listed) ? (listed as Array) : [] const map = new Map() for (const entry of entries) if (entry?.name && (entry.scope === 'user' || !entry.scope)) map.set(entry.name, entry) return map } catch { return null } } export type CredentialStatus = { secretName: string saved: boolean | null hostApproved: boolean | null ready: boolean | null setupUrl?: string hostApprovalUrl?: string } /** Whether the secret exists and may be sent to public-api.granola.ai (metadata only). */ export function credentialStatus(secretName: string, secrets: Map | null): CredentialStatus { if (!secrets) return { secretName, saved: null, hostApproved: null, ready: null } const meta = secrets.get(secretName) if (!meta) return { secretName, saved: false, hostApproved: false, ready: false, setupUrl: secretSetupUrl(secretName) } const hostApproved = (meta.allowed_hosts ?? []).includes(GRANOLA_HOST) return { secretName, saved: true, hostApproved, ready: hostApproved, hostApprovalUrl: hostApproved ? undefined : hostApprovalUrl(secretName) } } /** Accounts discovered from user secrets named `granola-`. */ export function discoverAccounts(secrets: Map | null) { if (!secrets) return null const out: Array<{ account: string } & CredentialStatus & { description?: string }> = [] for (const [name, meta] of secrets) { const account = accountFromSecretName(name) if (account) out.push({ account, ...credentialStatus(name, secrets), description: meta.description }) } return out.sort((x, y) => x.account.localeCompare(y.account)) }