import { kody } from 'kody:runtime'
import { getAccount } from './account.ts'
import {
API_HOST,
accountSidSetupUrl,
authTokenSetupUrl,
resolveAccountSidSecretName,
resolveAuthTokenSecretName,
type TwilioAuthOptions,
} from './validation.ts'
import { listIncomingNumbers } from './incoming-numbers.ts'
import { listMessages } from './messages.ts'
import { sendMessage } from './send-message.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
}
return Array.isArray(result) ? result : []
}
async function listUserSecretNames(): Promise<Set<string>> {
try {
const listed = await kody.secret_list({ scope: 'user' })
const names = new Set<string>()
for (const entry of secretEntries(listed)) {
if (entry?.name && (entry.scope === 'user' || !entry.scope)) {
names.add(entry.name)
}
}
return names
} catch {
return new Set()
}
}
/**
* Local helper checks, plus a live account read when both secrets exist.
*
* Without secrets this still returns `{ ok: true, live: false }` and the
* prefilled setup URLs so the package is forkable. With secrets it proves
* host approval via account lookup — it never sends SMS.
*
* @example
* import smokeTest from 'kody:@kody/twilio/smoke-test'
* const result = await smokeTest()
*/
export async function smokeTest(params: TwilioAuthOptions = {}) {
const preview = await sendMessage({
...params,
to: '+15555550100',
from: '+15555550101',
body: 'kody twilio dry-run smoke',
dryRun: true,
})
if (!('dryRun' in preview) || preview.dryRun !== true) {
throw new Error('sendMessage dry-run self-check failed.')
}
const accountSidSecret = resolveAccountSidSecretName(params)
const authTokenSecret = resolveAuthTokenSecretName(params)
const names = await listUserSecretNames()
const hasAccountSid = names.has(accountSidSecret)
const hasAuthToken = names.has(authTokenSecret)
const setup = {
secrets: {
[accountSidSecret]: hasAccountSid,
[authTokenSecret]: hasAuthToken,
},
nextSteps: [
'Save the Account SID. Do not paste it into chat.',
accountSidSetupUrl(accountSidSecret),
'Save the Auth Token. Do not paste it into chat.',
authTokenSetupUrl(authTokenSecret),
'Approve host api.twilio.com for both secrets.',
],
}
if (!hasAccountSid || !hasAuthToken) {
return {
ok: true,
live: false,
host: API_HOST,
selfCheck: { dryRunSend: true },
accountSidSecret,
authTokenSecret,
setup,
}
}
const [account, numbers, messages] = await Promise.all([
getAccount(params),
listIncomingNumbers({ ...params, pageSize: 1 }),
listMessages({ ...params, pageSize: 1 }),
])
return {
ok: true,
live: true,
host: API_HOST,
selfCheck: { dryRunSend: true },
accountSidSecret,
authTokenSecret,
account: {
sid: account.account.sid,
status: account.account.status,
type: account.account.type,
friendlyName: account.account.friendlyName,
},
incomingNumberCount: numbers.items.length,
messageSampleCount: messages.items.length,
setup: {
secrets: {
[accountSidSecret]: true,
[authTokenSecret]: true,
},
nextSteps: [],
},
}
}
export default smokeTest