My aggregated monorepo of OCaml code, automaintained

Skip past days with existing files, verify admin via delivery_email

monopam:
- Make --aggregate the default for daily changes (add --no-aggregate to skip)
- Skip past days entirely if per-day file exists to avoid redundant Claude calls
- For today, still check if entry exists before regenerating
- Simplify Zulip format headers ("Updates for..." instead of "## Changes for...")

poe:
- Fix admin verification to use delivery_email from Zulip API
- Fallback to sender_email if API call fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+220 -94
+142
.changes/20260119.json
··· 1 + { 2 + "date": "2026-01-19", 3 + "generated_at": "2026-01-19T16:41:20Z", 4 + "git_head": "e5fd33d", 5 + "entries": [ 6 + { 7 + "repository": "monopam", 8 + "hour": 16, 9 + "timestamp": "2026-01-19T16:12:08Z", 10 + "summary": "Added changes broadcast system with new monopam_changes library and poe bot automation", 11 + "changes": [ 12 + "Added monopam_changes library with Aggregated and Query modules for changes format", 13 + "Added --aggregate flag to `monopam changes --daily` for structured JSON output", 14 + "Added Daily module with Map-based indexes and query functions (since, for_repo, for_date)", 15 + "Changed daily files from <repo>-daily.json to <repo>-<date>.json with hour tracking", 16 + "Added poe bot commands: loop, last-broadcast, reset-broadcast, storage management" 17 + ], 18 + "commit_range": { 19 + "from": "5331f9b", 20 + "to": "440e98b", 21 + "count": 2 22 + }, 23 + "contributors": [ 24 + "Anil Madhavapeddy" 25 + ], 26 + "repo_url": "https://tangled.org/@anil.recoil.org/monopam.git", 27 + "change_type": "feature" 28 + }, 29 + { 30 + "repository": "ocaml-apubt", 31 + "hour": 16, 32 + "timestamp": "2026-01-19T16:12:08Z", 33 + "summary": "Added authentication system with XDG-based credential storage and completed CLI with webfinger integration.", 34 + "changes": [ 35 + "Added apub_auth library with XDG-compliant session persistence in ~/.config/apub/", 36 + "Added CLI commands: auth setup/status/logout, profile list/switch/current", 37 + "Added write commands: post, follow, like, boost (auto-load saved credentials)", 38 + "Integrated ocaml-webfinger for RFC 7033/7565 compliant actor discovery", 39 + "Added Question activity support with one_of, any_of, closed fields" 40 + ], 41 + "commit_range": { 42 + "from": "46d4063", 43 + "to": "fbd519f", 44 + "count": 4 45 + }, 46 + "contributors": [ 47 + "Anil Madhavapeddy" 48 + ], 49 + "repo_url": "https://tangled.org/@anil.recoil.org/ocaml-apubt.git", 50 + "change_type": "feature" 51 + }, 52 + { 53 + "repository": "ocaml-atp", 54 + "hour": 16, 55 + "timestamp": "2026-01-19T16:12:08Z", 56 + "summary": "Fixed non-deterministic code generation in hermest lexicon generator", 57 + "changes": [ 58 + "Fixed hermest producing different output on each run by sorting alphabetically", 59 + "Regenerated all lexicon files with deterministic ordering" 60 + ], 61 + "commit_range": { 62 + "from": "ae93f40", 63 + "to": "ae93f40", 64 + "count": 1 65 + }, 66 + "contributors": [ 67 + "Anil Madhavapeddy" 68 + ], 69 + "repo_url": "https://tangled.org/@anil.recoil.org/ocaml-atp.git", 70 + "change_type": "bugfix" 71 + }, 72 + { 73 + "repository": "ocaml-webfinger", 74 + "hour": 16, 75 + "timestamp": "2026-01-19T16:12:08Z", 76 + "summary": "New library providing RFC 7033 WebFinger protocol implementation for OCaml", 77 + "changes": [ 78 + "Added ocaml-webfinger library implementing RFC 7033 WebFinger protocol", 79 + "Added abstract Link and Jrd types with jsont JSON encoding/decoding", 80 + "Added HTTP request support via requests library", 81 + "Added command-line interface with cmdliner for WebFinger lookups", 82 + "Added nullable property support for JRD responses" 83 + ], 84 + "commit_range": { 85 + "from": "04ac158", 86 + "to": "cab4ed5", 87 + "count": 3 88 + }, 89 + "contributors": [ 90 + "Anil Madhavapeddy" 91 + ], 92 + "repo_url": "https://tangled.org/@anil.recoil.org/ocaml-webfinger.git", 93 + "change_type": "feature" 94 + }, 95 + { 96 + "repository": "ocaml-zulip", 97 + "hour": 16, 98 + "timestamp": "2026-01-19T16:12:08Z", 99 + "summary": "Improved bot functionality and cleaned up build configuration", 100 + "changes": [ 101 + "Improved bot functionality", 102 + "Removed public_name/package from test and example executables" 103 + ], 104 + "commit_range": { 105 + "from": "de14ffa", 106 + "to": "fb94ffb", 107 + "count": 2 108 + }, 109 + "contributors": [ 110 + "Anil Madhavapeddy" 111 + ], 112 + "repo_url": "https://tangled.org/@anil.recoil.org/ocaml-zulip.git", 113 + "change_type": "unknown" 114 + }, 115 + { 116 + "repository": "poe", 117 + "hour": 16, 118 + "timestamp": "2026-01-19T16:12:08Z", 119 + "summary": "Added automated changes broadcast system with new polling loop and admin commands", 120 + "changes": [ 121 + "Added `poe loop --interval` command for automated hourly change broadcasting", 122 + "Added admin commands: last-broadcast, reset-broadcast, storage keys/get/delete", 123 + "Added Commands module with deterministic parsing (help, status, broadcast, admin)", 124 + "Added Broadcast module for smart change detection (only sends new changes)", 125 + "Added config options: admin_emails and changes_dir fields" 126 + ], 127 + "commit_range": { 128 + "from": "de14ffa", 129 + "to": "440e98b", 130 + "count": 2 131 + }, 132 + "contributors": [ 133 + "Anil Madhavapeddy" 134 + ], 135 + "repo_url": "https://tangled.org/@anil.recoil.org/poe.git", 136 + "change_type": "feature" 137 + } 138 + ], 139 + "authors": [ 140 + "Anil Madhavapeddy" 141 + ] 142 + }
+7 -69
DAILY-CHANGES.md
··· 4 4 5 5 ### New Libraries 6 6 7 - - **[ocaml-webfinger](https://tangled.org/@anil.recoil.org/ocaml-webfinger.git)**: OCaml implementation of RFC 7033 WebFinger protocol for discovering information about resources using standard HTTP. Includes abstract Link and JRD types with jsont JSON encoding/decoding, nullable property support, and a command-line interface built with cmdliner. Uses [ocaml-requests](https://tangled.org/@anil.recoil.org/ocaml-requests.git) for HTTP operations. — *Anil Madhavapeddy* 7 + - **[ocaml-webfinger](https://tangled.org/@anil.recoil.org/ocaml-webfinger.git)**: OCaml implementation of RFC 7033 WebFinger protocol for discovering information about people and resources using standard web protocols. Provides abstract Link and JRD types with jsont JSON encoding/decoding, HTTP request support via the requests library, nullable property handling, and a command-line interface using cmdliner for performing lookups. — *Anil Madhavapeddy* 8 8 9 9 ### Major Features 10 10 11 - - **[ocaml-apubt](https://tangled.org/@anil.recoil.org/ocaml-apubt.git)**: Added complete authentication system with XDG-compliant credential storage in ~/.config/apub/. New CLI commands for auth (setup/status/logout), profile management (list/switch/current), and write operations (post, follow, like, boost) with auto-loaded credentials. Integrated [ocaml-webfinger](https://tangled.org/@anil.recoil.org/ocaml-webfinger.git) for RFC 7033/7565 compliant actor discovery. Added Question activity support with one_of, any_of, closed fields. — *Anil Madhavapeddy* 11 + - **[ocaml-apubt](https://tangled.org/@anil.recoil.org/ocaml-apubt.git)**: Added complete authentication system with XDG-compliant credential storage in `~/.config/apub/`. New CLI commands include auth setup/status/logout, profile management (list/switch/current), and write operations (post, follow, like, boost) that auto-load saved credentials. Integrated [ocaml-webfinger](https://tangled.org/@anil.recoil.org/ocaml-webfinger.git) for RFC 7033/7565 compliant actor discovery. Also added Question activity support with one_of, any_of, and closed fields. — *Anil Madhavapeddy* 12 12 13 - - **[poe](https://tangled.org/@anil.recoil.org/poe.git)**: Added automated changes broadcast system with new `poe loop --interval` command for hourly change broadcasting. New admin commands (last-broadcast, reset-broadcast, storage keys/get/delete), Commands module with deterministic parsing, and Broadcast module for smart change detection that only sends new changes. Added config options for admin_emails and changes_dir. — *Anil Madhavapeddy* 13 + - **[poe](https://tangled.org/@anil.recoil.org/poe.git)**: Added automated changes broadcast system with new `poe loop --interval` command for hourly change broadcasting. Includes new admin commands (last-broadcast, reset-broadcast, storage keys/get/delete), a Commands module with deterministic parsing, and a Broadcast module with smart change detection that only sends new updates. Added config options for admin_emails and changes_dir. — *Anil Madhavapeddy* 14 14 15 - - **[monopam](https://tangled.org/@anil.recoil.org/monopam.git)**: Added monopam_changes library with Aggregated and Query modules for structured changelog format. New --aggregate flag for `monopam changes --daily` producing structured JSON output. Daily module with Map-based indexes and query functions (since, for_repo, for_date). Changed daily files from <repo>-daily.json to <repo>-<date>.json with hour tracking. — *Anil Madhavapeddy* 15 + - **[monopam](https://tangled.org/@anil.recoil.org/monopam.git)**: Added monopam_changes library with Aggregated and Query modules for structured change tracking. New `--aggregate` flag for `monopam changes --daily` produces structured JSON output. Includes Daily module with Map-based indexes and query functions (since, for_repo, for_date). Changed daily file format from `<repo>-daily.json` to `<repo>-<date>.json` with hour tracking. — *Anil Madhavapeddy* 16 16 17 - ### Bug Fixes 18 - 19 - - **[ocaml-atp](https://tangled.org/@anil.recoil.org/ocaml-atp.git)**: Fixed non-deterministic code generation in hermest lexicon generator by sorting alphabetically. Regenerated all lexicon files with deterministic ordering. — *Anil Madhavapeddy* 20 - 21 - ### Code Quality Improvements 17 + ### Improvements 22 18 23 19 - **[ocaml-zulip](https://tangled.org/@anil.recoil.org/ocaml-zulip.git)**: Improved bot functionality and cleaned up build configuration by removing public_name/package from test and example executables. — *Anil Madhavapeddy* 24 20 25 - --- 26 - 27 - ## 2026-01-18 28 - 29 - ### New Libraries 30 - 31 - - **[ocaml-mail-flag](https://tangled.org/@anil.recoil.org/ocaml-mail-flag.git)**: Unified library for parsing and manipulating email flags across protocols. Provides shared Keyword, Mailbox_attr, and Flag_color modules used by both [ocaml-imap](https://tangled.org/@anil.recoil.org/ocaml-imap.git) and [ocaml-jmap](https://tangled.org/@anil.recoil.org/ocaml-jmap.git) for IMAP/JMAP interoperability. — *Anil Madhavapeddy* 32 - 33 - - **[ocaml-frontmatter](https://tangled.org/@anil.recoil.org/ocaml-frontmatter.git)**: OCaml library for parsing YAML and TOML frontmatter in documents, useful for static site generators and document processors. Uses [ocaml-yamlt](https://tangled.org/@anil.recoil.org/ocaml-yamlt.git) for YAML parsing. — *Anil Madhavapeddy* 34 - 35 - - **[ocaml-bushel](https://tangled.org/@anil.recoil.org/ocaml-bushel.git)**: Added ocaml-bushel library to the monorepo. — *Anil Madhavapeddy* 36 - 37 - ### Email Protocol Improvements 38 - 39 - - **[ocaml-imap](https://tangled.org/@anil.recoil.org/ocaml-imap.git)**: Major expansion of IMAP RFC compliance with ESEARCH, THREAD, QUOTA, LIST-EXTENDED, UTF-8, and CONDSTORE extensions. Added SORT command with sort keys (Arrival, Date, From, Size, Subject, To). Fixed SEARCH response parsing and APPEND literal synchronization (LITERAL+). Added BODY/BODYSTRUCTURE recursive MIME parsing with section specifiers. Integrated [ocaml-mail-flag](https://tangled.org/@anil.recoil.org/ocaml-mail-flag.git) for shared keyword/mailbox types. — *Anil Madhavapeddy* 40 - 41 - - **[ocaml-jmap](https://tangled.org/@anil.recoil.org/ocaml-jmap.git)**: Integrated [ocaml-mail-flag](https://tangled.org/@anil.recoil.org/ocaml-mail-flag.git) library for unified email flag handling. Added RFC 8621 keywords (Seen, Answered, Flagged, Draft, Forwarded, Phishing) and RFC 6154 mailbox roles (Inbox, Drafts, Sent, Trash, Archive, Snoozed). Added role/special_use conversion functions to Mail_mailbox and keywords conversion to Mail_email modules. — *Anil Madhavapeddy* 42 - 43 - ### Configuration & Tooling 44 - 45 - - **[poe](https://tangled.org/@anil.recoil.org/poe.git)**: Configuration now stored under unified XDG path (~/.config/poe/). Added xdg_app parameter to zulip-bot Config and renamed zulip config file from "config" to "zulip.config". — *Anil Madhavapeddy* 46 - 47 - - **[ocaml-zulip](https://tangled.org/@anil.recoil.org/ocaml-zulip.git)**: Config module now supports Zulip's native [api] section format alongside existing formats. Added xdg_app parameter for custom XDG config paths. Config loading tries [bot], then [api], then bare format automatically. — *Anil Madhavapeddy* 48 - 49 - - **[monopam](https://tangled.org/@anil.recoil.org/monopam.git)**: Improved push workflow with auto-clone of upstream repos when checkout missing. Fixed tangled.org URL parsing to strip @ prefix from usernames. — *Anil Madhavapeddy* 50 - 51 - ### API Improvements 52 - 53 - - **[ocaml-yamlt](https://tangled.org/@anil.recoil.org/ocaml-yamlt.git)**: Added convenience functions decode_string, decode_value, and decode_value' for decoding YAML directly from strings and pre-parsed Yamlrw.value types. — *Anil Madhavapeddy* 54 - 55 - - **[ocaml-matrix](https://tangled.org/@anil.recoil.org/ocaml-matrix.git)**: Added pp functions to matrix_id modules (User_id, Room_id, etc.), make constructors and accessors to event content types. Added 13 missing .mli files for matrix_client modules. Fixed odoc documentation warnings for clean @doc-full builds. — *Anil Madhavapeddy* 56 - 57 - - **[ocaml-atp](https://tangled.org/@anil.recoil.org/ocaml-atp.git)**: Regenerated OCaml bindings for atproto, bsky, standard-site, and tangled lexicons. — *Anil Madhavapeddy* 58 - 59 - --- 60 - 61 - ## 2026-01-17 62 - 63 - ### Major Features 64 - 65 - - **[monopam](https://tangled.org/@anil.recoil.org/monopam.git)**: New 'monopam changes' command generates AI-powered changelogs from git history. Added Changes module with jsont codecs for changelog serialization, Git.log function with date filtering, Claude AI integration for intelligent commit analysis, and aggregated CHANGES.md generation at monorepo root. — *Anil Madhavapeddy* 66 - 67 - ### Critical Bug Fixes 68 - 69 - - **[ocaml-imap](https://tangled.org/@anil.recoil.org/ocaml-imap.git)**: Fixed RECENT response parsing that was overwriting EXISTS count. Changed UIDs and UIDVALIDITY to int64 to handle values up to 4294967295. Fixed writer lifecycle bug causing "cannot write to closed writer" errors. Added Logs library integration for debugging. Reorganized lib/ into imap/ (client) and imapd/ (server) with clearer module names. — *Anil Madhavapeddy* 70 - 71 - - **[ocaml-requests](https://tangled.org/@anil.recoil.org/ocaml-requests.git)**: Fixed missing Uri module re-export that caused build errors. — *Anil Madhavapeddy* 72 - 73 - ### Code Quality Improvements 21 + ### Bug Fixes 74 22 75 - - **[ocaml-conpool](https://tangled.org/@anil.recoil.org/ocaml-conpool.git)**: Refactored is_healthy function to reduce nesting and improve clarity. — *Anil Madhavapeddy* 76 - 77 - - **[ocaml-zulip](https://tangled.org/@anil.recoil.org/ocaml-zulip.git)**: Improved retention type documentation in channels.mli. — *Anil Madhavapeddy* 78 - 79 - ### Documentation Updates 80 - 81 - - **[srcsetter](https://tangled.org/@anil.recoil.org/srcsetter.git)**: Added README documentation for the library. — *Anil Madhavapeddy* 82 - - **[ocaml-punycode](https://tangled.org/@anil.recoil.org/ocaml-punycode.git)**: Added README documenting unimplemented IDNA 2008 features. — *Anil Madhavapeddy* 83 - - **[ocaml-owntracks](https://tangled.org/@anil.recoil.org/ocaml-owntracks.git)**: Added README file documenting library purpose and usage. — *Anil Madhavapeddy* 84 - - **[ocaml-jsonwt](https://tangled.org/@anil.recoil.org/ocaml-jsonwt.git)**: Added README file documenting the jsonwt library. — *Anil Madhavapeddy* 85 - - **[ocaml-langdetect](https://tangled.org/@anil.recoil.org/ocaml-langdetect.git)**: Fixed language count accuracy (47→49) in dune-project synopsis. — *Anil Madhavapeddy* 23 + - **[ocaml-atp](https://tangled.org/@anil.recoil.org/ocaml-atp.git)**: Fixed non-deterministic code generation in hermest lexicon generator by sorting alphabetically. Regenerated all lexicon files with deterministic ordering. — *Anil Madhavapeddy*
+13 -8
monopam/bin/main.ml
··· 409 409 "Changes are stored in the .changes directory at the monorepo root:"; 410 410 `I (".changes/<repo>.json", "Weekly changelog entries"); 411 411 `I (".changes/<repo>-daily.json", "Daily changelog entries"); 412 - `I (".changes/YYYYMMDD.json", "Aggregated daily entries (with --aggregate)"); 412 + `I (".changes/YYYYMMDD.json", "Aggregated daily entries (default with --daily)"); 413 413 `P 414 414 "Also generates aggregated markdown files at the monorepo root:"; 415 415 `I ("CHANGES.md", "Aggregated weekly changelog"); ··· 426 426 "Repositories with no user-facing changes will have blank entries \ 427 427 (empty summary and changes) rather than 'no changes' text."; 428 428 `P 429 - "With --aggregate, also generates a structured JSON file suitable for \ 430 - the poe Zulip bot broadcasting system."; 429 + "When using --daily, an aggregated JSON file is generated by default \ 430 + for the poe Zulip bot broadcasting system. Use --no-aggregate to skip."; 431 + `P 432 + "If a per-repo-per-day JSON file already exists for a past day, that \ 433 + repo is skipped for that day to avoid redundant Claude API calls."; 431 434 ] 432 435 in 433 436 let info = Cmd.info "changes" ~doc ~man in ··· 451 454 let doc = "Preview changes without writing files" in 452 455 Arg.(value & flag & info [ "dry-run"; "n" ] ~doc) 453 456 in 454 - let aggregate = 455 - let doc = "Also generate .changes/YYYYMMDD.json aggregated file (only with --daily)" in 456 - Arg.(value & flag & info [ "aggregate"; "a" ] ~doc) 457 + let no_aggregate = 458 + let doc = "Skip generating .changes/YYYYMMDD.json aggregated file (--daily generates it by default)" in 459 + Arg.(value & flag & info [ "no-aggregate" ] ~doc) 457 460 in 458 - let run config_file package daily weeks days history dry_run aggregate () = 461 + let run config_file package daily weeks days history dry_run no_aggregate () = 459 462 Eio_main.run @@ fun env -> 460 463 with_config env config_file @@ fun config -> 461 464 let fs = Eio.Stdenv.fs env in ··· 465 468 if daily then begin 466 469 (* Use 30 as default history for daily if not explicitly set *) 467 470 let history = if history = 12 then 30 else history in 471 + (* Aggregate by default for daily, unless --no-aggregate is passed *) 472 + let aggregate = not no_aggregate in 468 473 Monopam.changes_daily ~proc ~fs ~config ~clock ?package ~days ~history ~dry_run ~aggregate () 469 474 end 470 475 else ··· 484 489 Term.( 485 490 ret 486 491 (const run $ config_file_arg $ package_arg $ daily $ weeks $ days $ history $ dry_run 487 - $ aggregate $ logging_term)) 492 + $ no_aggregate $ logging_term)) 488 493 489 494 (* Main command group *) 490 495
+9
monopam/lib/changes.ml
··· 146 146 let daily_filename repo_name date = 147 147 repo_name ^ "-" ^ date ^ ".json" 148 148 149 + (* Check if daily file exists on disk *) 150 + let daily_exists ~fs ~monorepo ~date repo_name = 151 + let filename = daily_filename repo_name date in 152 + let file_path = Eio.Path.(fs / Fpath.to_string monorepo / ".changes" / filename) in 153 + match Eio.Path.kind ~follow:true file_path with 154 + | `Regular_file -> true 155 + | _ -> false 156 + | exception Eio.Io _ -> false 157 + 149 158 (* Load daily changes from .changes/<repo>-<date>.json in monorepo *) 150 159 let load_daily ~fs ~monorepo ~date repo_name = 151 160 let filename = daily_filename repo_name date in
+4
monopam/lib/changes.mli
··· 78 78 val save : fs:_ Eio.Path.t -> monorepo:Fpath.t -> changes_file -> (unit, string) result 79 79 (** [save ~fs ~monorepo cf] saves the changes file to .changes/<repo_name>.json. *) 80 80 81 + val daily_exists : fs:_ Eio.Path.t -> monorepo:Fpath.t -> date:string -> string -> bool 82 + (** [daily_exists ~fs ~monorepo ~date repo_name] checks if a daily changes file exists. 83 + @param date Date in YYYY-MM-DD format *) 84 + 81 85 val load_daily : fs:_ Eio.Path.t -> monorepo:Fpath.t -> date:string -> string -> (daily_changes_file, string) result 82 86 (** [load_daily ~fs ~monorepo ~date repo_name] loads daily changes from .changes/<repo_name>-<date>.json. 83 87 Returns an empty changes file if the file does not exist.
+24 -12
monopam/lib/monopam.ml
··· 1052 1052 | None -> now_ptime 1053 1053 in 1054 1054 let date = Changes.date_of_ptime day_time in 1055 + let is_today = day_offset = 0 in 1055 1056 1056 - (* Load existing daily changes from .changes/<repo>-<date>.json *) 1057 - match Changes.load_daily ~fs:fs_t ~monorepo ~date repo_name with 1058 - | Error e -> Error (Claude_error e) 1059 - | Ok changes_file -> 1060 - (* Skip if day already has an entry *) 1061 - if Changes.has_day changes_file ~date then begin 1062 - Log.info (fun m -> m " Day %s already has entry, skipping" date); 1063 - all_changes_files := changes_file :: !all_changes_files; 1064 - process_days (day_offset + 1) 1065 - end 1066 - else begin 1057 + (* For past days, skip if file exists at all (already analyzed) *) 1058 + (* For today, skip only if file has entries (may need to catch new commits) *) 1059 + let should_skip = 1060 + if is_today then 1061 + Changes.daily_exists ~fs:fs_t ~monorepo ~date repo_name && 1062 + (match Changes.load_daily ~fs:fs_t ~monorepo ~date repo_name with 1063 + | Ok cf -> Changes.has_day cf ~date 1064 + | Error _ -> false) 1065 + else 1066 + Changes.daily_exists ~fs:fs_t ~monorepo ~date repo_name 1067 + in 1068 + if should_skip then begin 1069 + Log.info (fun m -> m " Day %s already processed, skipping" date); 1070 + (match Changes.load_daily ~fs:fs_t ~monorepo ~date repo_name with 1071 + | Ok cf -> all_changes_files := cf :: !all_changes_files 1072 + | Error _ -> ()); 1073 + process_days (day_offset + 1) 1074 + end 1075 + else 1076 + (* Load existing daily changes from .changes/<repo>-<date>.json *) 1077 + match Changes.load_daily ~fs:fs_t ~monorepo ~date repo_name with 1078 + | Error e -> Error (Claude_error e) 1079 + | Ok changes_file -> 1067 1080 (* Get commits for this day *) 1068 1081 let since = date ^ " 00:00:00" in 1069 1082 let until = date ^ " 23:59:59" in ··· 1143 1156 process_days (day_offset + 1) 1144 1157 end 1145 1158 end 1146 - end 1147 1159 end 1148 1160 in 1149 1161 match process_days 0 with
+2 -2
monopam/lib_changes/query.ml
··· 44 44 let buf = Buffer.create 1024 in 45 45 if include_date then begin 46 46 match date with 47 - | Some d -> Buffer.add_string buf (Printf.sprintf "## Changes for %s\n\n" d) 48 - | None -> Buffer.add_string buf "## Recent Changes\n\n" 47 + | Some d -> Buffer.add_string buf (Printf.sprintf "Updates for %s:\n\n" d) 48 + | None -> Buffer.add_string buf "Recent updates:\n\n" 49 49 end; 50 50 (* Group by change type *) 51 51 let by_type = [
+19 -3
poe/lib/handler.ml
··· 111 111 Log.info (fun m -> m "Claude response: %s" response); 112 112 Zulip_bot.Response.reply response 113 113 114 - let is_admin config email = 115 - List.mem email config.Config.admin_emails 114 + let is_admin config ~storage msg = 115 + let sender_id = Zulip_bot.Message.sender_id msg in 116 + let client = Zulip_bot.Storage.client storage in 117 + try 118 + let user = Zulip.Users.get_by_id client ~user_id:sender_id () in 119 + let delivery_email = Zulip.User.delivery_email user in 120 + let email = Zulip.User.email user in 121 + (* Check both delivery_email (actual email) and email (Zulip internal) *) 122 + let emails_to_check = 123 + match delivery_email with 124 + | Some de -> [ de; email ] 125 + | None -> [ email ] 126 + in 127 + List.exists (fun e -> List.mem e config.Config.admin_emails) emails_to_check 128 + with _ -> 129 + (* Fallback to sender_email from message if API call fails *) 130 + let sender_email = Zulip_bot.Message.sender_email msg in 131 + List.mem sender_email config.Config.admin_emails 116 132 117 133 let make_handler env config = 118 134 fun ~storage ~identity msg -> ··· 131 147 | Commands.Broadcast -> 132 148 Broadcast.run ~fs:env.fs ~storage ~config 133 149 | Commands.Admin cmd -> 134 - if is_admin config sender_email then 150 + if is_admin config ~storage msg then 135 151 Zulip_bot.Response.reply (Admin.handle ~storage cmd) 136 152 else 137 153 Zulip_bot.Response.reply "Admin commands require authorization. Contact an admin to be added to the admin_emails list."