README.md

# Ekko

Elixir SDK for building [Amazon Alexa custom skills](https://developer.amazon.com/en-US/docs/alexa/custom-skills/understanding-custom-skills.html).

One struct, one namespace. Parse requests, pattern-match on the inner type,
pipe a response together, and let `Ekko.Plug` handle verification and
serialization.

## Installation

Add `ekko` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ekko, "~> 0.1.0"}
  ]
end
```

## Quick start

Define a skill:

```elixir
defmodule MyApp.GreeterSkill do
  use Ekko.Skill

  @impl Ekko.Skill
  def handle_request(%Ekko.Request.Launch{}, ekko) do
    response =
      ekko
      |> Ekko.speak("Welcome! Ask me anything.")
      |> Ekko.reprompt("Go ahead, I'm listening.")
      |> Ekko.should_end_session(false)
      |> Ekko.build()

    {:ok, response}
  end

  def handle_request(%Ekko.Request.Intent{intent: %{name: "HelloIntent"}}, ekko) do
    response =
      ekko
      |> Ekko.speak("Hello from Ekko!")
      |> Ekko.should_end_session(true)
      |> Ekko.build()

    {:ok, response}
  end
end
```

Mount the plug in your router:

```elixir
# In a Phoenix endpoint or standalone Plug router
forward "/alexa", Ekko.Plug, skill: MyApp.GreeterSkill
```

That's it. Ekko handles request verification, JSON parsing, skill dispatch,
and response serialization.

## Features

- **Request parsing** — typed structs for `LaunchRequest`, `IntentRequest`,
  `SessionEndedRequest`, all five `AudioPlayer` playback events,
  `PlaybackController` commands, and `System.ExceptionEncountered`
- **Response builder** — speech (plain text / SSML), cards, directives,
  session attributes, AudioPlayer playback controls
- **Skill lifecycle** — `before_request/1`, `handle_request/2`,
  `after_request/2`, `handle_error/3` with sensible defaults
- **Request verification** — full 5-step Amazon pipeline (URL validation,
  cert chain download + caching, X.509 trust + SAN check, RSA-SHA256
  signature, timestamp tolerance)
- **AudioPlayer integration** — `Ekko.AudioPlayer.Playlist` for playlist
  state with shuffle, loop, and cursor; pluggable `PlaylistStore` behaviour
  for persistence
- **Response constraints** — `AudioPlayer` and `PlaybackController` handlers
  are prevented from returning forbidden fields (`outputSpeech`, `card`,
  `reprompt`) at the framework level

## Configuration

```elixir
# config/config.exs
config :ekko,
  verify_requests: true,                # false in test
  cert_cache_ttl_ms: 86_400_000,        # 24h
  timestamp_tolerance_seconds: 150,
  playlist_store: Ekko.AudioPlayer.PlaylistStore.ETS
```

## Guides

- [Getting Started](guides/getting-started.md)
- [AudioPlayer Integration](guides/audio-player.md)
- [Request Verification](guides/request-verification.md)

## License

Apache-2.0. See [LICENSE](LICENSE) for details.