kryten-api-gate 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. kryten_api_gate-0.2.0/.github/prompts/plan-krytenApiGate.prompt.md +288 -0
  2. kryten_api_gate-0.2.0/.github/workflows/python-publish.yml +79 -0
  3. kryten_api_gate-0.2.0/.github/workflows/release.yml +125 -0
  4. kryten_api_gate-0.2.0/.gitignore +71 -0
  5. kryten_api_gate-0.2.0/PKG-INFO +113 -0
  6. kryten_api_gate-0.2.0/README.md +93 -0
  7. kryten_api_gate-0.2.0/config.example.json +14 -0
  8. kryten_api_gate-0.2.0/kryten-api-gate.code-workspace +16 -0
  9. kryten_api_gate-0.2.0/pyproject.toml +50 -0
  10. kryten_api_gate-0.2.0/src/kryten_api_gate/__init__.py +8 -0
  11. kryten_api_gate-0.2.0/src/kryten_api_gate/__main__.py +126 -0
  12. kryten_api_gate-0.2.0/src/kryten_api_gate/app.py +47 -0
  13. kryten_api_gate-0.2.0/src/kryten_api_gate/auth.py +36 -0
  14. kryten_api_gate-0.2.0/src/kryten_api_gate/config.py +62 -0
  15. kryten_api_gate-0.2.0/src/kryten_api_gate/deps.py +16 -0
  16. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/__init__.py +1 -0
  17. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/admin.py +187 -0
  18. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/chat.py +42 -0
  19. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/emotes.py +71 -0
  20. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/filters.py +77 -0
  21. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/kv.py +82 -0
  22. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/library.py +33 -0
  23. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/moderation.py +108 -0
  24. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/playback.py +61 -0
  25. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/playlist.py +105 -0
  26. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/polls.py +58 -0
  27. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/state.py +46 -0
  28. kryten_api_gate-0.2.0/src/kryten_api_gate/routes/system.py +64 -0
  29. kryten_api_gate-0.2.0/src/kryten_api_gate/schemas/__init__.py +1 -0
  30. kryten_api_gate-0.2.0/src/kryten_api_gate/schemas/responses.py +24 -0
  31. kryten_api_gate-0.2.0/systemd/kryten-api-gate.service +19 -0
@@ -0,0 +1,288 @@
1
+ # Plan: kryten-api-gate — HTTP REST Gateway for kryten-py
2
+
3
+ A FastAPI microservice that exposes the full `KrytenClient` API surface over HTTP REST. One instance per kryten-robot/NATS instance, single channel. API-key authenticated for service-to-service use. Upstream consumer web apps talk to one or more instances.
4
+
5
+ ---
6
+
7
+ ## Phase 0: Fix kryten-robot Routing Gap (Prerequisite, Blocking)
8
+
9
+ **Problem:** kryten-py's `__send_command()` publishes to `kryten.robot.command` (handled by `RobotCommandHandler`), but only basic commands (chat, playlist, playback, moderation) are in its dispatch table. Phase 2/3 admin commands (`setChannelCSS`, `setMotd`, `updateEmote`, filters, polls, ranks, banlist, chanlog, library) are silently dropped.
10
+
11
+ **Fix:** Add ~20 handler entries to `Kryten-Robot/kryten/robot_command_handler.py` routing them to `self.sender` (the `CytubeEventSender`), matching what `CommandSubscriber` already does.
12
+
13
+ **Commands to add:**
14
+ - `setMotd` / `set_motd`
15
+ - `setChannelCSS` / `set_channel_css`
16
+ - `setChannelJS` / `set_channel_js`
17
+ - `setOptions` / `set_options`
18
+ - `setPermissions` / `set_permissions`
19
+ - `updateEmote` / `update_emote`
20
+ - `removeEmote` / `remove_emote`
21
+ - `addFilter` / `add_filter`
22
+ - `updateFilter` / `update_filter`
23
+ - `removeFilter` / `remove_filter`
24
+ - `newPoll` / `new_poll`
25
+ - `vote`
26
+ - `closePoll` / `close_poll`
27
+ - `setChannelRank` / `set_channel_rank`
28
+ - `requestChannelRanks` / `request_channel_ranks`
29
+ - `requestBanlist` / `request_banlist`
30
+ - `unban`
31
+ - `readChanLog` / `read_chan_log`
32
+ - `searchLibrary` / `search_library`
33
+ - `deleteFromLibrary` / `delete_from_library`
34
+ - `playNext` / `play_next`
35
+
36
+ ---
37
+
38
+ ## Phase 1: Project Scaffold
39
+
40
+ ```
41
+ kryten-api-gate/
42
+ ├── pyproject.toml
43
+ ├── config.example.json
44
+ ├── systemd/kryten-api-gate.service
45
+ └── src/kryten_api_gate/
46
+ ├── __init__.py
47
+ ├── __main__.py # Entry point
48
+ ├── config.py # Pydantic config model
49
+ ├── app.py # FastAPI app factory
50
+ ├── deps.py # DI (get_client, get_config)
51
+ ├── auth.py # API key dependency
52
+ ├── routes/ # One file per domain
53
+ │ ├── chat.py
54
+ │ ├── playlist.py
55
+ │ ├── playback.py
56
+ │ ├── moderation.py
57
+ │ ├── admin.py
58
+ │ ├── emotes.py
59
+ │ ├── filters.py
60
+ │ ├── polls.py
61
+ │ ├── library.py
62
+ │ ├── kv.py
63
+ │ ├── state.py
64
+ │ └── system.py
65
+ └── schemas/
66
+ ├── __init__.py
67
+ ├── requests.py # Pydantic request models per route group
68
+ └── responses.py # Pydantic response models
69
+ ```
70
+
71
+ **Dependencies:** `kryten-py>=0.10.5`, `fastapi>=0.115`, `uvicorn>=0.30`, `pydantic>=2.0`
72
+
73
+ ---
74
+
75
+ ## Phase 2: Core Infrastructure
76
+
77
+ ### 2.1 Config (`config.py`)
78
+
79
+ Pydantic model loading JSON:
80
+
81
+ | Field | Type | Default | Notes |
82
+ |-------|------|---------|-------|
83
+ | `nats_url` | `str` | `nats://localhost:4222` | |
84
+ | `channel` | `str` | required | CyTube channel name |
85
+ | `domain` | `str` | `cytu.be` | |
86
+ | `http_host` | `str` | `127.0.0.1` | |
87
+ | `http_port` | `int` | `28288` | |
88
+ | `api_keys` | `list[str]` | required | Bearer tokens |
89
+ | `kv_read_only` | `bool` | `false` | When true, KV PUT/DELETE return 403 |
90
+ | `service_name` | `str` | `api-gate` | |
91
+ | `log_level` | `str` | `INFO` | |
92
+
93
+ ### 2.2 Entry Point (`__main__.py`)
94
+
95
+ Standard kryten pattern:
96
+ 1. Parse args (`--config`, `--log-level`)
97
+ 2. Load config (json or yaml, check cwd, then `/etc/kryten/`, then `/opt/kryten/api-gate/` with precedence)
98
+ 3. Create `KrytenClient` with config
99
+ 4. `await client.connect()`
100
+ 5. Create FastAPI app, inject client + config into `app.state`
101
+ 6. Run uvicorn server
102
+ 7. Signal handler for graceful shutdown → `client.disconnect()`
103
+
104
+ ### 2.3 Auth (`auth.py`)
105
+
106
+ - FastAPI dependency extracting `Authorization: Bearer <key>`
107
+ - Validates against `config.api_keys`
108
+ - Returns 401 on mismatch
109
+ - Health endpoint exempted (public for monitoring)
110
+
111
+ ### 2.4 Dependencies (`deps.py`)
112
+
113
+ ```python
114
+ def get_client(request: Request) -> KrytenClient:
115
+ return request.app.state.client
116
+
117
+ def get_config(request: Request) -> Config:
118
+ return request.app.state.config
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Phase 3: Route Implementation
124
+
125
+ All routes under `/api/v1/`. Channel is fixed from config (not passed per-request).
126
+
127
+ ### Chat (`/api/v1/chat`)
128
+
129
+ | Method | Endpoint | KrytenClient method |
130
+ |--------|----------|---------------------|
131
+ | POST | `/send` | `send_chat(channel, message)` |
132
+ | POST | `/pm` | `send_pm(channel, username, message)` |
133
+
134
+ ### Playlist (`/api/v1/playlist`)
135
+
136
+ | Method | Endpoint | KrytenClient method |
137
+ |--------|----------|---------------------|
138
+ | POST | `/add` | `add_media(channel, type, id, position, temp)` |
139
+ | DELETE | `/{uid}` | `delete_media(channel, uid)` |
140
+ | PUT | `/{uid}/move` | `move_media(channel, uid, position)` |
141
+ | POST | `/{uid}/jump` | `jump_to(channel, uid)` |
142
+ | DELETE | `/` | `clear_playlist(channel)` |
143
+ | POST | `/shuffle` | `shuffle_playlist(channel)` |
144
+ | PUT | `/{uid}/temp` | `set_temp(channel, uid, is_temp)` |
145
+
146
+ ### Playback (`/api/v1/playback`)
147
+
148
+ | Method | Endpoint | KrytenClient method |
149
+ |--------|----------|---------------------|
150
+ | POST | `/pause` | `pause(channel)` | (disableable by config)
151
+ | POST | `/play` | `play(channel)` |
152
+ | POST | `/seek` | `seek(channel, time_seconds)` | (disableable by config)
153
+ | POST | `/voteskip` | `voteskip(channel)` | (disableable by config)
154
+ | POST | `/next` | `play_next(channel)` |
155
+
156
+ ### Moderation (`/api/v1/moderation`)
157
+
158
+ | Method | Endpoint | KrytenClient method |
159
+ |--------|----------|---------------------|
160
+ | POST | `/kick` | `kick_user(channel, username, reason)` |
161
+ | POST | `/ban` | `ban_user(channel, username, reason)` |
162
+ | POST | `/mute` | `mute_user(channel, username)` |
163
+ | POST | `/shadow-mute` | `shadow_mute_user(channel, username)` |
164
+ | POST | `/unmute` | `unmute_user(channel, username)` |
165
+ | POST | `/assign-leader` | `assign_leader(channel, username)` |
166
+ | GET | `/banlist` | `request_banlist(channel)` |
167
+ | DELETE | `/ban/{ban_id}` | `unban(channel, ban_id)` |
168
+
169
+ ### Admin (`/api/v1/admin`)
170
+
171
+ | Method | Endpoint | KrytenClient method |
172
+ |--------|----------|---------------------|
173
+ | PUT | `/motd` | `set_motd(channel, motd)` |
174
+ | GET | `/motd` | `request_motd(channel)` |
175
+ | PUT | `/css` | `set_channel_css(channel, css)` |
176
+ | GET | `/css` | `request_channel_css(channel)` |
177
+ | PUT | `/js` | `set_channel_js(channel, js)` |
178
+ | GET | `/js` | `request_channel_js(channel)` |
179
+ | PUT | `/options` | `set_options(channel, options)` |
180
+ | GET | `/options` | `request_options(channel)` |
181
+ | PUT | `/permissions` | `set_permissions(channel, permissions)` |
182
+ | GET | `/permissions` | `request_permissions(channel)` |
183
+ | GET | `/ranks` | `request_channel_ranks(channel)` |
184
+ | PUT | `/rank` | `set_channel_rank(channel, username, rank)` |
185
+ | GET | `/log` | `read_chan_log(channel, count)` |
186
+
187
+ ### Emotes (`/api/v1/emotes`)
188
+ ## (Note: There is a way in the cytube UI to bulk import emotes via JSON, and to bulk export the JSON as well. No corresponding kryten-py methods exist, so we NEED to add `export_emotes(channel)` and `import_emotes(channel, emotes_json)` methods to KrytenClient, and route them here as POST `/import` and GET `/export`. The single-emote CRUD routes below are still needed for fine-grained updates.)
189
+ | Method | Endpoint | KrytenClient method |
190
+ |--------|----------|---------------------|
191
+ | PUT | `/{name}` | `update_emote(channel, name, image, source)` |
192
+ | DELETE | `/{name}` | `remove_emote(channel, name)` |
193
+
194
+ ### Filters (`/api/v1/filters`)
195
+
196
+ | Method | Endpoint | KrytenClient method |
197
+ |--------|----------|---------------------|
198
+ | POST | `/` | `add_filter(channel, name, source, flags, replace, ...)` |
199
+ | PUT | `/{name}` | `update_filter(channel, ...)` |
200
+ | DELETE | `/{name}` | `remove_filter(channel, name)` |
201
+
202
+ ### Polls (`/api/v1/polls`)
203
+
204
+ | Method | Endpoint | KrytenClient method |
205
+ |--------|----------|---------------------|
206
+ | POST | `/` | `new_poll(channel, title, options, obscured, timeout)` |
207
+ | POST | `/vote` | `vote(channel, option)` |
208
+ | POST | `/close` | `close_poll(channel)` |
209
+
210
+ ### Library (`/api/v1/library`)
211
+
212
+ | Method | Endpoint | KrytenClient method |
213
+ |--------|----------|---------------------|
214
+ | GET | `/search` | `search_library(channel, query, source)` |
215
+ | DELETE | `/{media_id}` | `delete_from_library(channel, media_id)` |
216
+
217
+ ### KV Store (`/api/v1/kv`)
218
+
219
+ | Method | Endpoint | KrytenClient method | Notes |
220
+ |--------|----------|---------------------|-------|
221
+ | GET | `/buckets/{bucket}/keys` | `kv_keys(bucket)` | |
222
+ | GET | `/buckets/{bucket}/keys/{key}` | `kv_get(bucket, key)` | |
223
+ | GET | `/buckets/{bucket}` | `kv_get_all(bucket)` | |
224
+ | PUT | `/buckets/{bucket}/keys/{key}` | `kv_put(bucket, key, value)` | Gated by `kv_read_only` |
225
+ | DELETE | `/buckets/{bucket}/keys/{key}` | `kv_delete(bucket, key)` | Gated by `kv_read_only` | (Delete whole bucket supported via `kv_delete(bucket, '*')`)
226
+
227
+ ### State (`/api/v1/state`)
228
+
229
+ | Method | Endpoint | KrytenClient method |
230
+ |--------|----------|---------------------|
231
+ | GET | `/user/{username}` | `get_user(channel, username)` |
232
+ | GET | `/channels` | `get_channels()` |
233
+ | GET | `/playlist` | `get_state_playlist_items(channel)` |
234
+ | GET | `/now-playing` | `get_state_current_media(channel)` |
235
+
236
+ ### System (`/api/v1/system`)
237
+
238
+ | Method | Endpoint | KrytenClient method | Auth |
239
+ |--------|----------|---------------------|------|
240
+ | GET | `/health` | local health check | **Public** |
241
+ | GET | `/version` | `get_version()` | Required |
242
+ | GET | `/stats` | `get_stats()` | Required |
243
+ | GET | `/services` | `get_services()` | Required |
244
+ | GET | `/config` | `get_config()` | Required |
245
+ | GET | `/ping` | `ping()` | Required |
246
+ | POST | `/reload` | `reload_config()` | Required |
247
+ | POST | `/shutdown` | `shutdown()` | Required |
248
+
249
+ ---
250
+
251
+ ## Phase 4: Packaging & Deployment
252
+
253
+ - `pyproject.toml` with console script `kryten-api-gate = kryten_api_gate.__main__:main`
254
+ - `config.example.json` with documented defaults
255
+ - `systemd/kryten-api-gate.service` — standard kryten pattern (port 28288, user `kryten`, `/opt/kryten/api-gate/`)
256
+
257
+ ---
258
+
259
+ ## Verification
260
+
261
+ 1. Start NATS + kryten-robot → start kryten-api-gate
262
+ 2. `GET /api/v1/system/ping` with API key → success response
263
+ 3. `POST /api/v1/chat/send` → message appears on CyTube
264
+ 4. `GET /api/v1/system/health` without auth → 200 (public)
265
+ 5. Any protected route without auth → 401
266
+ 6. KV PUT with `kv_read_only: true` → 403
267
+
268
+ ---
269
+
270
+ ## Key Decisions
271
+
272
+ - **Channel from config, not per-request** — matches single-channel-per-instance model across all kryten services
273
+ - **API keys only** — machine-to-machine; user auth lives in upstream consumer app
274
+ - **No event streaming** — REST only; upstream app can poll state endpoints
275
+ - **Health is unauthenticated** — standard for monitoring/load balancers
276
+ - **KV write gating via config** — allows deployment in read-only mode
277
+ - **Phase 0 is blocking** — API gate depends on robot dispatching all commands properly
278
+
279
+ ---
280
+
281
+ ## Excluded from Scope
282
+
283
+ - WebSocket/SSE streaming
284
+ - OTP/session user auth
285
+ - Frontend/UI
286
+ - Multi-channel or multi-NATS routing
287
+ - Docker
288
+ - Bulk emote import (consumer can loop PUT calls)
@@ -0,0 +1,79 @@
1
+ # This workflow will upload a Python Package to PyPI when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ name: Publish Python Package to PyPI
5
+
6
+ on:
7
+ workflow_call: # Called directly from release.yml to bypass GITHUB_TOKEN cross-workflow trigger limitation
8
+ release:
9
+ types: [published]
10
+ push:
11
+ tags:
12
+ - 'kryten-api-gate-v*'
13
+ - 'v*'
14
+
15
+ permissions:
16
+ contents: read
17
+ id-token: write # Required for OIDC trusted publishing
18
+
19
+ jobs:
20
+ release-build:
21
+ name: Build distribution packages
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - name: Checkout repository
26
+ uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v4
30
+ with:
31
+ version: "latest"
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.11"
37
+
38
+ - name: Build release distributions
39
+ run: |
40
+ uv build
41
+ echo "📦 Built packages:"
42
+ ls -lh dist/
43
+
44
+ - name: Upload distributions
45
+ uses: actions/upload-artifact@v4
46
+ with:
47
+ name: release-dists
48
+ path: dist/
49
+
50
+ pypi-publish:
51
+ name: Publish to PyPI
52
+ runs-on: ubuntu-latest
53
+ needs:
54
+ - release-build
55
+ environment:
56
+ name: pypi
57
+ permissions:
58
+ # IMPORTANT: this permission is mandatory for trusted publishing
59
+ id-token: write
60
+
61
+ steps:
62
+ - name: Retrieve release distributions
63
+ uses: actions/download-artifact@v4
64
+ with:
65
+ name: release-dists
66
+ path: dist/
67
+
68
+ - name: Publish release distributions to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+ with:
71
+ packages-dir: dist/
72
+ skip-existing: true
73
+ attestations: false # workflow_call sets workflow_ref=release.yml but attestations check uses that not job_workflow_ref, causing a mismatch with the trusted publisher config
74
+
75
+ - name: Success notification
76
+ if: success()
77
+ run: |
78
+ echo "✅ Successfully published to PyPI!"
79
+ echo "🔗 Package URL: https://pypi.org/project/kryten-api-gate/"
@@ -0,0 +1,125 @@
1
+ name: Release Automation
2
+
3
+ # Automatically create GitHub releases when version in pyproject.toml changes
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ paths:
9
+ - 'pyproject.toml'
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: write
14
+ pull-requests: read
15
+ id-token: write
16
+
17
+ jobs:
18
+ create-release:
19
+ name: Create Release
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ with:
25
+ fetch-depth: 0 # Full history for changelog
26
+
27
+ - name: Read version from pyproject.toml
28
+ id: version
29
+ run: |
30
+ VERSION=$(grep -m1 'version = "' pyproject.toml | cut -d '"' -f 2)
31
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
32
+ echo "tag=v$VERSION" >> $GITHUB_OUTPUT
33
+ echo "📦 Version: $VERSION"
34
+
35
+ - name: Check if tag exists
36
+ id: check_tag
37
+ run: |
38
+ git fetch --tags
39
+ if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
40
+ echo "exists=true" >> $GITHUB_OUTPUT
41
+ echo "ℹ️ Tag ${{ steps.version.outputs.tag }} already exists"
42
+ else
43
+ echo "exists=false" >> $GITHUB_OUTPUT
44
+ echo "✨ Tag ${{ steps.version.outputs.tag }} will be created"
45
+ fi
46
+
47
+ - name: Generate changelog
48
+ id: changelog
49
+ if: steps.check_tag.outputs.exists == 'false'
50
+ run: |
51
+ echo "Generating changelog..."
52
+
53
+ # Get previous tag
54
+ PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
55
+
56
+ if [ -z "$PREV_TAG" ]; then
57
+ echo "📝 First release - generating full changelog"
58
+ COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges)
59
+ else
60
+ echo "📝 Generating changelog since $PREV_TAG"
61
+ COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges)
62
+ fi
63
+
64
+ # Save changelog to file
65
+ cat > changelog.md << EOF
66
+ ## What's Changed
67
+
68
+ ${COMMITS}
69
+
70
+ **Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${{ steps.version.outputs.tag }}
71
+ EOF
72
+
73
+ cat changelog.md
74
+
75
+ - name: Create Git tag
76
+ if: steps.check_tag.outputs.exists == 'false'
77
+ run: |
78
+ git config user.name "github-actions[bot]"
79
+ git config user.email "github-actions[bot]@users.noreply.github.com"
80
+ git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
81
+ git push origin "${{ steps.version.outputs.tag }}"
82
+
83
+ - name: Create GitHub Release
84
+ if: steps.check_tag.outputs.exists == 'false'
85
+ env:
86
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87
+ run: |
88
+ gh release create "${{ steps.version.outputs.tag }}" \
89
+ --title "Release ${{ steps.version.outputs.tag }}" \
90
+ --notes-file changelog.md
91
+
92
+ - name: Skip release creation
93
+ if: steps.check_tag.outputs.exists == 'true'
94
+ run: |
95
+ echo "ℹ️ Release ${{ steps.version.outputs.tag }} already exists - skipping"
96
+
97
+ publish-to-pypi:
98
+ name: Publish to PyPI
99
+ needs: [create-release]
100
+ uses: ./.github/workflows/python-publish.yml
101
+ permissions:
102
+ id-token: write
103
+ contents: read
104
+
105
+ notify-release:
106
+ name: Notify Release Created
107
+ runs-on: ubuntu-latest
108
+ needs: [create-release, publish-to-pypi]
109
+
110
+ steps:
111
+ - uses: actions/checkout@v4
112
+
113
+ - name: Read version from pyproject.toml
114
+ id: version
115
+ run: |
116
+ VERSION=$(grep -m1 'version = "' pyproject.toml | cut -d '"' -f 2)
117
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
118
+ echo "tag=v$VERSION" >> $GITHUB_OUTPUT
119
+
120
+ - name: Send notification
121
+ run: |
122
+ echo "📢 Release notification"
123
+ echo "Version: ${{ steps.version.outputs.tag }}"
124
+ echo "Repository: ${{ github.repository }}"
125
+ echo "Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}"
@@ -0,0 +1,71 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ pip-wheel-metadata/
20
+ share/python-wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Virtual environments
27
+ .venv/
28
+ venv/
29
+ ENV/
30
+ env/
31
+
32
+ # PyCharm
33
+ .idea/
34
+
35
+ # VSCode
36
+ .vscode/
37
+
38
+ # pytest
39
+ .pytest_cache/
40
+ htmlcov/
41
+ .coverage
42
+ .coverage.*
43
+ coverage.xml
44
+ *.cover
45
+ .hypothesis/
46
+
47
+ # mypy
48
+ .mypy_cache/
49
+ .dmypy.json
50
+ dmypy.json
51
+
52
+ # Logs
53
+ *.log
54
+ logs/
55
+
56
+ # Configuration files (keep examples only)
57
+ config.json
58
+ config-*.json
59
+ !config.example.json
60
+
61
+ # OS
62
+ .DS_Store
63
+ Thumbs.db
64
+
65
+ # Backup files
66
+ *~
67
+ *.bak
68
+ *.swp
69
+
70
+ # Test output
71
+ test-output/
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: kryten-api-gate
3
+ Version: 0.2.0
4
+ Summary: HTTP REST gateway for the Kryten ecosystem — exposes KrytenClient via FastAPI
5
+ Author: grobertson
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: fastapi>=0.115
9
+ Requires-Dist: kryten-py>=0.10.5
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: uvicorn[standard]>=0.30
12
+ Provides-Extra: dev
13
+ Requires-Dist: black>=24.0; extra == 'dev'
14
+ Requires-Dist: httpx>=0.27; extra == 'dev'
15
+ Requires-Dist: mypy>=1.10; extra == 'dev'
16
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
17
+ Requires-Dist: pytest>=7.0; extra == 'dev'
18
+ Requires-Dist: ruff>=0.4; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # kryten-api-gate
22
+
23
+ HTTP REST gateway for the Kryten ecosystem — exposes [`kryten-py`](https://github.com/grobertson/kryten-py) via FastAPI.
24
+
25
+ ## Overview
26
+
27
+ `kryten-api-gate` sits between HTTP clients and the Kryten NATS message bus, translating REST calls into `KrytenClient` commands dispatched to [Kryten-Robot](https://github.com/grobertson/Kryten-Robot).
28
+
29
+ ```
30
+ HTTP Client → kryten-api-gate → KrytenClient → NATS → Kryten-Robot → CyTube
31
+ ```
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install kryten-api-gate
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ 1. Copy `config.example.json` to `config.json` and fill in your values:
42
+ ```json
43
+ {
44
+ "nats_url": "nats://localhost:4222",
45
+ "channel": "your-channel",
46
+ "api_key": "your-secret-api-key",
47
+ "host": "0.0.0.0",
48
+ "port": 8080
49
+ }
50
+ ```
51
+
52
+ 2. Run the server:
53
+ ```bash
54
+ kryten-api-gate
55
+ # or
56
+ python -m kryten_api_gate
57
+ ```
58
+
59
+ 3. Interactive API docs at `http://localhost:8080/docs`
60
+
61
+ ## Authentication
62
+
63
+ All endpoints (except `/api/v1/system/health` and `/api/v1/system/version`) require a Bearer token:
64
+
65
+ ```
66
+ Authorization: Bearer <api_key>
67
+ ```
68
+
69
+ ## API Routes
70
+
71
+ | Prefix | Description |
72
+ |--------|-------------|
73
+ | `GET /api/v1/system/health` | Health check (unauthenticated) |
74
+ | `GET /api/v1/system/version` | Version info (unauthenticated) |
75
+ | `POST /api/v1/chat/send` | Send a chat message |
76
+ | `GET/POST /api/v1/playlist/` | Playlist management |
77
+ | `POST /api/v1/playback/` | Playback control |
78
+ | `POST /api/v1/moderation/` | Moderation actions |
79
+ | `GET/POST /api/v1/admin/` | Admin channel settings |
80
+ | `GET/POST /api/v1/emotes/` | Emote management |
81
+ | `GET/POST /api/v1/filters/` | Chat filter management |
82
+ | `GET/POST /api/v1/polls/` | Poll management |
83
+ | `GET /api/v1/library/` | Media library search |
84
+ | `GET/PUT/DELETE /api/v1/kv/` | Key-value store |
85
+ | `GET /api/v1/state/` | Channel state queries |
86
+
87
+ ## Configuration
88
+
89
+ | Key | Description | Default |
90
+ |-----|-------------|---------|
91
+ | `nats_url` | NATS server URL | `nats://localhost:4222` |
92
+ | `channel` | CyTube channel name | required |
93
+ | `api_key` | API authentication key | required |
94
+ | `host` | Listen address | `0.0.0.0` |
95
+ | `port` | Listen port | `8080` |
96
+ | `log_level` | Logging level | `info` |
97
+
98
+ ## Systemd Service
99
+
100
+ A systemd unit file is provided at `systemd/kryten-api-gate.service`.
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ git clone https://github.com/grobertson/kryten-api-gate
106
+ cd kryten-api-gate
107
+ pip install -e ".[dev]"
108
+ uvicorn kryten_api_gate.app:create_app --factory --reload
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT