Anthropic Readings Daemon is a Python service that watches Anthropic learning repositories, renders changed documents to PDF, uploads the outputs to Microsoft OneDrive AppFolder, and emails recipients links to the latest documents.
Disclaimer: This project was initially assembled with AI assistance to deliver core functionality quickly, and has not yet been fully polished for broad public consumption.
It is designed to run as an unattended background job, with a safe one-shot mode for local testing and manual runs.
- Keeps a local clone of configured GitHub repositories and updates them on each run.
- Discovers documents from:
cookbooks: fromregistry.yamlentriescourses: from configurable file globs (for example*.md,*.ipynb)
- Detects changes using both document date and file content hash (SHA-256, truncated).
- Renders new/updated markdown and notebooks to PDF:
- Markdown (
.md) viapandoc+weasyprint - Jupyter Notebooks (
.ipynb) viajupyter nbconvert --to=html+weasyprint
- Markdown (
- Rewrites internal links before rendering so references to
.md/.ipynbbecome.pdflinks. - Uploads generated PDFs and per-repo version metadata files to OneDrive under
special/approot(AppFolder). - Generates recipient-scoped sharing links for each uploaded PDF.
- Sends a single update email with all new/updated links and render/upload errors.
- Performs all upload/link/version operations with rollback behavior: if any stage fails, uploaded OneDrive items are removed.
- Cleans up temporary output and cloned repository directories after each run.
- No config fallback: startup fails fast if config is missing or invalid.
- Date stability during a run: output folders use the discovered document metadata date (not file mtime), preventing date drift on long runs.
- Course paths:
AmazonBedrockcontent is excluded.- Folder structure is preserved in output/upload paths (except
Anthropic 1Ppath segments are dropped in OneDrive PDFs).
- Cookbook naming:
- PDF filenames come from manifest titles and are slugified.
00-foo.mdbecomes00-<slugified-title>.pdf.
- Upload paths are repo-scoped:
- PDFs:
<repo_name>/... - Version files:
<repo_name>/<version_file_name>
- PDFs:
- OneDrive cleanup semantics:
- rollback deletes uploaded artifacts via permanent delete endpoint behavior where required.
- Error reporting: if configured as production, failures are also summarized and sent as an error email.
.
├── src/
│ └── anthropic_readings/
│ ├── core/
│ │ ├── link_rewrite.py
│ │ └── output_paths.py
│ ├── cli.py
│ ├── config.py
│ ├── discovery.py
│ ├── orchestrator.py
│ ├── rendering.py
│ ├── graph.py
│ ├── mailer.py
│ ├── repository.py
│ └── models.py
├── daemon.py
├── run_onedrive_test.py
├── anthropic_daemon.service
├── anthropic_daemon.timer
├── config.yaml.example
└── tests/
- Python 3.12+
uvgitpandocweasyprint- Microsoft Entra / Azure AD app with Microsoft Graph access
macOS:
brew install pandoc weasyprintDebian / Ubuntu:
sudo apt-get update
sudo apt-get install -y pandoc weasyprint gitNotebook rendering uses static HTML plus weasyprint, so Chromium is not required.
git clone <your-repo-url>
cd claude-cookbooks-auto-updateuv venv
source .venv/bin/activate
uv pip install -e .The project entry point is:
anthropic-readings-daemon --helpThis repository now includes a flake.nix that exposes:
packages.<system>.default: the packagedanthropic-readings-daemonhomeManagerModules.default: a Home Manager module for asystemd --userservicenixosModules.default: a NixOS module that also defines asystemd --userservice
The packaged daemon is wrapped with the runtime tools it needs at execution time:
gitpandocweasyprint
{
inputs.anthropic-readings.url = "path:/path/to/claude-cookbooks-auto-update";
outputs = { self, nixpkgs, home-manager, anthropic-readings, ... }: {
homeConfigurations.alice = home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs { system = "x86_64-linux"; };
modules = [
anthropic-readings.homeManagerModules.default
{
services.anthropic-readings = {
enable = true;
configFile = "/home/alice/.config/anthropic-readings/config.yaml";
};
}
];
};
};
}{
inputs.anthropic-readings.url = "path:/path/to/claude-cookbooks-auto-update";
outputs = { self, nixpkgs, anthropic-readings, ... }: {
nixosConfigurations.host = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
anthropic-readings.nixosModules.default
{
services.anthropic-readings = {
enable = true;
configFile = "/home/alice/.config/anthropic-readings/config.yaml";
};
}
];
};
};
}The user service runs continuously with Restart=always and RestartSec=4h by default, so if it exits, systemd waits four hours before attempting the next restart. You can override that with services.anthropic-readings.restartDelay.
You can also run with Python directly:
python -m anthropic_readings --helpcp config.yaml.example config.yamlEdit config.yaml to match your environment.
azure.tenant_idazure.client_idazure.client_secretuser.emailemail.senderemail.recipients(non-empty list)repos(at least one repo config)
Each repo item requires:
nameurllocal_pathversion_filediscover_patterns
Optional repo field:
manifest_file(used for cookbook manifest mode)
azure:
tenant_id: "your-tenant-id"
client_id: "your-client-id"
client_secret: "your-client-secret"
user:
email: "operator@company.com"
# Leave empty to use device-code flow (recommended for unattended jobs)
password: ""
email:
sender: "operator@company.com"
sender_name: "Anthropic Readings"
recipients:
- "recipient1@company.com"
- "recipient2@company.com"
# Optional: explicit users for sharing links
# share_recipients:
# - "reader1@company.com"
share_domain_filter_enabled: false
share_domain: "company.com"
subject_prefix: "[Anthropic Readings]"
paths:
output_dir: "outputs"
repos:
- name: "cookbooks"
url: "https://github.com/anthropics/claude-cookbooks.git"
local_path: "claude-cookbooks"
version_file: "cookbook-version.json"
discover_patterns:
- "*.ipynb"
manifest_file: "registry.yaml"
- name: "courses"
url: "https://github.com/anthropics/courses.git"
local_path: "claude-courses"
version_file: "courses-version.json"
discover_patterns:
- "*.ipynb"
- "*.md"
manifest_file: null
daemon:
log_level: "INFO"
random_delay_max_hours: 1
render_concurrency: 1
render_timeout_seconds: 600
upload_concurrency: 4
is_production: false- Device code flow (recommended): set
user.password: "". - Username/password flow: provide both
user.emailanduser.password.
The daemon asks for graph scopes similar to:
User.ReadMail.SendFiles.ReadWrite(AppFolder workflow expects AppFolder access)
Validate config:
python -m anthropic_readings --check --config config.yamlRun once (recommended for testing):
python -m anthropic_readings --once --config config.yamlRun continuously:
python -m anthropic_readings --config config.yamlIf --config is omitted, defaults are checked in this order:
./config.yamlsrc/anthropic_readings/config.yaml<project-root>/config.yaml
If schedule is not installed, long-lived mode automatically falls back to --once.
The included unit files are wired for installation at /opt/anthropic-readings/.
sudo cp anthropic_daemon.service /etc/systemd/system/
sudo cp anthropic_daemon.timer /etc/systemd/system/
sudo systemctl enable anthropic_daemon.timer
sudo systemctl start anthropic_daemon.timerService checks:
systemctl status anthropic_daemon.timer
systemctl status anthropic_daemon.service
journalctl -u anthropic-daemon -n 200- Load and validate config
- Clone or pull each configured repo
- Discover current documents
- Compare against previous version metadata in OneDrive
- Render only changed documents
- Upload PDFs and create sharing links
- Send one consolidated email update
- Upload updated version metadata files
- Remove temporary local artifacts on completion
- If startup fails with
CONFIG ERROR, fix the reported field and rerun--check. - If PDF output fails, verify
pandoc,weasyprint, and notebook rendering dependencies. - Keep
daemon.render_concurrency: 1on low-memory hosts; each render can be memory-intensive. - If emails are skipped, verify
Mail.Sendconsent in Azure and thatemail.sender/email.recipientsare valid. - If device code auth is expected, keep
user.passwordempty. - Local output and cloned repo directories are deleted after the run; if a repo disappears unexpectedly, it will be re-cloned next run.
Targeted unit suite:
python -m pytest tests/test_daemon.pyRun all tests:
python -m pytest testsOneDrive-focused tests (mocked by default):
python run_onedrive_test.pyLive OneDrive test:
python run_onedrive_test.py --live --credentials /path/to/onedrive-test-credentials.yamlKeep remote test artifacts:
python run_onedrive_test.py --live --credentials /path/to/onedrive-test-credentials.yaml --keep-remoteUse this project when you need a reliable pipeline that continuously keeps OneDrive links updated as Anthropic content evolves, while keeping link references valid inside rendered PDFs and preserving repo-aware upload locations.