@kody/alexa
Alexa custom skill webhook into Kody. Checks the skill id, maps common requests, and leaves utterance forwarding unwired.
AGENTS.md
87 lines · 4.5 KB · Markdown@kody/alexa
Agent notes for the head-start Alexa skill stub. Human setup lives in README.md.
Alexa calls the minted skill webhook. This package answers with Alexa response JSON. It does not propagate the utterance. alexaForwardUrl is only detected so speech can say the secret is set.
Imports
import skillRequest from 'kody:@kody/alexa/skill-request'
import verifyAlexaRequest from 'kody:@kody/alexa/verify'kody:@kody/alexa is the same function as ./skill-request.
Direct calls accept only body, dryRun, and allowUnsigned. Unknown keys throw. body is the Alexa request object or a JSON string. Alexa itself sends many extra fields; those belong inside body, not beside it.
The minted webhook calls the same export with { webhook, request } (inputMode: request). Do not reject that envelope for unknown platform keys.
Platform calls that target @kody/alexa need package_scope: "kody".
Smoke tests
Prefer local CLI execute after publish. Pass dryRun: true so the call does not pretend to enforce the skill id.
kody execute --local --invoke kody:@kody/alexa/skill-request --params '{"body":{"version":"1.0","request":{"type":"LaunchRequest"}},"dryRun":true}'kody execute --local --invoke kody:@kody/alexa/verify --params '{"dryRun":true}'import skillRequest from 'kody:@kody/alexa/skill-request'
import verifyAlexaRequest from 'kody:@kody/alexa/verify'
const launch = await skillRequest({
body: { version: '1.0', request: { type: 'LaunchRequest' } },
dryRun: true,
})
const intent = await skillRequest({
body: {
version: '1.0',
request: {
type: 'IntentRequest',
intent: {
name: 'Ask',
slots: { query: { name: 'query', value: 'hello from the kitchen' } },
},
},
},
dryRun: true,
})
const signature = await verifyAlexaRequest({ dryRun: true })Expect launch.response.outputSpeech.text to include Dry run and not wired yet. Expect intent to include hello from the kitchen. Expect signature.verified === false and signature.implemented === false.
A second smoke with an unknown key must fail:
await skillRequest({ body: { version: '1.0', request: { type: 'LaunchRequest' } }, dryRun: true, extra: true })Webhook mint
Declaring kody.webhooks[name=skill] does not open ingress. After publish:
webhookUrlMintwithkodyId: "@kody/alexa",package_scope: "kody",webhookName: "skill"(orpackageIdif the scoped name is not accepted).- The owner copies the URL from package settings, Webhooks section. MCP does not return the credential.
- Paste that URL into the Alexa skill HTTPS endpoint.
responseModeissync, so the HTTP body is this export's return value. Keep that value a bare Alexa response. Do not wrap it in{ ok: true }. - Optional synthetic check:
webhookSyntheticDispatchwithrequest.jsonset to a minimalLaunchRequest. Side effects are only spoken stub text. Do not log or repeat the webhook URL.
Edge cases
- Signature verification is not implemented.
./verifyalways returnsskipped: trueandunsafeForProduction: true.dryRunorallowUnsignedis a loud dev bypass, not a real check. - Kody's webhook header allowlist does not forward
SignatureorSignatureCertChainUrl, so the live webhook cannot see the material Amazon signs with. Do not claim in-handler verification works until those headers are available or the platform verifies them. alexaSkillApplicationIdis a user secret name. Unresolved placeholders count as unset. Live calls (nodryRun, noallowUnsigned) with the secret set must matchsession.application.applicationIdorcontext.System.application.applicationId. Mismatch speaks a refusal and setssessionAttributes.applicationIdChecktomismatch. Do not include the secret value in speech or logs.dryRun: truesetsapplicationIdChecktoskipped-dry-runand prefixes speech withDry run.alexaForwardUrlis detected only so speech can say the secret is set. This package must notfetchit. Propagation from Kody is a later step, not this stub.- Supported
request.typevalues areLaunchRequest,IntentRequest, andSessionEndedRequest. Anything else gets a spoken unsupported-type reply and ends the session. SessionEndedRequestreturnsresponse: { shouldEndSession: true }and no output speech.- Launch and intent replies keep the session open and include a reprompt, because Alexa expects one when
shouldEndSessionis false. .kody/icon.pngis the official Alexa Smile wordmark already committed. Do not replace or redraw it.