Adding Analytics to 9 Sites in One Session with Hermes Agent + GitHub Copilot

Chris Child | 2026-04-15 | 7 min read

I wanted analytics on all my active projects. Not the "set up a Google Analytics property and feel bad about cookies" kind — just lightweight, privacy-first page views. Tinylytics is perfect for this: one <script defer> tag, no cookies, no consent banners, GDPR-compliant out of the box.

The problem: I have 9 repos spanning 5 different tech stacks. Doing this manually would mean opening each repo, finding the right file, adding the tag, committing, pushing, opening a PR, reviewing, merging — nine times. That's the kind of task that sounds quick and ends up eating an afternoon.

Instead, I handed the whole thing to Hermes Agent and GitHub Copilot. The entire process — from "here are the repos" to "all merged" — happened in a single Telegram conversation.

The Setup

The 9 repos are a proper mixed bag:

  • React Router v7 apps: hardcopy-static, payuporgetshot.com, routinee, spanners, stubkit
  • Static React app: wordz
  • Jekyll GitHub Pages site: WearAmp
  • Plain HTML docs site: jigsaw
  • Single-page HTML site: boingfwip.com

Each one needs the Tinylytics script in a different place, in a different kind of file, following different conventions. The kind of variation that's trivial for a human to handle one at a time but tedious to handle nine times in a row.

Step 1: Hermes Investigates

I gave Hermes the list of repos and asked it to:

  1. Figure out the deployed domain for each
  2. Register a Tinylytics site for each domain
  3. Create a GitHub issue in each repo with the exact script tag and placement instructions

Domain discovery was the fun part. Hermes dug through wrangler.toml, package.json, config files, README.md, HTML titles, and subdomain patterns to work out where each repo actually lives.

Most were straightforward. A few needed detective work:

  • wordz turned out to be wordz.hardcopy.dev — buried in config files, not obvious from the repo name
  • jigsaw and WearAmp — no custom domain; Hermes correctly identified them as GitHub Pages sites at 03c.github.io/jigsaw/ and 03c.github.io/WearAmp/
  • spanners — nothing in the repo pointed to a domain. Hermes guessed spanners.dev based on the naming pattern (consistent with stubkit.dev) and flagged it as unconfirmed in the issue

Step 2: Registering Tinylytics Sites via Browser

Here's where it got creative. The Tinylytics API is read-only for site management — you can list sites but you can't create them via the API. So Hermes automated the browser directly: navigating to the dashboard, filling in the form, and saving. Nine times.

One quirk: the URL input field was doubling https:// when using standard browser automation fill methods. Hermes worked around it by setting the field value with JavaScript and dispatching DOM events manually, bypassing the browser's default input handling. Fragile? A little. But it worked.

The result: 9 sites registered, each with a unique embed ID.

Site Domain
hardcopy-static hardcopy.dev
payuporgetshot.com payuporgetshot.com
jigsaw 03c.github.io/jigsaw/
routinee routinee.app
boingfwip.com boingfwip.com
wordz wordz.hardcopy.dev
spanners spanners.dev
stubkit stubkit.dev
WearAmp 03c.github.io/WearAmp/

Step 3: Issues With Real Instructions

For each repo, Hermes created a GitHub issue titled "Add Tinylytics analytics script to all pages" containing:

  • The exact <script> tag with the correct embed ID for that site
  • Specific instructions on where to add it — not just "in the head" but which file, which component, which line

For the React Router v7 apps, it specified app/root.tsx inside the <head> in the Layout component. For the Jekyll site it pointed to docs/_layouts/default.html. For the plain HTML docs site, it noted that the tag needed adding to each .html file individually since there's no shared layout.

This specificity is what made the next step work so well.

One side note: Hermes created the issues via the GitHub REST API using curl rather than gh issue create — the CLI was timing out, so it adapted on the fly. That kind of resilience is underrated.

Step 4: Copilot Opens the PRs

I assigned all 9 issues to GitHub Copilot in the web UI, and within minutes it had opened draft PRs across all 9 repos. Every single one was correct first time.

Copilot handled the stack variation exactly right:

  • React Router v7 appsapp/root.tsx inside <head> in Layout
  • Static React apppublic/index.html
  • Jekyll GitHub Pagesdocs/_layouts/default.html (the shared layout, so it applies everywhere)
  • Plain HTML docs site → each individual .html file (8 files)
  • Single-page HTMLindex.html

The reason this went smoothly: the issues Hermes wrote were specific enough that Copilot didn't have to guess anything. Good issue writing is the single biggest factor in getting good results from AI code agents.

Step 5: Hermes Reviews and Merges

Back in the same Telegram conversation, I asked Hermes to review each PR and merge if it looked good. It:

  1. Pulled the diff for all 9 PRs
  2. Verified each script tag had the correct embed ID for that repo
  3. Confirmed placement was in the <head> at the right level
  4. Approved each PR with a review comment
  5. Promoted all 9 from draft to ready-for-review
  6. Merged all 9 — using rebase for hardcopy-static (which only allows rebase merges) and squash for the rest

Done. One conversation, nine repos, zero manual file edits.

Why This Worked

Hermes as orchestrator. It held the full context across all 9 repos simultaneously — which embed ID belongs to which repo, what the domain is, what stack each one uses. No copy-pasting between browser tabs, no spreadsheet to track which one you've done.

Copilot as implementer. Given a clear issue with the exact script tag and target file, Copilot's implementation was correct every time. It plays to its strength: reading instructions and writing focused, well-placed code changes.

The handoff pattern. Hermes wrote issues detailed enough for Copilot to act on autonomously. Copilot wrote code that Hermes could verify against a clear spec. Each tool stayed in its lane and did what it does best.

The Rough Edges

This wasn't completely frictionless. A few things tripped up the flow:

  • Tinylytics site creation isn't API-automatable. Browser automation worked but it's brittle — especially that URL field doubling issue. I'd love to see a proper write API for site management.
  • You can't assign Copilot via the API. GitHub silently ignores "assignees": ["copilot"] on PATCH requests. The web UI is currently the only way to assign issues to Copilot. Annoying when you're trying to automate the full loop.
  • Copilot opens PRs as drafts. Which is probably the right default, but it adds an extra step when you want to merge programmatically.
  • The spanners domain is still unconfirmed. spanners.dev was Hermes's best guess. Needs verifying.

What Is Tinylytics?

Quick shout-out since it's central to this whole post: Tinylytics is privacy-first analytics with no cookies, no fingerprinting, and no consent banners. It's a single <script defer> tag — that's the entire integration. It also does uptime monitoring and sends a weekly email digest. For side projects where you want signal without infrastructure, it's a great fit.

The Takeaway

The interesting thing here isn't that AI did the work — it's the orchestrator/implementer split.

Hermes handled the messy, cross-cutting coordination: investigating repos, registering accounts, writing specs, reviewing code, merging PRs. Copilot handled the focused implementation: reading an issue and writing the correct code change.

Neither tool could have done the whole job alone. Together, they turned a boring afternoon into a five-minute Telegram exchange.

If you have a batch of similar-but-not-identical changes across multiple repos, this pattern — AI orchestrator writes detailed issues, AI implementer opens PRs, orchestrator reviews and merges — scales surprisingly well. The key is writing issues that are specific enough to act on without interpretation. That's true whether your implementer is Copilot, a junior developer, or your future self at 4pm on a Friday.

Comments

Related Posts