pyproject.toml
Rama main · réplica de lectura del repositorio original.
[build-system]
requires = ["hatchling>=1.18"]
build-backend = "hatchling.build"
[project]
name = "morning-signal"
dynamic = ["version"]
description = "Auto-generated daily briefing podcast: Claude + web search → script → Polly TTS → S3 + RSS"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
{ name = "Brian McMahon" },
]
keywords = ["podcast", "claude", "anthropic", "polly", "rss", "tts", "ai"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Multimedia :: Sound/Audio :: Speech",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
]
dependencies = [
"anthropic>=0.40.0",
"boto3>=1.35.0",
"pyyaml>=6.0",
"typer>=0.12.0",
# Telegram-routed failure + healthy-completion pings.
"flow-doctor==0.19.9",
# The commodity utilities morning-signal uses (anthropic_payload, cost,
# trading_calendar) live in the public MIT `krepis` foundation library.
# morning-signal is MIT and depends down onto krepis (also MIT); the
# earlier vendored copies under _vendor/nousergon/ are removed. The
# `openai` extra is required for the openai-transport (OpenAI, OpenRouter)
# path of krepis.llm.LLMClient — needed by scripts/oss_bakeoff.py
# (config#1659 Phase B shadow canary) even though production generation
# stays on the anthropic transport until the bakeoff gate is met.
# >=0.11.0 for ModelSpec.reasoning (krepis#16) — without it a
# reasoning-capable OpenRouter candidate can burn its whole output
# budget on chain-of-thought and return empty content even at a
# generous max_tokens (reproduced live 2026-07-06 against Kimi K2.6).
# >=0.11.2 for the corrected server_tool_use_details fix (krepis#17
# found the right field name but read it via getattr on what's
# actually a plain dict — an unmodeled Pydantic extra field, not an
# attribute object; krepis#18 fixed the access pattern). Without it
# usage.web_search_requests always reads 0 on OpenRouter regardless
# of real search activity, permanently breaking the min_web_searches
# floor on that transport — confirmed live re-running the config#1659
# bakeoff on 0.11.1 before this fix landed.
# >=0.12.2 for complete_grounded raising LLMError on an unresolved/
# leaked OpenRouter tool call (krepis#22) — without it, a model that
# emits its own native tool-call token dialect straight into
# message.content (reproduced live 2026-07-14 against Kimi K2.6:
# `<|tool_calls_section_begin|>...`) is trusted as a valid final
# answer instead of triggering the existing fallback-to-secondary-
# provider path, and a garbage/empty-content episode ships live.
# >=0.16.2 for two fixes to complete_grounded/_structured_openai
# (krepis#38, krepis-PR39), both confirmed live 2026-07-20:
# (1) an unresolved-tool-call/control-token leak now retries the SAME
# call up to `attempts=2` times before raising — three live incidents
# (7/14, 7/16, 7/20) each showed a bare retry resolves it, so this
# repo's cascade no longer burns its tier-2 fallback on a one-shot
# sampling glitch. (2) a non-JSON OpenRouter transport response
# (invisible to the SDK's own retry logic) previously bubbled up as a
# raw json.JSONDecodeError — a ValueError, NOT a RuntimeError/
# LLMError — which silently skipped past this repo's `except
# RuntimeError` tier-2/tier-3 fallback gates in claude.py instead of
# engaging them; this is what actually lost the 2026-07-20 AM episode
# despite the morning-signal#119 tier-3 forced-search last-resort
# existing specifically to catch it. Both failure classes now raise a
# properly-typed LLMError so the existing cascade engages correctly.
# Upper bound is load-bearing, not caution. krepis added a REQUIRED
# keyword-only `callsite_id` to LLMClient after 0.24.0, and because this
# was an open `>=` range every fresh resolve broke on import — silently,
# since CI only ran on PRs and main had not been exercised since
# 2026-07-27 while `Publish to PyPI` kept shipping (morning-signal-I132).
# Raise this deliberately after reading krepis's changelog, never by
# deleting the ceiling.
# Raised 0.24 -> 0.36 on 2026-08-08 for `krepis.router.resolve_group_spec`
# and the per-consumer `KREPIS_ROUTER_CREDENTIAL_SECRET` override
# (alpha-engine-config-I6414) — together they are what lets this repo
# address the AUTHENTICATED ROUTER EDGE by (url, credential) instead of
# constructing an in-process LiteLLM Router that egresses to providers
# directly (alpha-engine-config-I6567). The `callsite_id` break the
# original ceiling was pinned against is already satisfied: every
# LLMClient construction here passes it. Verified by running the full
# suite against 0.36.0 before raising the floor — 363 passed.
#
# The FLOOR is what matters here, not just the ceiling. The box's
# ExecStartPre runs `pip install -e .`, which leaves an
# already-constraint-satisfying version alone; with the old `>=0.24.0` the
# box sat on 0.24.2 indefinitely and never saw a newer krepis.
#
# Raised 0.36 -> 0.48 on 2026-08-12. 0.36.0 addresses the router group by
# its BARE ALIAS, and LiteLLM stamps the requested model back onto the
# response, so `resp.model == "high"` on every healthy call and krepis'
# own `_resolve_group_served_model` refuses it — "the router response did
# not report a served model distinct from the group alias ... it is not a
# real model and carries no price card". Refusing to bill under an alias
# is correct; the fix is to stop addressing one. Two krepis releases carry
# the two halves:
# 0.40.0 the litellm_proxy route resolves to the QUALIFIED primary
# deployment name, `{group}-{mid}` (alpha-engine-config-I6727)
# 0.41.1 a deployment-addressed call is not alias masquerade, so the
# echoed qualified name resolves through the registry to a
# billable upstream id (alpha-engine-config-I6543)
# Below 0.41.1 the primary cannot succeed on this route at all, which is
# how `scripts/oss_bakeoff.py` — the only caller here with no fallback —
# has exited 1 every Wednesday.
# Verified by running the full suite against 0.48.0 before raising.
"krepis[anthropic,openai]>=0.48.0,<0.60.0",
"pydantic>=2.0",
# Pydantic v2 cannot eval PEP 604 `str | None` field annotations under
# Python 3.9 even with `from __future__ import annotations` (mirrors krepis).
"eval_type_backport>=0.2.0; python_version < '3.10'",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"moto[ses,s3,sts,ssm]>=5.0.0",
# Consumer-side contract test for the news_digest_daily pinned schema
# (data-collector plan P-07, alpha-engine-config-I10870, D36) — test-only,
# not a runtime dependency of the package. 4.26.0 requires Python>=3.10;
# this repo's CI matrix floors at 3.9 (test.yml), so pinned to the latest
# version compatible across the whole matrix.
"jsonschema>=4.23.0",
]
# Google Chirp3 HD TTS engine (config tts.engine: google). Kept optional so
# Polly-only installs stay light; tts_google fails loud with an install hint
# if this extra is absent.
google = [
"google-cloud-texttospeech>=2.16.0",
]
[project.scripts]
morning-signal = "morning_signal.cli:main"
[project.urls]
Homepage = "https://github.com/nousergon/morning-signal"
Repository = "https://github.com/nousergon/morning-signal"
Issues = "https://github.com/nousergon/morning-signal/issues"
[tool.hatch.version]
path = "src/morning_signal/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["src/morning_signal"]
[tool.hatch.build.targets.sdist]
include = [
"src/morning_signal",
"README.md",
"LICENSE",
"pyproject.toml",
]