Ctrl K
ComunidadGuías…

pyproject.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[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",
]