Python SDK for consuming the Kit's Deals river — real-time, structured, agent-friendly deal events
Project description
kitsdeals-river
Python SDK for consuming the Kit's Deals river — a real-time, structured stream of approved deals built for agent consumers.
The interaction model is agent-mediated. You're not expected to edit YAML by hand. You tell your Claude/Code agent something like "set up github.com/kitsdeals/kitsdeals-river for me — I'm looking for TVs and a laptop refresh", and the agent runs the onboarding skill (PR 37) to translate the conversation into a
~/.kitsdeals/profile.yamlplus a running watcher daemon. The CLI commands and library API exist so the agent has reliable surfaces to call; humans rarely use them directly.
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ RiverWatcher │
│ SSE consumer (Kit's Deals river v2) │
│ ↓ │
│ Reconnect with exponential backoff │
│ Cursor persistence (~/.kitsdeals/cursor.json) │
│ Two-step catch-up: REST for gaps, SSE for forward │
│ ↓ │
│ Handler (your code or the spawn_claude_evaluator from PR 37) │
│ ↓ │
│ Profile DecisionsLog Notifier │
│ ~/.kitsdeals/ ~/.kitsdeals/ telegram / stdout / … │
│ profile.yaml decisions.jsonl │
└──────────────────────────────────────────────────────────────────┘
Status
| Component | Status |
|---|---|
RiverWatcher — SSE + reconnect + cursor + filter + SIGHUP reload |
✓ |
Profile — Pydantic schema, YAML load/save, dedup helpers |
✓ |
DecisionsLog — JSONL append-only with size-based rotation |
✓ |
Notifier — protocol + 6 built-ins (stdout, telegram, slack, discord, http_post, subprocess) |
✓ |
spawn_evaluator — agent-agnostic wake-up with structured prompt |
✓ |
skills/onboarding.md — agent-facing setup interview |
✓ |
skills/update-profile.md — agent-facing ongoing refinement |
✓ |
CLI (setup, run, reload, status, profile show/update/validate) |
✓ |
Server-side GET /v1/quickstart agent funnel |
✓ |
examples/kit_dual_emit.py — reference dual-emit pattern (observability + agent action) |
✓ |
Install
pip install kitsdeals-river
(Pre-publish: pip install -e . from this directory.)
Agent-driven setup (the expected install flow)
Paste this repo URL to your Claude Code agent and say:
Set up Kit's Deals for me — I'm looking for X and Y.
The agent loads skills/onboarding.md, runs a short interview (4–6
questions max), translates your answers to a profile, and starts the
watcher. You don't see the YAML.
For ongoing changes — "stop showing me TVs", "I bought one of those",
"only show me clear wins" — your agent loads skills/update-profile.md
and translates the natural-language request into a structured CLI edit.
Live reload via SIGHUP, no restart required.
CLI
The CLI is the agent-facing API to the profile and the watcher daemon. Humans rarely run these directly; the skills tell the agent how to use them.
kitsdeals-river setup --json '<full profile JSON>' # write profile to disk
kitsdeals-river run # foreground watcher (start here, or via systemd)
kitsdeals-river run --initial-backfill # also surface currently-live matching deals on first start
kitsdeals-river status # daemon health snapshot
kitsdeals-river reload # SIGHUP a running watcher
kitsdeals-river profile show [--json] # inspect current profile
kitsdeals-river profile update --remove-watch "TVs" # programmatic edits
kitsdeals-river profile validate # well-formedness check
setup writes the profile only — it does not start the watcher.
run (or a systemd unit invoking run) is what actually opens the SSE
connection. Treat them as separate concerns: one configures, the other
keeps the daemon alive.
See skills/onboarding.md and skills/update-profile.md for the agent-
facing recipes.
Multiple watches
Profiles can declare multiple watches (profile.watches[]). When more
than one is present, kitsdeals-river run opens one SSE connection
per watch concurrently in a single process — each with its own
filter, its own cursor file (<cursor>-<sanitized-label>.json), and
its own notify_threshold. A deal that matches multiple watches is
delivered on each matching connection; the default handler dedupes by
deal id and routes to the first matching watch's threshold.
Notify thresholds
Each watch declares a notify_threshold ∈ {anything, good, clear_win}
that gates which matched deals actually fire a notification. Mapping
against the server's deal-quality score (deal_score, 0–100):
| threshold | floor | when to use |
|---|---|---|
anything |
none — every match notifies | broad watches you actively want noisy |
good |
deal_score ≥ 60 |
typical default; filters out obvious lemons |
clear_win |
deal_score ≥ 80 |
only-the-best alerts; lots will be filtered |
Deals lacking deal_score only notify when notify_threshold is
anything — refuse-to-guess posture.
Telegram delivery setup
The default notifier reads TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID
from the environment. Two prerequisites:
- Create a bot via Telegram's
@BotFather: send/newbot, pick a display name and username, save the token BotFather replies with. - The user must DM the bot first. Telegram bots cannot send the
first message — the user has to open a chat with their new bot and
send
/start(or any message). Skip this step and the watcher runs fine butsendMessagereturnsForbidden: bot can't initiate conversation with a user.
Then retrieve the chat_id (GET https://api.telegram.org/bot<TOKEN>/getUpdates
returns result[*].message.chat.id) and put both values in the
environment file your service reads. For full step-by-step including
verification and forum-topic routing, see skills/onboarding.md — the
agent-facing version walks through every failure mode.
The default notification format includes brand, product name, current
- original price, discount %, merchant, condition, deal score, optional quality verdict, the product URL (Telegram unfurls into a card), and an expiry countdown when known.
Minimal example (without an agent in the loop)
from kitsdeals_river import RiverWatcher, RiverEvent, StdoutNotifier
from kitsdeals_river.profile import WatchFilter
import asyncio
async def main():
notifier = StdoutNotifier()
watcher = RiverWatcher(
filter=WatchFilter(
category="tv",
brand_in=["LG", "Sony"],
min_discount_pct=25,
),
)
@watcher.on_event
async def handle(event: RiverEvent):
if event.is_deal:
deal = event.data
await notifier.send(
f"{deal['brand']} — ${deal['current_price_cents']/100:.2f}",
deal,
)
await watcher.run_async()
asyncio.run(main())
See examples/minimal.py for a runnable version.
Testing
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE. Use freely, build on top, fork it for your own personal-event runtime.
Roadmap
This package is one channel of a broader pattern: decoupled summarization — a centralized service does expensive synthesis work once, emits a structured event stream, and agents do cheap per-event filtering against a user profile. Same architecture applies to news, jobs, regulatory filings, GitHub releases, real-estate listings. Kit's Deals is the first concrete implementation; the SDK's profile + watcher + decisions-log shapes are designed to translate.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kitsdeals_river-0.4.1.tar.gz.
File metadata
- Download URL: kitsdeals_river-0.4.1.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e812fd9043ff0d5457ce90620df0f9bd7644f0ee827fb553e7168477f06f78b8
|
|
| MD5 |
2cefd630f8e7fa1e540bc160a7744a0c
|
|
| BLAKE2b-256 |
6ed9eb18859f0cf66e27dd37f60a920cd7904a36bdfba13c0356db7edc0f8170
|
File details
Details for the file kitsdeals_river-0.4.1-py3-none-any.whl.
File metadata
- Download URL: kitsdeals_river-0.4.1-py3-none-any.whl
- Upload date:
- Size: 44.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e1e0a3adc779c3187879fa1316b0d2fd45fce9c6af4a6ae5e9d7bd9fb1626b77
|
|
| MD5 |
197320beca5db228303bfcb6f75940d1
|
|
| BLAKE2b-256 |
0c83c4a86a5cb0cbf2958eb5b0d90a7311332d23e043bb43c1e75a2ec058d42a
|