feat: done vibing

This commit is contained in:
ytshih committed 2026-09-05 09:55:24 +08:00
1 parent 3de31a57c3
commit 4352fd1ad1
27 files changed
+1922 -13

No files matched your search

+21 -12
View File
@@ -5,7 +5,7 @@ Daily financial news + market data analysis service. Fetches free-tier market da
## Architecture
```
systemd timer (daily) --> stonks.service (oneshot) --> cmd/stonks/main.go
systemd timer (every 5h) --> stonks.service (oneshot) --> cmd/stonks/main.go
|
---------------------------------------------
| | |
@@ -110,6 +110,12 @@ outputs:
Both modes implement the same `Analyzer` interface, selected by `analyzer.mode` in config.
### Multi-agent mode (optional advisors)
`analyzer.advisors` (a list of `{name, command, args}`) names additional one-shot CLIs — e.g. `gemini`, `codex exec -` — that receive the *same* fetched-data prompt (piped via stdin, `internal/analyze/advisor.go`) and produce an independent take. Advisors run concurrently; the ones that succeed are appended to Claude's final prompt as an "Other AI Analysts' Independent Takes" section, and `SystemPrompt` instructs Claude to weigh them (agree/disagree explicitly) rather than defer to them — Claude still writes the one final report and recommendations block.
This is deliberately **not** a fourth `Analyzer` implementation — advisors don't produce the final report shape (no recommendations-JSON contract), they're inputs to it. A failing or unauthenticated advisor CLI is logged as a warning and excluded from the prompt, same graceful-degradation pattern as the fetchers; it never fails the run. Each advisor CLI needs its own separate login (`gemini`, `codex login`) — independent of the `claude`/Anthropic auth used for the final analysis.
## systemd integration — user scope (`systemctl --user`)
Units live under `~/.config/systemd/user/` and are managed with `systemctl --user`, not root-owned `/etc/systemd/system/`. This fits naturally with `subscription` mode, since `claude login`'s session lives under the same user's `$HOME` anyway — no `User=`/permissions mismatch to manage.
@@ -129,10 +135,11 @@ ExecStart=%h/.local/bin/stonks --config %h/.config/stonks/config.yaml
```ini
# ~/.config/systemd/user/stonks.timer
[Unit]
Description=Run stonks daily
Description=Run stonks every 5 hours
[Timer]
OnCalendar=*-*-* 07:00:00
# Fires at 00:00, 05:00, 10:00, 15:00, 20:00 (wall-clock aligned).
OnCalendar=*-*-* 0/5:00:00
Persistent=true
[Install]
@@ -145,7 +152,7 @@ WantedBy=timers.target
systemctl --user enable --now stonks.timer
```
**User-scope services normally stop when the user logs out.** Since this needs to fire daily unattended (including when nobody's logged in), enable lingering once:
**User-scope services normally stop when the user logs out.** Since this needs to fire every 5 hours unattended (including when nobody's logged in), enable lingering once:
```sh
loginctl enable-linger "$USER"
@@ -169,18 +176,20 @@ Estimated build time: a working v1 (one fetcher, one output, hardcoded targets)
## Cost
**`subscription` mode: $0 marginal cost**, covered by your existing Claude Pro/Max subscription, assuming usage stays well within its rate limits — trivially true for one call/day.
The timer now fires every 5 hours (`OnCalendar=*-*-* 0/5:00:00`) — 5 runs/day instead of 1.
**`api` mode**, for ~5-15k input / ~1-2k output tokens per day (no caching benefit at one call/day):
**`subscription` mode: still $0 marginal cost**, covered by your existing Claude Pro/Max subscription. The one thing worth watching at 5x the frequency: subscription plans have rolling usage windows (typically ~5 hours), so if this job's prompt is large or other subscription usage is heavy at the same time, it's worth an occasional `journalctl --user -u stonks.service` check for rate-limit errors — not a cost concern, just a headroom one.
| Model | Input $/MTok | Output $/MTok | Daily (worst case: 15k in / 2k out) | Daily (typical: 8k in / 1.5k out) | Monthly (typical) |
**`api` mode**, for ~5-15k input / ~1-2k output tokens per run (no caching benefit between runs — each is a cold prompt), now at 5 runs/day:
| Model | Input $/MTok | Output $/MTok | Daily (worst case: 5 × 15k in / 2k out) | Daily (typical: 5 × 8k in / 1.5k out) | Monthly (typical) |
|---|---|---|---|---|---|
| Claude Opus 5 | $5.00 | $25.00 | $0.125 | $0.078 | ~$2.30 |
| Claude Sonnet 5 (intro, through 2026-08-31) | $2.00 | $10.00 | $0.050 | $0.031 | ~$0.94 |
| Claude Sonnet 5 (standard) | $3.00 | $15.00 | $0.075 | $0.047 | ~$1.40 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.025 | $0.016 | ~$0.47 |
| Claude Opus 5 | $5.00 | $25.00 | $0.625 | $0.39 | ~$11.70 |
| Claude Sonnet 5 (intro, through 2026-08-31) | $2.00 | $10.00 | $0.25 | $0.155 | ~$4.65 |
| Claude Sonnet 5 (standard) | $3.00 | $15.00 | $0.375 | $0.235 | ~$7.05 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.125 | $0.08 | ~$2.40 |
Even at Opus 5 pricing in `api` mode this is under $3/month — the choice between modes should be driven by which auth story you'd rather run unattended (personal subscription login vs. a static API key), not by cost.
Still cheap in absolute terms, but ~5x the daily-cadence estimate — worth knowing if you're on `api` mode and cost-sensitive. `subscription` mode sidesteps this entirely since it isn't metered per token.
## Open questions / next steps