<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Agnel Nieves - Claude Code</title>
    <link>https://agnelnieves.com/blog/tag/claude-code</link>
    <description>Blog posts on Claude Code by Agnel Nieves.</description>
    <language>en-US</language>
    <lastBuildDate>Fri, 18 Sep 2026 17:57:04 GMT</lastBuildDate>
    <atom:link href="https://agnelnieves.com/blog/tag/claude-code/feed.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title><![CDATA[Connecting Claude to Google Ads and GA4 via MCP (A Live Agency Audit)]]></title>
      <link>https://agnelnieves.com/blog/connecting-claude-to-google-ads-and-ga4-via-mcp</link>
      <guid isPermaLink="true">https://agnelnieves.com/blog/connecting-claude-to-google-ads-and-ga4-via-mcp</guid>
      <description><![CDATA[I wired Claude to a client's Google Ads and GA4 via MCP servers, then ran a live 90 day audit in one session. Here is the setup and what fell out.]]></description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>I connected Claude (via Claude Code and Cursor) directly to a client&#39;s Google Ads account and GA4 property using two MCP servers: the official <code>googleads/google-ads-mcp</code> and <code>googleanalytics/google-analytics-mcp</code>. Both are read only, both are Python, both are maintained by Google.</p>
<p>Once they were wired up, I pointed Claude at the account and asked it to run a 90 day audit. It pulled live data via GAQL and the GA4 Data API, cross referenced it with the WordPress IDX plugin source in the same repo, and produced a 119 KB HTML report ranking 5 critical issues, 10 high priority opportunities, and a 30/60/90 day roadmap. Total active build time, including the setup itself: about three hours. The output is hosted at <a href="/lyfe-realty-audit-report.html">/lyfe-realty-audit-report.html</a> if you want to see what falls out of this pattern on a small, real account.</p>
<p>This post is the runbook for anyone who wants to do the same thing on an agency account. It covers what to install, the two gotchas that will eat your first afternoon (the Google Ads developer token tier and the GA4 service account email bug), and how to structure the repo so a fresh Claude session can run the audit again next month without re asking you for IDs.</p>
<h2>Why MCP, not CSV exports</h2>
<p>The first instinct for an ad hoc audit is to download Search Terms, Negative Keywords, and a few GA4 reports as CSVs and hand them to Claude. That works once. It does not work as a workflow.</p>
<p>The MCP approach is different in three ways that compound:</p>
<ol>
<li><strong>It is live.</strong> The data Claude is reasoning over is the same data the dashboard shows you, refreshed at query time. There is no &quot;I exported this yesterday, has anything changed?&quot; lag.</li>
<li><strong>It is queryable.</strong> Claude can decide what fields to pull, what date ranges to use, and what segments to slice by. If a finding raises a follow up question, the follow up is one tool call away, not &quot;go back to the UI and re export.&quot;</li>
<li><strong>It is reusable.</strong> Same MCP, same auth, different client. Once you have set it up for one account, every future account is a <code>claude mcp add-json</code> away.</li>
</ol>
<p>The cost is a one time setup that has some sharp edges. The rest of this post is about those edges.</p>
<h2>The two servers</h2>
<p>Both are official Google projects. Both were pushed within the last week as of writing. Both are exposed as Python entry points you can install with <a href="https://pipx.pypa.io/">pipx</a>.</p>
<table>
<thead>
<tr>
<th>MCP</th>
<th>Repo</th>
<th>Auth</th>
<th>Surface</th>
</tr>
</thead>
<tbody><tr>
<td>Google Ads</td>
<td><a href="https://github.com/googleads/google-ads-mcp"><code>googleads/google-ads-mcp</code></a></td>
<td>OAuth refresh token + developer token + <code>login_customer_id</code> for MCC</td>
<td><code>search</code>, <code>get_resource_metadata</code>, <code>list_accessible_customers</code></td>
</tr>
<tr>
<td>GA4</td>
<td><a href="https://github.com/googleanalytics/google-analytics-mcp"><code>googleanalytics/google-analytics-mcp</code></a></td>
<td>Service account JSON, Viewer on the GA4 property</td>
<td><code>run_report</code>, <code>run_conversions_report</code>, <code>run_funnel_report</code>, <code>run_realtime_report</code>, <code>get_account_summaries</code>, <code>get_property_details</code>, <code>list_google_ads_links</code></td>
</tr>
</tbody></table>
<p>I also kept <a href="https://github.com/cohnen/mcp-google-ads"><code>cohnen/mcp-google-ads</code></a> on standby as a backup. The official server has an open bug where its <code>search</code> tool rejects array parameters, so for any query that needs multiple values you either pass them one at a time or fall back to the community fork.</p>
<h2>Set GA4 up first</h2>
<p>GA4 is the fast half. No developer tokens, no Google approval queues, no OAuth dance. You can be running queries in under an hour.</p>
<p>The steps:</p>
<ol>
<li><p>In your Google Cloud project, enable the Google Analytics Admin API and the Google Analytics Data API.</p>
</li>
<li><p>Create a service account, give it no roles inside GCP itself (it does not need any), and download a JSON key.</p>
</li>
<li><p>Grant the service account <strong>Viewer</strong> access on the GA4 property. This is the step where you will hit the bug below.</p>
</li>
<li><p>Install the MCP: <code>pipx install analytics-mcp</code>.</p>
</li>
<li><p>Wire it into Claude Code at user scope so it follows you across projects:</p>
<pre><code class="language-bash">claude mcp add-json --scope user google-analytics &#39;{
  &quot;command&quot;: &quot;/Users/you/.local/bin/google-analytics-mcp&quot;,
  &quot;env&quot;: {
    &quot;GOOGLE_APPLICATION_CREDENTIALS&quot;: &quot;/Users/you/.gcp-keys/ga4-service-account.json&quot;
  }
}&#39;
</code></pre>
</li>
<li><p>Restart <code>claude</code>. Smoke test with <code>claude mcp list</code>. You should see <code>google-analytics: ✓ Connected</code>.</p>
</li>
</ol>
<h3>The GA4 UI bug (Property Access Management)</h3>
<p>When you try to add the service account email under GA4 Admin → Property Access Management, the form will reject it with <strong>&quot;This email doesn&#39;t match a Google Account.&quot;</strong> This is a long standing bug. The form refuses to accept emails ending in <code>.iam.gserviceaccount.com</code> even though the underlying API accepts them just fine.</p>
<p>The workaround is to call the Admin API directly. Pick up a fresh access token via the <a href="https://developers.google.com/oauthplayground/">OAuth Playground</a> (scoped to <code>https://www.googleapis.com/auth/analytics.manage.users</code>) and POST the access binding:</p>
<pre><code class="language-bash">curl -X POST \
  &quot;https://analyticsadmin.googleapis.com/v1alpha/properties/YOUR_PROPERTY_ID/accessBindings&quot; \
  -H &quot;Authorization: Bearer YOUR_TOKEN&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{
    &quot;user&quot;: &quot;ga4-mcp-service-account@your-project.iam.gserviceaccount.com&quot;,
    &quot;roles&quot;: [&quot;predefinedRoles/viewer&quot;]
  }&#39;
</code></pre>
<p>If it returns a <code>name</code> field, the binding is live. Reload the UI and you will now see the service account in the access list. The MCP can authenticate against the property starting on its next call.</p>
<h2>Then set up Google Ads</h2>
<p>Google Ads is the slow half. The most common failure mode is people thinking they are blocked by setup complexity when they are actually blocked by their <strong>developer token tier</strong>. Fix that first.</p>
<h3>Step 1: Confirm your developer token tier</h3>
<p>In your Google Ads manager account (MCC), go to <strong>Tools → API Center</strong>. There are four tiers:</p>
<table>
<thead>
<tr>
<th>Tier</th>
<th>What it lets you do</th>
</tr>
</thead>
<tbody><tr>
<td>Test</td>
<td>Only query Google Ads test accounts. Production accounts return <code>developer token only approved for use with test accounts</code>.</td>
</tr>
<tr>
<td>Explorer</td>
<td>Query production accounts, up to 2,880 operations per day. Auto issued on apply, usually within 24 hours.</td>
</tr>
<tr>
<td>Basic</td>
<td>Higher quota, full production. Needs a written use case review by Google. Usually 1 to 3 business days.</td>
</tr>
<tr>
<td>Standard</td>
<td>Highest quota, for tool builders. Heavier review.</td>
</tr>
</tbody></table>
<p>For an audit workflow on a few client accounts, <strong>Explorer is enough</strong>. If you only have Test, the first real query you run on the client account will fail with the exact error string above. Apply for Explorer before you do anything else.</p>
<h3>Step 2: Create the OAuth client and mint a refresh token</h3>
<p>Inside the same GCP project, enable the Google Ads API and create an <strong>OAuth 2.0 Client ID</strong> of type <strong>Desktop</strong>. Download the <code>installed</code> block JSON. This is the client identity that the MCP will impersonate as you.</p>
<p>Run a one shot Python script to walk the consent flow and capture a long lived refresh token. The script is small enough to keep in the repo (mine lives at <code>scripts/mint_google_ads_refresh_token.py</code>):</p>
<pre><code class="language-python">from google_auth_oauthlib.flow import InstalledAppFlow
import json, argparse, pathlib

SCOPES = [&quot;https://www.googleapis.com/auth/adwords&quot;]

def main(client_secret, output):
    flow = InstalledAppFlow.from_client_secrets_file(client_secret, SCOPES)
    creds = flow.run_local_server(port=0, prompt=&quot;consent&quot;, access_type=&quot;offline&quot;)
    pathlib.Path(output).write_text(json.dumps({
        &quot;type&quot;: &quot;authorized_user&quot;,
        &quot;client_id&quot;: creds.client_id,
        &quot;client_secret&quot;: creds.client_secret,
        &quot;refresh_token&quot;: creds.refresh_token,
    }, indent=2))
    print(&quot;Wrote&quot;, output)

if __name__ == &quot;__main__&quot;:
    p = argparse.ArgumentParser()
    p.add_argument(&quot;--client-secret-file&quot;, required=True)
    p.add_argument(&quot;--output&quot;, required=True)
    main(p.parse_args().client_secret_file, p.parse_args().output)
</code></pre>
<p>A browser pops, you sign in with the agency account, click through the unverified app warning (Advanced → Go to ... (unsafe)), approve, and the script writes an <code>authorized_user</code> ADC JSON to disk. The MCP picks it up automatically.</p>
<h3>Step 3: Wire the MCP</h3>
<pre><code class="language-bash">pipx run --spec git+https://github.com/googleads/google-ads-mcp.git google-ads-mcp --help

claude mcp add-json --scope user google-ads &#39;{
  &quot;command&quot;: &quot;/Users/you/.local/bin/google-ads-mcp&quot;,
  &quot;env&quot;: {
    &quot;GOOGLE_APPLICATION_CREDENTIALS&quot;: &quot;/Users/you/.gcp-keys/google-ads-adc.json&quot;,
    &quot;GOOGLE_PROJECT_ID&quot;: &quot;your-gcp-project&quot;,
    &quot;GOOGLE_ADS_DEVELOPER_TOKEN&quot;: &quot;redacted&quot;,
    &quot;GOOGLE_ADS_LOGIN_CUSTOMER_ID&quot;: &quot;YOUR_MCC_ID_NO_DASHES&quot;
  }
}&#39;
</code></pre>
<p>The two details that catch people:</p>
<ul>
<li><strong><code>GOOGLE_ADS_LOGIN_CUSTOMER_ID</code> is the MCC ID, not the client account ID.</strong> Without it, the API routes the request to whatever account it can find directly and returns nothing for the client. With it, the MCC routes the call through and you get the client&#39;s data.</li>
<li><strong>IDs are stored without dashes in the API.</strong> The UI shows <code>767-971-9496</code>, the env var wants <code>7679719496</code>. When in doubt, strip dashes.</li>
</ul>
<h3>Step 4: Smoke test</h3>
<p>Restart <code>claude</code> and ask:</p>
<blockquote>
<p>Using the google-ads MCP, run <code>list_accessible_customers</code> and tell me what comes back.</p>
</blockquote>
<p>You should see your MCC and any other manager accounts you own. The <strong>client account does not appear in this list</strong> when accessed via MCC, which trips people up. To confirm the client is reachable, ask Claude to query the customer resource directly:</p>
<blockquote>
<p>Using the google-ads MCP for customer 7679719496 (login-customer-id 5754939043), select <code>customer.id, customer.descriptive_name, customer.currency_code, customer.time_zone</code> from <code>customer</code>.</p>
</blockquote>
<p>If you get back the client&#39;s name, you are wired.</p>
<h2>Make the setup outlast the session</h2>
<p>The whole point of MCP is that next month, when you want to run the same audit, you do not start from zero. The pattern I landed on: a single markdown runbook in the repo that any fresh Claude session can read to operate the account without re asking for IDs.</p>
<p>The structure is intentionally boring:</p>
<pre><code>docs/marketing-mcps-runbook.md
  1. Client and business context
  2. MCP servers that are wired up
  3. Account inventory (every ID you need)
  4. Credential storage (paths, never values)
  5. MCP client configuration
  6. How to call the MCPs from a chat
  7. How the auth pipeline actually works
  8. Audit playbook
  9. Known issues and gotchas
 10. Files in this repo that support this setup
 11. If you (a future agent) are starting fresh
</code></pre>
<p>Section 11 is the load bearing one. It tells the next agent: read sections 1, 8, and 9 first; confirm both MCPs are visible; run a smoke query against each before doing real work; if either smoke check fails, surface the error to the user instead of improvising. The whole file is about 400 lines. It is the difference between &quot;Claude needed me to dig up the MCC ID again&quot; and &quot;Claude opened the file, ran the audit, and produced a report.&quot;</p>
<p>Pair the runbook with a project level <code>CLAUDE.md</code> that points to it and any agent that opens the project will pick it up automatically. This is the same pattern I described in <a href="/blog/optimizing-your-website-for-ai-agents-and-llms">Optimizing Your Website for AI Agents and LLMs</a>: write the context once, in markdown, near the code, so both humans and agents read from the same source of truth.</p>
<h2>What a live audit actually looks like</h2>
<p>Once both MCPs are live and the runbook exists, the audit prompt is short:</p>
<blockquote>
<p>Read <code>docs/marketing-mcps-runbook.md</code>, run the audit playbook in section 8 against the last 90 days, and put the detailed output in an HTML report in this directory.</p>
</blockquote>
<p>What Claude does next:</p>
<ol>
<li><p><strong>Field discovery.</strong> The Google Ads <code>search</code> tool will only accept fields that exist for a given resource in the API version your MCP is pinned to. Claude calls <code>get_resource_metadata</code> on each resource before constructing the query. This avoids the common failure where you copy a GAQL snippet from documentation and find that one field has been renamed or moved.</p>
</li>
<li><p><strong>Parallel pulls.</strong> Claude fires the cheap reads in parallel: campaign settings, conversion actions, account level negatives, ad groups, ads, geo breakdown, device split, day of week. Most of the audit data is in your context within a minute.</p>
</li>
<li><p><strong>The expensive reads.</strong> Search terms and keyword views are the heavy queries. They blow past most agent context windows if you let them, so the MCP returns them as files on disk and the agent uses <code>jq</code> and Python to extract the slices it actually needs. This is built into the tool result protocol. You do not have to wire it.</p>
</li>
<li><p><strong>Cross referencing source.</strong> Because the audit is running inside the client&#39;s repo, Claude can also read the WordPress plugin source to confirm, for example, that the MLS feed is correctly scoped to one office, or that the IDX integration is not creating publicly indexable rental pages. The campaign data and the site code get reconciled in the same session.</p>
</li>
<li><p><strong>Report generation.</strong> I asked for the output as a single, standalone HTML file with inline CSS so it travels well over email and prints cleanly. Claude wrote about 1,500 lines of HTML and styled it as a proper audit deliverable.</p>
</li>
</ol>
<p>A representative GAQL query, the kind Claude runs without intervention once it knows the resource shape:</p>
<pre><code class="language-text">SELECT
  campaign.id,
  campaign.name,
  campaign.geo_target_type_setting.positive_geo_target_type,
  campaign.bidding_strategy_type,
  campaign_budget.amount_micros,
  metrics.cost_micros,
  metrics.clicks,
  metrics.conversions,
  metrics.search_impression_share,
  metrics.search_budget_lost_impression_share,
  metrics.search_rank_lost_impression_share
FROM campaign
WHERE segments.date BETWEEN &#39;2026-02-22&#39; AND &#39;2026-05-22&#39;
  AND campaign.status IN (&#39;ENABLED&#39;, &#39;PAUSED&#39;)
</code></pre>
<p>The MCP returns this as JSON, Claude correlates it with the rest of the pulls, and the findings start writing themselves.</p>
<h2>What fell out of one real account</h2>
<p>The account in this case was a residential real estate practice in Southwest Broward, FL. The trigger was the client complaining about renter calls coming through ads targeted at buyers and sellers. The full report lives at <a href="/lyfe-realty-audit-report.html">/lyfe-realty-audit-report.html</a>. The shape of the findings, without the full numbers, looked like this:</p>
<ul>
<li><strong>The original renter call problem was already closed at the campaign level.</strong> A paused Performance Max campaign, 92 negative keywords on the active Search campaign, presence only geo targeting, and a correctly scoped MLS feed had already shut the gap. The audit pivoted from defense to offense.</li>
<li><strong>The Buyers Search campaign was configured with <code>PRESENCE_OR_INTEREST</code> geo targeting</strong> instead of <code>PRESENCE</code>. Roughly 92% of its spend was going to people who searched about Southwest Broward, not people in it. 1,484 clicks in 90 days produced about 55 actual GA4 sessions. The 84% click to session gap was the smoking gun. Five minute fix.</li>
<li><strong>82% of paid keywords had Quality Score 0 or null.</strong> Sellers Search was losing 62% of available impression share to ad rank, only 4% to budget. The bottleneck was creative and landing page relevance, not spend.</li>
<li><strong>The lead form had a 94% abandonment rate</strong> on mobile. 66 form starts produced 4 form completions. The single highest leverage point in the funnel and not visible from inside Google Ads.</li>
<li><strong>Phone clicks were happening (27 in 90 days, recorded in GA4) but the matching Google Ads conversion action was HIDDEN.</strong> The bidder was blind to roughly a third of the real lead volume.</li>
<li><strong>Both campaigns ran Maximize Conversions with no Target CPA.</strong> The bidder had no efficiency anchor on a five month old account that had earned the right to one.</li>
</ul>
<p>The cumulative projection, again on the same budget: visible conversions per month from 7 to 25 or 40, CPA from $138 to $25 to $40. The audit ranks every recommendation by dollar impact, effort, and owner so the marketing team can sequence the work without re reading the source data.</p>
<p>The interesting thing is that none of those findings required cleverness from the model. They required the model having access to the right data, knowing where to look, and being willing to cross reference fields most analysts skim past (the <code>location_type</code> on <code>geographic_view</code>, the <code>status</code> field on <code>conversion_action</code>, the form_start vs form_submission gap in GA4 events). MCP plus a decent runbook gets you that for the same effort as a single one off Looker Studio dashboard.</p>
<h2>Lessons from the build</h2>
<p>A few things I would tell my one day earlier self:</p>
<ol>
<li><strong>Set up GA4 first.</strong> It is the fast half and it gives you something to play with while you wait on the Google Ads developer token review.</li>
<li><strong>Apply for Explorer tier on day one.</strong> Even if your token comes back as Test, you can still rehearse the rest of the setup against a test account while the Explorer review runs.</li>
<li><strong>Keep credentials in <code>~/.gcp-keys/</code> with <code>chmod 600</code>, never in the repo.</strong> The runbook can describe the paths and roles, but the JSON files belong outside the repo. The Google Ads ADC JSON in particular is effectively a production password for every linked client account.</li>
<li><strong>Use <code>pipx run --spec git+...</code></strong> to install the official Google Ads MCP. It is not on PyPI yet. The community fork at <code>cohnen/mcp-google-ads</code> is on PyPI and useful as a fallback.</li>
<li><strong>Wire MCPs at <code>user</code> scope, not <code>project</code> scope.</strong> Agency work spans many directories. <code>claude mcp add-json --scope user</code> makes the MCPs visible from anywhere.</li>
<li><strong>Write the runbook the day you do the setup.</strong> Not later. Not &quot;when I have time.&quot; The 30 minutes you spend documenting the IDs and the gotchas will save you (and the next agent) an afternoon every time you come back.</li>
<li><strong>Read tool results as data, not as conclusions.</strong> The MCP will happily return field values that look right but are stale, or null fields the API simply does not surface in this version. When something looks off, check the resource metadata before changing anything in the account.</li>
</ol>
<h2>What I would build next</h2>
<p>Two things are sitting on the list:</p>
<ul>
<li><strong>A Meta Ads MCP.</strong> Facebook ads are 30% of the same client&#39;s paid mix and currently convert better than Google. There is no first party Meta MCP yet, but the Marketing API is well covered and a thin wrapper would be a couple of evenings.</li>
<li><strong>A &quot;monthly delta&quot; prompt.</strong> Same runbook, but instead of a 90 day audit it diffs the current month against the prior. Lower cognitive load for the agent, faster to run, and natural to schedule.</li>
</ul>
<p>If you have done a similar build, or you are running into one of the gotchas above and stuck, I would like to compare notes. <a href="/connect">Let&#39;s connect</a>.</p>
]]></content:encoded>
      <pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
      <author>agnel@agnelnieves.com (Agnel Nieves)</author>
      <dc:creator><![CDATA[Agnel Nieves]]></dc:creator>
      <category>Claude Code</category>
      <category>MCP</category>
      <category>Google Ads</category>
      <category>Google Analytics</category>
      <category>Marketing</category>
    </item>
    <item>
      <title><![CDATA[We Found a Hidden Pet System in Claude Code's Leaked Source and Shipped It Overnight]]></title>
      <link>https://agnelnieves.com/blog/we-found-a-hidden-pet-system-in-claude-codes-leaked-source-and-shipped-it-overnight</link>
      <guid isPermaLink="true">https://agnelnieves.com/blog/we-found-a-hidden-pet-system-in-claude-codes-leaked-source-and-shipped-it-overnight</guid>
      <description><![CDATA[Anthropic's Claude Code source leaked via npm. Buried inside: a pixel pet system called Buddy. We turned it into an open-source toy in a day.]]></description>
      <content:encoded><![CDATA[<p>On March 30th, 2026, Anthropic accidentally shipped a 59.8 MB source map file inside their Claude Code npm package. Within hours, the entire 512,000-line TypeScript codebase was mirrored across GitHub and picked apart by thousands of developers. Buried inside that code, alongside feature flags for autonomous agents and undercover commit modes, was something nobody expected: a fully built virtual pet system called <strong>Buddy</strong>.</p>
<p>My co-founder <a href="https://x.com/peronif5">peroni</a> and I did the only sensible thing. We shipped it.</p>
<TwitterEmbed>
<blockquote className="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">We all know what happened yday with Claude Code. Buried in the source: a hidden pet system called &quot;Buddy.&quot; Every user gets a unique pixel creature based on their ID. Deterministic, same hash, same buddy, every time. So <a href="https://twitter.com/peronif5?ref_src=twsrc%5Etfw">@peronif5</a> and I did the most sensible thing... Shipped it.… <a href="https://t.co/YE4ZSEIXq7">pic.twitter.com/YE4ZSEIXq7</a></p>&mdash; Agnel (🇵🇷) (@agnelnieves) <a href="https://twitter.com/agnelnieves/status/2039311800005525807?ref_src=twsrc%5Etfw">April 1, 2026</a></blockquote>
</TwitterEmbed><h2>What Happened with the Claude Code Leak</h2>
<p>If you missed it, here&#39;s the short version. Chaofan Shou (<a href="https://x.com/Fried_rice">@Fried_rice</a>) noticed that version 2.1.88 of the <code>@anthropic-ai/claude-code</code> package on npm included an unminified source map — <code>cli.js.map</code> — containing the full, readable TypeScript source. By 4:23 AM ET, it was public. By noon, the internet had catalogued every hidden feature, internal codename, and unreleased capability Anthropic had been quietly building.</p>
<p>Among the bigger discoveries:</p>
<ul>
<li><strong>KAIROS</strong> — an always-on daemon mode that lets Claude Code operate as a persistent background agent, watching, logging, and acting without waiting for user input</li>
<li><strong>Undercover mode</strong> — auto-activated for Anthropic employees on public repos, stripping AI attribution from commits</li>
<li><strong>44 feature flags</strong> covering unreleased functionality</li>
<li>And tucked away, a complete companion pet system called <strong>Buddy</strong></li>
</ul>
<h2>The Buddy System: A Pixel Pet for Every User</h2>
<p>The Buddy system was fully implemented. Every Claude Code user was supposed to get a unique pixel creature generated deterministically from their user ID. Same hash, same buddy, every time. The code included 18 species across rarity tiers, stat generation, personality descriptions — the whole gacha experience, just waiting to be turned on.</p>
<p>It was the kind of detail that makes you smile. In a tool built for productivity and code generation, someone at Anthropic took the time to build a pet system. A little pixel friend that lives in your terminal. That&#39;s the kind of craft and whimsy that makes developer tools memorable.</p>
<p>The moment I saw it, I knew what we had to do.</p>
<h2>From Discovery to Deploy in a Day</h2>
<p>Peroni and I have a rhythm. We spot something interesting, we build. No planning committee, no Jira tickets, no &quot;let&#39;s circle back Monday.&quot; As a design engineer with 15+ years of shipping products, I&#39;ve learned that the best side projects are the ones you can&#39;t <em>not</em> build. This was one of those.</p>
<p>We built <a href="https://www.claudebuddy.me/">Claude Buddy</a> — a web app that lets anyone generate their own pixel companion. Type your name or your Claude Code user ID, and watch it draw your buddy pixel by pixel, complete with retro CRT animations and pop sounds.</p>
<p>Here&#39;s what we shipped:</p>
<ul>
<li><strong>12 unique pixel art species</strong> across 5 rarity tiers — from common Blobbits to the legendary Nebulynx (3% chance)</li>
<li><strong>Deterministic generation</strong> — same name always produces the same buddy, making them feel like <em>yours</em></li>
<li><strong>Shiny variants</strong> with a 5% drop rate, because of course</li>
<li><strong>Buddy stats</strong> — Vibe, Chaos, Focus, and Luck, each randomly rolled but consistent to your hash</li>
<li><strong>One-command terminal install</strong> — <code>curl</code> a script and your buddy appears in your Claude Code statusline</li>
<li><strong>Social sharing</strong> — download as PNG, share via URL, generate QR codes, post to X/LinkedIn with pre-populated text</li>
</ul>
<p>The whole thing runs on Next.js 15, renders sprites on HTML5 Canvas, and uses a Mulberry32 PRNG seeded by a DJB2 hash of your input. No backend, no database, no authentication. Pure deterministic fun.</p>
<h2>Why Build This?</h2>
<p>Partly because it&#39;s fun. Partly because of <a href="/blog/ai-native-design-gap-from-static-to-dynamic-experiences">how I approach creative work</a> — I try to ship a hackathon-style project every quarter to stay sharp and experiment with new patterns. But mostly because I think the Buddy system represents something important about how we relate to our tools.</p>
<p>Developer tools don&#39;t have to be purely utilitarian. The best ones have personality. They reward curiosity. They make you <em>want</em> to open the terminal. A pixel pet that lives next to your cursor won&#39;t make you a better programmer, but it might make the work feel a little less solitary.</p>
<p>Anthropic clearly felt the same way — they built the whole thing, ready to ship. We just opened the door a little early.</p>
<h2>How the Generation Algorithm Works</h2>
<p>For the technically curious, the generation is straightforward:</p>
<ol>
<li>Take the input string (name or user ID), lowercase and trim it</li>
<li>Salt it with a fixed string to avoid collisions</li>
<li>Run a DJB2 hash to convert it to a numeric seed</li>
<li>Feed that seed into a Mulberry32 PRNG</li>
<li>Roll for species (weighted by rarity tier probabilities)</li>
<li>Roll for shiny status (5% chance)</li>
<li>Generate four stats between 1 and 99</li>
<li>Select a soul description from the species pool</li>
</ol>
<p>Same input, same seed, same rolls, same buddy. Every time. No server involved.</p>
<h2>It&#39;s Open Source</h2>
<p>The entire project is <a href="https://github.com/basement-browser/claude-buddy">open source on GitHub</a>. Built by the <a href="https://basementbrowser.com">Basement</a> team. Fork it, remix it, hatch your own buddy.</p>
<p>If you want to see what came out of this, check the <a href="https://x.com/agnelnieves/status/2039311800005525807">thread on X</a> where we announced it. And if you&#39;re building with Claude Code, maybe your buddy is already waiting — just type your name and find out.</p>
<hr>
<p><em>Have thoughts on this or want to collaborate on something weird? <a href="/connect">Let&#39;s connect</a>.</em></p>
]]></content:encoded>
      <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
      <author>agnel@agnelnieves.com (Agnel Nieves)</author>
      <dc:creator><![CDATA[Agnel Nieves]]></dc:creator>
      <category>AI</category>
      <category>Claude Code</category>
      <category>Open Source</category>
      <category>Side Projects</category>
      <category>Creative Coding</category>
    </item>
  </channel>
</rss>