# @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 ```ts 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. ```bash kody execute --local --invoke kody:@kody/alexa/skill-request --params '{"body":{"version":"1.0","request":{"type":"LaunchRequest"}},"dryRun":true}' ``` ```bash kody execute --local --invoke kody:@kody/alexa/verify --params '{"dryRun":true}' ``` ```ts 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: ```ts 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: 1. `webhookUrlMint` with `kodyId: "@kody/alexa"`, `package_scope: "kody"`, `webhookName: "skill"` (or `packageId` if the scoped name is not accepted). 2. The owner copies the URL from package settings, Webhooks section. MCP does not return the credential. 3. Paste that URL into the Alexa skill HTTPS endpoint. `responseMode` is `sync`, so the HTTP body is this export's return value. Keep that value a bare Alexa response. Do not wrap it in `{ ok: true }`. 4. Optional synthetic check: `webhookSyntheticDispatch` with `request.json` set to a minimal `LaunchRequest`. Side effects are only spoken stub text. Do not log or repeat the webhook URL. ## Edge cases - Signature verification is not implemented. `./verify` always returns `skipped: true` and `unsafeForProduction: true`. `dryRun` or `allowUnsigned` is a loud dev bypass, not a real check. - Kody's webhook header allowlist does not forward `Signature` or `SignatureCertChainUrl`, 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. - `alexaSkillApplicationId` is a user secret name. Unresolved placeholders count as unset. Live calls (no `dryRun`, no `allowUnsigned`) with the secret set must match `session.application.applicationId` or `context.System.application.applicationId`. Mismatch speaks a refusal and sets `sessionAttributes.applicationIdCheck` to `mismatch`. Do not include the secret value in speech or logs. - `dryRun: true` sets `applicationIdCheck` to `skipped-dry-run` and prefixes speech with `Dry run`. - `alexaForwardUrl` is detected only so speech can say the secret is set. This package must not `fetch` it. Propagation from Kody is a later step, not this stub. - Supported `request.type` values are `LaunchRequest`, `IntentRequest`, and `SessionEndedRequest`. Anything else gets a spoken unsupported-type reply and ends the session. - `SessionEndedRequest` returns `response: { shouldEndSession: true }` and no output speech. - Launch and intent replies keep the session open and include a reprompt, because Alexa expects one when `shouldEndSession` is false. - `.kody/icon.png` is the official Alexa Smile wordmark already committed. Do not replace or redraw it.