earnings-watch 0.1.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 (97) hide show
  1. earnings_watch-0.1.0/.github/workflows/ci.yml +15 -0
  2. earnings_watch-0.1.0/.github/workflows/release.yml +235 -0
  3. earnings_watch-0.1.0/.gitignore +11 -0
  4. earnings_watch-0.1.0/CHANGELOG.md +17 -0
  5. earnings_watch-0.1.0/CONTRIBUTING.md +9 -0
  6. earnings_watch-0.1.0/Dockerfile +27 -0
  7. earnings_watch-0.1.0/LICENSE +21 -0
  8. earnings_watch-0.1.0/Makefile +20 -0
  9. earnings_watch-0.1.0/NOTICE +3 -0
  10. earnings_watch-0.1.0/PKG-INFO +105 -0
  11. earnings_watch-0.1.0/README.md +87 -0
  12. earnings_watch-0.1.0/SECURITY.md +7 -0
  13. earnings_watch-0.1.0/compose.yaml +27 -0
  14. earnings_watch-0.1.0/docs/ARCHITECTURE.md +19 -0
  15. earnings_watch-0.1.0/docs/DESIGN.md +13 -0
  16. earnings_watch-0.1.0/docs/IMPLEMENTATION.md +54 -0
  17. earnings_watch-0.1.0/docs/OPERATIONS.md +98 -0
  18. earnings_watch-0.1.0/docs/PERMISSIONS.md +13 -0
  19. earnings_watch-0.1.0/docs/PROVIDERS.md +39 -0
  20. earnings_watch-0.1.0/earnings-watch.example.toml +9 -0
  21. earnings_watch-0.1.0/examples/sources.yaml +11 -0
  22. earnings_watch-0.1.0/pyproject.toml +43 -0
  23. earnings_watch-0.1.0/scripts/check-boundaries.py +42 -0
  24. earnings_watch-0.1.0/scripts/check-conformance.py +92 -0
  25. earnings_watch-0.1.0/scripts/snapshot-surface.py +30 -0
  26. earnings_watch-0.1.0/server.json +30 -0
  27. earnings_watch-0.1.0/src/earnings_watch/__init__.py +7 -0
  28. earnings_watch-0.1.0/src/earnings_watch/browser.py +65 -0
  29. earnings_watch-0.1.0/src/earnings_watch/calendar.py +117 -0
  30. earnings_watch-0.1.0/src/earnings_watch/cli.py +423 -0
  31. earnings_watch-0.1.0/src/earnings_watch/composition.py +11 -0
  32. earnings_watch-0.1.0/src/earnings_watch/documents.py +184 -0
  33. earnings_watch-0.1.0/src/earnings_watch/engine/__init__.py +0 -0
  34. earnings_watch-0.1.0/src/earnings_watch/engine/coverage.py +115 -0
  35. earnings_watch-0.1.0/src/earnings_watch/engine/reconcile.py +408 -0
  36. earnings_watch-0.1.0/src/earnings_watch/engine/timing.py +387 -0
  37. earnings_watch-0.1.0/src/earnings_watch/engine/timing_search.py +128 -0
  38. earnings_watch-0.1.0/src/earnings_watch/engine/verify.py +676 -0
  39. earnings_watch-0.1.0/src/earnings_watch/errors.py +10 -0
  40. earnings_watch-0.1.0/src/earnings_watch/exports.py +162 -0
  41. earnings_watch-0.1.0/src/earnings_watch/heal.py +52 -0
  42. earnings_watch-0.1.0/src/earnings_watch/http_server.py +92 -0
  43. earnings_watch-0.1.0/src/earnings_watch/ics.py +49 -0
  44. earnings_watch-0.1.0/src/earnings_watch/legacy.py +319 -0
  45. earnings_watch-0.1.0/src/earnings_watch/llm/__init__.py +0 -0
  46. earnings_watch-0.1.0/src/earnings_watch/llm/doctor.py +75 -0
  47. earnings_watch-0.1.0/src/earnings_watch/llm/research_server.py +58 -0
  48. earnings_watch-0.1.0/src/earnings_watch/llm/runner.py +353 -0
  49. earnings_watch-0.1.0/src/earnings_watch/maintenance.py +332 -0
  50. earnings_watch-0.1.0/src/earnings_watch/mcp_server.py +155 -0
  51. earnings_watch-0.1.0/src/earnings_watch/model/__init__.py +0 -0
  52. earnings_watch-0.1.0/src/earnings_watch/model/events.py +333 -0
  53. earnings_watch-0.1.0/src/earnings_watch/model/limits.py +12 -0
  54. earnings_watch-0.1.0/src/earnings_watch/model/sources.py +531 -0
  55. earnings_watch-0.1.0/src/earnings_watch/model/urls.py +28 -0
  56. earnings_watch-0.1.0/src/earnings_watch/notify.py +113 -0
  57. earnings_watch-0.1.0/src/earnings_watch/outbox.py +70 -0
  58. earnings_watch-0.1.0/src/earnings_watch/polling.py +97 -0
  59. earnings_watch-0.1.0/src/earnings_watch/providers/__init__.py +21 -0
  60. earnings_watch-0.1.0/src/earnings_watch/providers/base.py +21 -0
  61. earnings_watch-0.1.0/src/earnings_watch/providers/definitions/generic_ir.toml +4 -0
  62. earnings_watch-0.1.0/src/earnings_watch/providers/definitions/hkexnews.toml +4 -0
  63. earnings_watch-0.1.0/src/earnings_watch/providers/definitions/sec_edgar.toml +5 -0
  64. earnings_watch-0.1.0/src/earnings_watch/providers/generic_ir.py +56 -0
  65. earnings_watch-0.1.0/src/earnings_watch/providers/sec_edgar.py +25 -0
  66. earnings_watch-0.1.0/src/earnings_watch/registration.py +71 -0
  67. earnings_watch-0.1.0/src/earnings_watch/release.py +238 -0
  68. earnings_watch-0.1.0/src/earnings_watch/research.py +50 -0
  69. earnings_watch-0.1.0/src/earnings_watch/service.py +160 -0
  70. earnings_watch-0.1.0/src/earnings_watch/settings.py +58 -0
  71. earnings_watch-0.1.0/src/earnings_watch/store/__init__.py +0 -0
  72. earnings_watch-0.1.0/src/earnings_watch/store/database.py +690 -0
  73. earnings_watch-0.1.0/src/earnings_watch/store/schema.sql +22 -0
  74. earnings_watch-0.1.0/src/earnings_watch/templates/__init__.py +0 -0
  75. earnings_watch-0.1.0/src/earnings_watch/transport.py +224 -0
  76. earnings_watch-0.1.0/src/earnings_watch/worker.py +33 -0
  77. earnings_watch-0.1.0/tests/conformance-baseline.json +11 -0
  78. earnings_watch-0.1.0/tests/conftest.py +57 -0
  79. earnings_watch-0.1.0/tests/schema-v1.sql +21 -0
  80. earnings_watch-0.1.0/tests/surface.json +526 -0
  81. earnings_watch-0.1.0/tests/test_audit_regressions.py +252 -0
  82. earnings_watch-0.1.0/tests/test_browser.py +79 -0
  83. earnings_watch-0.1.0/tests/test_coverage_engine.py +143 -0
  84. earnings_watch-0.1.0/tests/test_engine_safety_cases.py +204 -0
  85. earnings_watch-0.1.0/tests/test_events_state_engine.py +151 -0
  86. earnings_watch-0.1.0/tests/test_fetch_verify_engine.py +275 -0
  87. earnings_watch-0.1.0/tests/test_legacy_and_cli.py +415 -0
  88. earnings_watch-0.1.0/tests/test_maintenance_documents.py +222 -0
  89. earnings_watch-0.1.0/tests/test_mcp.py +125 -0
  90. earnings_watch-0.1.0/tests/test_polling_worker.py +139 -0
  91. earnings_watch-0.1.0/tests/test_runners_and_surface.py +137 -0
  92. earnings_watch-0.1.0/tests/test_sources_universe_engine.py +209 -0
  93. earnings_watch-0.1.0/tests/test_store_service.py +200 -0
  94. earnings_watch-0.1.0/tests/test_timing_evidence_engine.py +132 -0
  95. earnings_watch-0.1.0/tests/test_timing_resolution_engine.py +5 -0
  96. earnings_watch-0.1.0/tests/test_transport.py +95 -0
  97. earnings_watch-0.1.0/uv.lock +1923 -0
@@ -0,0 +1,15 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ permissions:
7
+ contents: read
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ - run: make bootstrap
15
+ - run: make check
@@ -0,0 +1,235 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ tags: ['v*.*.*']
5
+ workflow_dispatch: {}
6
+ permissions:
7
+ contents: read
8
+ concurrency:
9
+ group: release-${{ github.ref }}
10
+ cancel-in-progress: false
11
+ jobs:
12
+ released:
13
+ if: github.ref_type == 'tag'
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ exists: ${{ steps.check.outputs.exists }}
17
+ steps:
18
+ - id: check
19
+ env:
20
+ GH_TOKEN: ${{ github.token }}
21
+ run: |
22
+ if gh release view "$GITHUB_REF_NAME" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
23
+ echo exists=true >> "$GITHUB_OUTPUT"
24
+ else
25
+ echo exists=false >> "$GITHUB_OUTPUT"
26
+ fi
27
+ test:
28
+ needs: released
29
+ if: needs.released.outputs.exists != 'true'
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: astral-sh/setup-uv@v6
34
+ - run: make bootstrap
35
+ - run: make check
36
+ - uses: actions/upload-artifact@v4
37
+ with:
38
+ name: distributions
39
+ path: dist/*
40
+ images:
41
+ needs: test
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ contents: read
45
+ packages: write
46
+ strategy:
47
+ matrix:
48
+ target: [serve, worker]
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+ - uses: docker/setup-qemu-action@v3
52
+ - uses: docker/setup-buildx-action@v3
53
+ id: builder
54
+ with:
55
+ use: false
56
+ - uses: docker/login-action@v3
57
+ with:
58
+ registry: ghcr.io
59
+ username: ${{ github.actor }}
60
+ password: ${{ secrets.GITHUB_TOKEN }}
61
+ - id: metadata
62
+ shell: bash
63
+ run: |
64
+ name=earnings-watch
65
+ if [ '${{ matrix.target }}' = worker ]; then name=earnings-watch-worker; fi
66
+ echo name="$name" >> "$GITHUB_OUTPUT"
67
+ echo version="${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
68
+ - id: build
69
+ uses: docker/build-push-action@v6
70
+ with:
71
+ builder: ${{ steps.builder.outputs.name }}
72
+ context: .
73
+ target: ${{ matrix.target }}
74
+ platforms: linux/amd64,linux/arm64
75
+ push: true
76
+ tags: ghcr.io/${{ github.repository_owner }}/${{ steps.metadata.outputs.name }}:candidate-${{ github.sha }}
77
+ labels: |
78
+ org.opencontainers.image.source=https://github.com/${{ github.repository }}
79
+ org.opencontainers.image.revision=${{ github.sha }}
80
+ org.opencontainers.image.version=${{ steps.metadata.outputs.version }}
81
+ - env:
82
+ DIGEST: ${{ steps.build.outputs.digest }}
83
+ run: |
84
+ mkdir -p digests
85
+ printf '%s\n' "$DIGEST" > 'digests/${{ steps.metadata.outputs.name }}'
86
+ - uses: actions/upload-artifact@v4
87
+ with:
88
+ name: digest-${{ matrix.target }}
89
+ path: digests/*
90
+ smoke:
91
+ needs: images
92
+ runs-on: ubuntu-latest
93
+ strategy:
94
+ matrix:
95
+ image: [earnings-watch, earnings-watch-worker]
96
+ arch: [amd64, arm64]
97
+ steps:
98
+ - uses: docker/setup-qemu-action@v3
99
+ - uses: actions/download-artifact@v4
100
+ with:
101
+ pattern: digest-*
102
+ path: digests
103
+ merge-multiple: true
104
+ - env:
105
+ IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
106
+ ARCH: ${{ matrix.arch }}
107
+ run: |
108
+ digest="$(cat 'digests/${{ matrix.image }}')"
109
+ docker run --rm --platform "linux/$ARCH" --read-only --cap-drop ALL --security-opt no-new-privileges --tmpfs /data:uid=1000,gid=1000,mode=0700 --tmpfs /tmp --entrypoint sh "$IMAGE@$digest" -c 'earnings-watch init && earnings-watch doctor && earnings-watch upcoming --format json'
110
+ if [ '${{ matrix.image }}' = earnings-watch-worker ]; then
111
+ docker run --rm --platform "linux/$ARCH" --entrypoint claude "$IMAGE@$digest" --version
112
+ docker run --rm --platform "linux/$ARCH" --entrypoint codex "$IMAGE@$digest" --version
113
+ fi
114
+ promote:
115
+ needs: smoke
116
+ runs-on: ubuntu-latest
117
+ permissions:
118
+ contents: read
119
+ packages: write
120
+ steps:
121
+ - uses: docker/setup-buildx-action@v3
122
+ - uses: docker/login-action@v3
123
+ with:
124
+ registry: ghcr.io
125
+ username: ${{ github.actor }}
126
+ password: ${{ secrets.GITHUB_TOKEN }}
127
+ - uses: actions/download-artifact@v4
128
+ with:
129
+ pattern: digest-*
130
+ path: digests
131
+ merge-multiple: true
132
+ - env:
133
+ OWNER: ${{ github.repository_owner }}
134
+ run: |
135
+ set -eu
136
+ version="${GITHUB_REF_NAME#v}"
137
+ for name in earnings-watch earnings-watch-worker; do
138
+ image="ghcr.io/$OWNER/$name"
139
+ if docker buildx imagetools inspect "$image:$version" >/dev/null 2>&1; then
140
+ echo "Release version already exists; use a new version."
141
+ exit 1
142
+ fi
143
+ done
144
+ for name in earnings-watch earnings-watch-worker; do
145
+ image="ghcr.io/$OWNER/$name"
146
+ digest="$(cat "digests/$name")"
147
+ docker buildx imagetools create --prefer-index=false --tag "$image:$version" --tag "$image:latest" "$image@$digest"
148
+ done
149
+ github-release:
150
+ needs: [test, promote]
151
+ runs-on: ubuntu-latest
152
+ permissions:
153
+ contents: write
154
+ steps:
155
+ - uses: actions/download-artifact@v4
156
+ with:
157
+ name: distributions
158
+ path: dist
159
+ - env:
160
+ GH_TOKEN: ${{ github.token }}
161
+ run: |
162
+ cd dist
163
+ sha256sum * > SHA256SUMS
164
+ gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REF_NAME" --generate-notes ./*
165
+ pypi:
166
+ needs: [released, github-release]
167
+ if: >-
168
+ always() && github.ref_type == 'tag' && vars.PYPI_PUBLISH == 'true' &&
169
+ (needs.github-release.result == 'success' || needs.released.outputs.exists == 'true')
170
+ runs-on: ubuntu-latest
171
+ environment: pypi
172
+ permissions:
173
+ contents: read
174
+ id-token: write
175
+ steps:
176
+ - uses: actions/checkout@v4
177
+ - uses: astral-sh/setup-uv@v6
178
+ - env:
179
+ GH_TOKEN: ${{ github.token }}
180
+ run: |
181
+ set -eu
182
+ gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --dir dist --pattern '*.whl' --pattern '*.tar.gz' --pattern SHA256SUMS
183
+ (cd dist && sha256sum --check SHA256SUMS)
184
+ uv tool run --from twine twine check --strict dist/*.whl dist/*.tar.gz
185
+ uv venv /tmp/earnings-wheel-check
186
+ uv pip install --python /tmp/earnings-wheel-check/bin/python dist/*.whl
187
+ EARNINGS_WATCH_DATA_DIR=/tmp/earnings-release-state /tmp/earnings-wheel-check/bin/earnings-watch init
188
+ EARNINGS_WATCH_DATA_DIR=/tmp/earnings-release-state /tmp/earnings-wheel-check/bin/earnings-watch doctor
189
+ rm dist/SHA256SUMS
190
+ - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
191
+ with:
192
+ attestations: true
193
+ skip-existing: true
194
+ registry:
195
+ needs: [released, github-release, pypi]
196
+ if: >-
197
+ always() && github.ref_type == 'tag' && vars.MCP_REGISTRY_PUBLISH == 'true' &&
198
+ (needs.github-release.result == 'success' || needs.released.outputs.exists == 'true')
199
+ runs-on: ubuntu-latest
200
+ permissions:
201
+ contents: read
202
+ id-token: write
203
+ env:
204
+ MCP_PUBLISHER_VERSION: v1.8.1
205
+ steps:
206
+ - uses: actions/checkout@v4
207
+ - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6
208
+ - name: Verify the publisher and the published package
209
+ env:
210
+ GH_TOKEN: ${{ github.token }}
211
+ run: |
212
+ set -eu
213
+ version="${GITHUB_REF_NAME#v}"
214
+ curl --fail --silent --show-error "https://pypi.org/pypi/earnings-watch/$version/json" -o /tmp/package.json
215
+ gh release download "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --pattern SHA256SUMS --dir /tmp/earnings-registry-assets
216
+ python3 - <<'PY'
217
+ import json
218
+ from pathlib import Path
219
+ published = json.loads(Path('/tmp/package.json').read_text())
220
+ expected = dict(line.split()[::-1] for line in Path('/tmp/earnings-registry-assets/SHA256SUMS').read_text().splitlines())
221
+ actual = {item['filename']: item['digests']['sha256'] for item in published['urls']}
222
+ if not expected or expected != actual:
223
+ raise SystemExit('PyPI files differ from the accepted GitHub release')
224
+ PY
225
+ archive=mcp-publisher_linux_amd64.tar.gz
226
+ base_url="https://github.com/modelcontextprotocol/registry/releases/download/$MCP_PUBLISHER_VERSION"
227
+ curl --fail --location --silent --show-error -o "$archive" "$base_url/$archive"
228
+ curl --fail --location --silent --show-error -o "$archive.sigstore.json" "$base_url/$archive.sigstore.json"
229
+ cosign verify-blob --bundle "$archive.sigstore.json" --certificate-identity "https://github.com/modelcontextprotocol/registry/.github/workflows/release.yml@refs/tags/$MCP_PUBLISHER_VERSION" --certificate-oidc-issuer https://token.actions.githubusercontent.com "$archive"
230
+ tar xzf "$archive" mcp-publisher
231
+ jq --arg version "$version" '.version = $version | .packages[].version = $version' server.json > server-release.json
232
+ mv server-release.json server.json
233
+ - run: |
234
+ ./mcp-publisher login github-oidc
235
+ ./mcp-publisher publish
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .coverage
7
+ htmlcov/
8
+ dist/
9
+ *.egg-info/
10
+ *.db*
11
+ .artifacts/
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial standalone CLI, Python API and MCP server. Shared SQLite facts with personal follows, delegated calendar plans and durable delivery state; deterministic date/timing verification; official release polling, document links and opt-in downloads; standalone and gateway HTTP authentication; separate serving and maintenance images.
6
+
7
+ Candidate audit fixes preserve revisions and download receipts across catalogue
8
+ retirement, cover the full release-polling window, keep accepted documents after
9
+ partial provider failures, align date boundaries to UTC, treat disabled models
10
+ as intentional, and harden interrupted HTTP requests and download integrity.
11
+ Existing schema-1 candidates upgrade explicitly through `init`; see operations.
12
+
13
+ - Preserve legacy polling, document verification and reviewed overrides during
14
+ migration; block external effects until explicit reconciliation, and export
15
+ current-state compatibility projections with retained rollback evidence.
16
+ - Bind concurrent image builds to their own builders so one completed job cannot
17
+ stop another job's qualification or release build.
@@ -0,0 +1,9 @@
1
+ # Contributing
2
+
3
+ Report bugs with synthetic inputs and redacted diagnostics. Do not attach account tokens, personal follows, calendar records or captured private pages.
4
+
5
+ ## How changes land
6
+
7
+ This repository is a release mirror. Maintained source is reviewed separately and exported as complete releases. Pull requests are reviewed and reimplemented in that source, then credited in release notes; they are not merged directly into the mirror because the next export would replace them.
8
+
9
+ Run `make bootstrap` and `make check`. Keep network, models and account effects behind fake boundaries in offline tests. Add a regression test when changing date, period, timing, persistence or authentication behavior. Public fixtures must be fictional.
@@ -0,0 +1,27 @@
1
+ FROM node:24-bookworm-slim AS clis
2
+ RUN npm install --prefix /opt/clis @anthropic-ai/claude-code@2.1.312 @openai/codex@0.164.0
3
+ FROM ghcr.io/astral-sh/uv:0.13.5 AS uv
4
+ FROM python:3.12-slim-bookworm AS base
5
+ ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/home/earnings EARNINGS_WATCH_DATA_DIR=/data PATH=/app/.venv/bin:$PATH
6
+ RUN useradd --uid 1000 --create-home earnings && mkdir /data && chown earnings /data
7
+ WORKDIR /app
8
+ FROM base AS dependencies
9
+ COPY --from=uv /uv /usr/local/bin/uv
10
+ COPY pyproject.toml uv.lock README.md LICENSE NOTICE ./
11
+ COPY src ./src
12
+ RUN uv sync --locked --no-dev --no-editable
13
+ FROM base AS serve
14
+ COPY --from=dependencies /app/.venv /app/.venv
15
+ USER 1000:1000
16
+ VOLUME /data
17
+ HEALTHCHECK --interval=60s --timeout=10s CMD ["earnings-watch", "health", "--json"]
18
+ ENTRYPOINT ["earnings-watch"]
19
+ CMD ["serve", "--http", "--host", "0.0.0.0"]
20
+ FROM serve AS worker
21
+ USER root
22
+ RUN apt-get update && apt-get install -y --no-install-recommends libstdc++6 ca-certificates && rm -rf /var/lib/apt/lists/*
23
+ COPY --from=clis /usr/local/bin/node /usr/local/bin/node
24
+ COPY --from=clis /opt/clis /opt/clis
25
+ ENV PATH=/opt/clis/node_modules/.bin:$PATH
26
+ USER 1000:1000
27
+ CMD ["worker"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 benjamin-di
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ .PHONY: bootstrap check test live conformance
2
+ bootstrap:
3
+ uv sync --locked
4
+ check:
5
+ uv lock --check
6
+ uv run --locked ruff check src tests scripts
7
+ uv run --locked ruff format --check src tests scripts
8
+ uv run --locked mypy src
9
+ uv run --locked python scripts/check-boundaries.py
10
+ uv run --locked pytest -m 'not live' -q
11
+ uv build --no-sources
12
+ git diff --check
13
+
14
+ test:
15
+ uv run --locked pytest -q
16
+ live:
17
+ uv run --locked pytest -m live private/ -q
18
+
19
+ conformance:
20
+ uv run --locked python scripts/check-conformance.py
@@ -0,0 +1,3 @@
1
+ Earnings Watch
2
+
3
+ The deterministic timing and verification engine was extracted from its original service.
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.5
2
+ Name: earnings-watch
3
+ Version: 0.1.0
4
+ Summary: Verified issuer earnings calendars and results documents, through CLI and MCP
5
+ Project-URL: Homepage, https://github.com/benjamin-di/earnings-watch
6
+ Author: benjamin-di
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ License-File: NOTICE
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: fastmcp==4.1.3
12
+ Requires-Dist: html2text>=2024.2
13
+ Requires-Dist: httpx<1,>=0.28
14
+ Requires-Dist: pydantic<3,>=2.11
15
+ Requires-Dist: pyyaml<7,>=6
16
+ Requires-Dist: tzdata>=2024.1
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Earnings Watch
20
+
21
+ <!-- mcp-name: io.github.benjamin-di/earnings-watch -->
22
+
23
+ Verified issuer earnings calendars and results documents through a CLI, Python API and MCP server. Models propose dates; deterministic checks require quoted evidence and consistent issuer, period and timing. A call time never silently becomes a publication time.
24
+
25
+ Calendar updates are optional. Earnings Watch produces an operation plan; your MCP client uses its external Outlook app to apply it and acknowledges confirmed writes. No Microsoft credentials or calendar SDK are required by this package.
26
+
27
+ Requires Python 3.12+. Install a release wheel with `uv tool install path/to/earnings_watch-0.1.0-py3-none-any.whl`, or develop from a source checkout:
28
+
29
+ ```sh
30
+ make bootstrap
31
+ uv run earnings-watch init
32
+ uv run earnings-watch doctor
33
+ uv run earnings-watch sources import examples/sources.yaml
34
+ uv run earnings-watch watch EXAMPLE
35
+ uv run earnings-watch upcoming --format json
36
+ ```
37
+
38
+ The example uses reserved domains and fictional data; replace its source URLs and issuer metadata before fetching. There is no bundled issuer universe or market-data subscription.
39
+
40
+ ## Maintenance and models
41
+
42
+ Serving reads SQLite and updates personal follows/ledgers; it never fetches issuer pages or calls a model. Run maintenance separately:
43
+
44
+ ```sh
45
+ uv run earnings-watch sweep --dry-run
46
+ uv run earnings-watch sweep
47
+ uv run earnings-watch sweep --docs-only
48
+ uv run earnings-watch worker
49
+ ```
50
+
51
+ The default backend is `none`: it preserves existing dates and still polls official release providers. To extract new dates, install and sign into your own Claude Code or Codex CLI, then configure it in `earnings-watch.toml`:
52
+
53
+ ```toml
54
+ [llm]
55
+ backends = ["claude-code", "codex", "none"]
56
+ max_calls_per_run = 20
57
+ ```
58
+
59
+ Supported minimum versions are Claude Code 2.1.312 and Codex 0.164.0. Run `earnings-watch doctor --live` to check capabilities and an optional model canary. The package has no login flow. Model usage follows your CLI account's limits and terms. Missing credentials or capacity may try the next backend; a timed-out run is not repeated in the same tick.
60
+
61
+ ## MCP
62
+
63
+ A local client configuration, after initialization:
64
+
65
+ ```json
66
+ {"mcpServers":{"earnings":{"command":"earnings-watch","args":["serve","--stdio"]}}}
67
+ ```
68
+
69
+ | Tool | Purpose |
70
+ | --- | --- |
71
+ | `earnings_upcoming` | Bounded upcoming dates with timing uncertainty |
72
+ | `earnings_get_events` | Evidence, revisions and recent history |
73
+ | `earnings_documents` | Official result-document links and verification status |
74
+ | `earnings_changes` | Cursor-based public change feed |
75
+ | `earnings_watchlist` | Your follows and registration states |
76
+ | `earnings_watch` | Follow an issuer; queue registration or resolve ambiguity |
77
+ | `earnings_unwatch` | Remove a manual follow |
78
+ | `earnings_calendar_plan` | Plan optional external calendar operations |
79
+ | `earnings_calendar_ack` | Record confirmed operations from a signed plan |
80
+ | `earnings_status` | Catalogue and maintenance status |
81
+
82
+ Prompts: `weekly_earnings_brief`, `results_brief`, `sync_to_my_outlook`. Resource: `earnings://upcoming.ics`. Exact times require verified clock and timezone evidence; otherwise calendar entries are all-day. Outlook operations never include attendees.
83
+
84
+ HTTP standalone mode uses a separate bearer token per person at `/users/<id>/mcp`. Gateway mode accepts a configured backend bearer and trusted person header at `/mcp`. These modes are separate trust boundaries; see [permissions](docs/PERMISSIONS.md).
85
+
86
+ ## Docker Compose
87
+
88
+ ```sh
89
+ cp earnings-watch.example.toml earnings-watch.toml
90
+ docker compose build
91
+ docker compose run --rm server init
92
+ docker compose run --rm server users add example
93
+ docker compose up -d
94
+ ```
95
+
96
+ Save the returned token privately and use it only on that person's HTTP path. The server binds to loopback by default. Both images run as uid 1000; Compose drops capabilities, prevents privilege escalation and mounts the root filesystem read-only. The worker image includes pinned model CLIs; their auth volume is optional with `backends=["none"]`. Configure CLI sign-in outside the read-only worker before enabling model maintenance. Protect auth files and the data volume from other users.
97
+
98
+ Use `--config PATH`, `--person ID`, and `--json` with the CLI. Settings come from TOML and `EARNINGS_WATCH_*` environment variables. See [operations](docs/OPERATIONS.md), [architecture](docs/ARCHITECTURE.md), [providers](docs/PROVIDERS.md) and [design decisions](docs/DESIGN.md).
99
+
100
+ ## Development
101
+
102
+ `make check` verifies lock drift, lint, formatting, types, dependency boundaries, offline tests and wheel/source builds. Tests use synthetic issuers and fake network/model/account boundaries. Real-model and real-page checks are separate operator gates. Read [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
103
+
104
+ See [implementation status](docs/IMPLEMENTATION.md) for completed capabilities,
105
+ release gates and the production adapter work still required before cutover.
@@ -0,0 +1,87 @@
1
+ # Earnings Watch
2
+
3
+ <!-- mcp-name: io.github.benjamin-di/earnings-watch -->
4
+
5
+ Verified issuer earnings calendars and results documents through a CLI, Python API and MCP server. Models propose dates; deterministic checks require quoted evidence and consistent issuer, period and timing. A call time never silently becomes a publication time.
6
+
7
+ Calendar updates are optional. Earnings Watch produces an operation plan; your MCP client uses its external Outlook app to apply it and acknowledges confirmed writes. No Microsoft credentials or calendar SDK are required by this package.
8
+
9
+ Requires Python 3.12+. Install a release wheel with `uv tool install path/to/earnings_watch-0.1.0-py3-none-any.whl`, or develop from a source checkout:
10
+
11
+ ```sh
12
+ make bootstrap
13
+ uv run earnings-watch init
14
+ uv run earnings-watch doctor
15
+ uv run earnings-watch sources import examples/sources.yaml
16
+ uv run earnings-watch watch EXAMPLE
17
+ uv run earnings-watch upcoming --format json
18
+ ```
19
+
20
+ The example uses reserved domains and fictional data; replace its source URLs and issuer metadata before fetching. There is no bundled issuer universe or market-data subscription.
21
+
22
+ ## Maintenance and models
23
+
24
+ Serving reads SQLite and updates personal follows/ledgers; it never fetches issuer pages or calls a model. Run maintenance separately:
25
+
26
+ ```sh
27
+ uv run earnings-watch sweep --dry-run
28
+ uv run earnings-watch sweep
29
+ uv run earnings-watch sweep --docs-only
30
+ uv run earnings-watch worker
31
+ ```
32
+
33
+ The default backend is `none`: it preserves existing dates and still polls official release providers. To extract new dates, install and sign into your own Claude Code or Codex CLI, then configure it in `earnings-watch.toml`:
34
+
35
+ ```toml
36
+ [llm]
37
+ backends = ["claude-code", "codex", "none"]
38
+ max_calls_per_run = 20
39
+ ```
40
+
41
+ Supported minimum versions are Claude Code 2.1.312 and Codex 0.164.0. Run `earnings-watch doctor --live` to check capabilities and an optional model canary. The package has no login flow. Model usage follows your CLI account's limits and terms. Missing credentials or capacity may try the next backend; a timed-out run is not repeated in the same tick.
42
+
43
+ ## MCP
44
+
45
+ A local client configuration, after initialization:
46
+
47
+ ```json
48
+ {"mcpServers":{"earnings":{"command":"earnings-watch","args":["serve","--stdio"]}}}
49
+ ```
50
+
51
+ | Tool | Purpose |
52
+ | --- | --- |
53
+ | `earnings_upcoming` | Bounded upcoming dates with timing uncertainty |
54
+ | `earnings_get_events` | Evidence, revisions and recent history |
55
+ | `earnings_documents` | Official result-document links and verification status |
56
+ | `earnings_changes` | Cursor-based public change feed |
57
+ | `earnings_watchlist` | Your follows and registration states |
58
+ | `earnings_watch` | Follow an issuer; queue registration or resolve ambiguity |
59
+ | `earnings_unwatch` | Remove a manual follow |
60
+ | `earnings_calendar_plan` | Plan optional external calendar operations |
61
+ | `earnings_calendar_ack` | Record confirmed operations from a signed plan |
62
+ | `earnings_status` | Catalogue and maintenance status |
63
+
64
+ Prompts: `weekly_earnings_brief`, `results_brief`, `sync_to_my_outlook`. Resource: `earnings://upcoming.ics`. Exact times require verified clock and timezone evidence; otherwise calendar entries are all-day. Outlook operations never include attendees.
65
+
66
+ HTTP standalone mode uses a separate bearer token per person at `/users/<id>/mcp`. Gateway mode accepts a configured backend bearer and trusted person header at `/mcp`. These modes are separate trust boundaries; see [permissions](docs/PERMISSIONS.md).
67
+
68
+ ## Docker Compose
69
+
70
+ ```sh
71
+ cp earnings-watch.example.toml earnings-watch.toml
72
+ docker compose build
73
+ docker compose run --rm server init
74
+ docker compose run --rm server users add example
75
+ docker compose up -d
76
+ ```
77
+
78
+ Save the returned token privately and use it only on that person's HTTP path. The server binds to loopback by default. Both images run as uid 1000; Compose drops capabilities, prevents privilege escalation and mounts the root filesystem read-only. The worker image includes pinned model CLIs; their auth volume is optional with `backends=["none"]`. Configure CLI sign-in outside the read-only worker before enabling model maintenance. Protect auth files and the data volume from other users.
79
+
80
+ Use `--config PATH`, `--person ID`, and `--json` with the CLI. Settings come from TOML and `EARNINGS_WATCH_*` environment variables. See [operations](docs/OPERATIONS.md), [architecture](docs/ARCHITECTURE.md), [providers](docs/PROVIDERS.md) and [design decisions](docs/DESIGN.md).
81
+
82
+ ## Development
83
+
84
+ `make check` verifies lock drift, lint, formatting, types, dependency boundaries, offline tests and wheel/source builds. Tests use synthetic issuers and fake network/model/account boundaries. Real-model and real-page checks are separate operator gates. Read [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md).
85
+
86
+ See [implementation status](docs/IMPLEMENTATION.md) for completed capabilities,
87
+ release gates and the production adapter work still required before cutover.
@@ -0,0 +1,7 @@
1
+ # Security
2
+
3
+ Use GitHub's private vulnerability reporting for security issues. Never publish credentials, database files, calendar handles or raw private diagnostics in an issue.
4
+
5
+ Keep the data/auth volumes private and use TLS for remote HTTP. Standalone per-person tokens and gateway backend credentials have different authority; see [permissions](docs/PERMISSIONS.md). Do not expose gateway mode directly to untrusted callers.
6
+
7
+ Security-relevant fixes receive a new release. Treat model output, captured pages, document links and mirror contributions as untrusted inputs. Financial dates and document verification are evidence checks, not investment advice or financial-content certification.
@@ -0,0 +1,27 @@
1
+ services:
2
+ server:
3
+ image: ghcr.io/benjamin-di/earnings-watch:0.1.0
4
+ build:
5
+ context: .
6
+ target: serve
7
+ ports: ["127.0.0.1:8787:8787"]
8
+ volumes: ["data:/data", "./earnings-watch.toml:/app/earnings-watch.toml:ro"]
9
+ read_only: true
10
+ cap_drop: [ALL]
11
+ security_opt: ["no-new-privileges:true"]
12
+ tmpfs: ["/tmp:mode=1777"]
13
+ restart: unless-stopped
14
+ worker:
15
+ image: ghcr.io/benjamin-di/earnings-watch-worker:0.1.0
16
+ build:
17
+ context: .
18
+ target: worker
19
+ volumes: ["data:/data", "./earnings-watch.toml:/app/earnings-watch.toml:ro", "cli-auth:/home/earnings"]
20
+ read_only: true
21
+ cap_drop: [ALL]
22
+ security_opt: ["no-new-privileges:true"]
23
+ tmpfs: ["/tmp:mode=1777"]
24
+ restart: unless-stopped
25
+ volumes:
26
+ data:
27
+ cli-auth:
@@ -0,0 +1,19 @@
1
+ # Architecture
2
+
3
+ One SQLite database owns both shared public facts and person-scoped state. Shared tables hold sources, events, revisions, polling state and documents. Personal tables hold follows, calendar ledgers, delivery receipts and downloads. Personal access always requires an explicit person selected at the authentication boundary.
4
+
5
+ CLI, MCP and Python callers share `PersonService`. It receives a read-only library reader and a personal store. The serving surface has no network, model or provider dependency. FastMCP runs synchronous service calls outside its event loop. SQL connections use WAL, five-second busy timeouts and explicit transactions; personal acknowledgements use immediate write transactions.
6
+
7
+ Maintenance runs separately under one process lock. Its stages register pending issuers, capture pages, extract claims, verify evidence, reconcile facts, poll official releases and prepare personal notification outboxes. A failed or incomplete extraction cannot establish absence. Removal caps guard catalogue commits. Models and web pages cannot write directly to the database.
8
+
9
+ Only `store/` imports SQLite. `transport.py` owns HTTP, including DNS validation and IP pinning with original-host TLS SNI. `llm/` owns model processes. The pure engine depends on models and the standard library. An AST gate enforces capability boundaries.
10
+
11
+ Calendar plans read follows, events, revision sequence and ledger in one snapshot. A signed handle binds that snapshot, person, sink, operations and thirty-minute expiry. Acknowledgement validates the handle and revisions in the same transaction that records confirmed writes. Retries of an acknowledged handle return its original receipt.
12
+
13
+ Event revisions increase across removal and reintroduction, so change consumers
14
+ and ICS clients never see a sequence reset. Personal download receipts describe
15
+ historical effects and survive catalogue retirement. They reference a document's
16
+ stable identifier without requiring that document to remain in the current
17
+ catalogue; new receipts validate current catalogue membership before insertion.
18
+
19
+ The library deliberately shares public facts. A public query can reveal that a source was registered or refreshed; it never returns another person's follows, tokens or calendar ledger. Operators who need to hide interest in an issuer must use separate deployments.
@@ -0,0 +1,13 @@
1
+ # Design decisions
2
+
3
+ Earnings Watch describes the task and retains the `earnings_` MCP namespace. It combines an evidence-backed issuer calendar with document discovery; it is not a price feed or a forecast of earnings dates.
4
+
5
+ The design uses three familiar patterns: a pure validation/reconciliation core, a shared catalogue with personal projections, and a durable outbox. SQLite keeps backup and installation simple for one host. A database server, built-in OAuth issuer and plugin loader would add operational boundaries without improving this workload.
6
+
7
+ Model access uses user-owned agent CLIs. A raw API integration would introduce a second authentication and billing path. Extraction runs with tools disabled. Research has only search and scoped page/observation tools; page evidence must still pass deterministic validation. Browser automation is an extension boundary rather than a requirement for reading static issuer pages.
8
+
9
+ Maintenance is asynchronous because requests and model subprocesses have independent deadlines. The serving service remains synchronous and bounded. This is a deliberate deviation from a synchronous-core preference: I/O orchestration lives outside the pure engine and read service.
10
+
11
+ Side effects have explicit boundaries. R0 queries produce facts or plans. R1 operations update only personal state. Outbound notification delivery requires a separate notifier invocation. Calendar plan/ack delegates account effects to the client's optional Outlook app. No calendar feature grants arbitrary web content authority to invite people.
12
+
13
+ A single authoritative database replaces hand-edited state files. YAML, JSON, CSV and ICS are projections. Legacy import/export supports migration rehearsal; an uncertain legacy delivery remains unknown until an operator reconciles it. A failed image rollout does not restore database state.