← Public packages
@kody/blandPublic
src/smoke-test.ts
97 lines · 2.7 KB · TypeScript/**
* Safe Bland setup check: secret metadata + dry-run send-call preview.
* Pass `live: true` to also call `GET /v1/me` (read-only auth proof).
*
* @param input - optional live flag and auth overrides
* @returns Setup status and next steps
*
* @example
* import smokeTest from 'kody:@kody/bland/smoke-test'
* await smokeTest({ live: true })
*/
import { kody } from 'kody:runtime'
import {
API_HOST,
blandRequest,
resolveApiKeySecretName,
setupUrls,
type BlandAuthInput,
} from './core.ts'
import { readBlandSettings } from './settings.ts'
function secretEntries(result: unknown): Array<{ name?: string; scope?: string }> {
if (result && typeof result === 'object' && Array.isArray((result as { secrets?: unknown }).secrets)) {
return (result as { secrets: Array<{ name?: string; scope?: string }> }).secrets
}
if (Array.isArray(result)) return result as Array<{ name?: string; scope?: string }>
return []
}
export type SmokeTestInput = BlandAuthInput & {
/** When true, also GET /v1/me (requires saved key + host approval). */
live?: boolean
}
export default async function smokeTest(input: SmokeTestInput = {}) {
const settings = await readBlandSettings()
const apiKeySecret = resolveApiKeySecretName(input)
const names = new Set<string>()
try {
const listed = await kody.secretList({ scope: 'user' })
for (const entry of secretEntries(listed)) {
if (entry?.name) names.add(String(entry.name))
}
} catch {
/* listing can be empty in some contexts */
}
const hasApiKey = names.has(apiKeySecret)
const preview = await blandRequest({
...input,
method: 'POST',
path: '/v1/calls',
body: {
phone_number: '+15551234567',
task: 'Smoke-test preview only — do not dial.',
voice: settings.defaultVoice ?? 'maya',
max_duration: 1,
},
dryRun: true,
})
const result: Record<string, unknown> = {
ok: hasApiKey,
hasApiKey,
apiKeySecret,
settings: { defaultVoice: settings.defaultVoice },
sendCallPreview: preview,
setup: setupUrls(input),
approveHost: API_HOST,
next: [] as string[],
}
const next = result.next as string[]
if (!hasApiKey) {
next.push('Save Bland API key: ' + settings.apiKeyUrl)
next.push('Approve host ' + API_HOST + ' on that secret.')
} else {
next.push(
'Try auth: import me from "kody:@kody/bland/me"; await me()',
)
next.push(
'Dry-run a call: import { sendCall } from "kody:@kody/bland/send-call"; await sendCall({ phone_number, task })',
)
}
if (input.live) {
try {
result.me = await blandRequest({ ...input, method: 'GET', path: '/v1/me' })
result.ok = true
} catch (error) {
result.liveError = error instanceof Error ? error.message : String(error)
result.ok = false
}
}
return result
}