Claude Skills Guide

Chrome Extension Daily Standup Automation: A Practical Guide

Daily standups are essential for team coordination, but the ritual of documenting what you did yesterday, what you’re doing today, and any blockers can become repetitive. A well-built Chrome extension can automate significant portions of this workflow, pulling data from your development tools and generating standup messages automatically.

This guide walks you through building a Chrome extension that collects activity data from GitHub, Jira, Linear, and other tools, then formats it into a clean standup update.

Understanding the Architecture

A standup automation extension operates across several components:

The key challenge is accessing data from multiple sources without requiring you to manually input credentials for each service. We’ll use OAuth flows where supported and API tokens for services that require them.

Setting Up Your Extension Project

Every Chrome extension needs a manifest file. For a standup automation tool, you’ll need to request appropriate permissions:

{
  "manifest_version": 3,
  "name": "Standup Automator",
  "version": "1.0",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "https://github.com/*",
    "https://api.github.com/*",
    "https://*.linear.app/*"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  }
}

The host permissions allow your extension to interact with GitHub and Linear APIs directly from the background worker.

Extracting Activity Data from GitHub

GitHub provides a clean API for fetching your recent contributions. Here’s how to retrieve commits and pull requests from the past 24 hours:

// background.js - Fetch GitHub activity
async function getGitHubActivity(token, username) {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  const dateStr = yesterday.toISOString().split('T')[0];
  
  const response = await fetch(
    `https://api.github.com/users/${username}/events?per_page=50`,
    {
      headers: {
        'Authorization': `token ${token}`,
        'Accept': 'application/vnd.github.v3+json'
      }
    }
  );
  
  const events = await response.json();
  
  const pushEvents = events.filter(e => 
    e.type === 'PushEvent' && e.created_at.startsWith(dateStr)
  );
  
  const prEvents = events.filter(e => 
    e.type === 'PullRequestEvent' && e.created_at.startsWith(dateStr)
  );
  
  return {
    commits: pushEvents.map(e => ({
      repo: e.repo.name,
      message: e.payload.commits[0].message,
      url: `https://github.com/${e.repo.name}/commit/${e.payload.commits[0].sha.substring(0, 7)}`
    })),
    prs: prEvents.map(e => ({
      action: e.payload.action,
      title: e.payload.pull_request.title,
      url: e.payload.pull_request.html_url,
      number: e.payload.number
    }))
  };
}

This function retrieves the events and filters them to show only those from yesterday. You can adapt the date range to match your team’s standup cadence.

Integrating with Linear for Task Tracking

Linear uses GraphQL for its API. Here’s how to fetch your assigned issues:

// background.js - Fetch Linear issues
async function getLinearIssues(apiKey) {
  const query = `
    query {
      issues(filter: { assignee: { isMe: true } }, first: 10) {
        nodes {
          title
          state { name }
          priority
          updatedAt
        }
      }
    }
  `;
  
  const response await fetch('https://api.linear.app/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': apiKey
    },
    body: JSON.stringify({ query })
  });
  
  const { data } = await response.json();
  return data.issues.nodes;
}

The key insight here is filtering for issues assigned to you that were recently updated. This gives you a solid foundation for the “what I’m working on today” portion of your standup.

Building the Standup Generator

Once you have data from multiple sources, you need to combine and format it:

// background.js - Generate standup message
function generateStandupMessage(data) {
  const lines = [];
  
  // Yesterday's accomplishments
  lines.push('## Yesterday');
  if (data.github.commits.length > 0) {
    lines.push('- Commits:');
    data.github.commits.forEach(commit => {
      lines.push(`  - ${commit.message} (${commit.url})`);
    });
  }
  if (data.github.prs.length > 0) {
    lines.push('- Pull Requests:');
    data.github.prs.forEach(pr => {
      lines.push(`  - [${pr.title} #${pr.number}](${pr.url}) - ${pr.action}`);
    });
  }
  
  // Today's plan
  lines.push('\n## Today');
  if (data.linear.issues.length > 0) {
    data.linear.issues.forEach(issue => {
      lines.push(`- ${issue.title} (${issue.state.name})`);
    });
  }
  
  // Blockers
  lines.push('\n## Blockers');
  lines.push('- None');
  
  return lines.join('\n');
}

This generates a markdown-formatted standup that works well with Slack, Teams, or any markdown-supported platform.

Creating the Popup Interface

Your popup needs to be simple and functional:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 320px; padding: 16px; font-family: system-ui, -apple-system, sans-serif; }
    .btn {
      width: 100%; padding: 10px; margin: 8px 0;
      background: #2563eb; color: white;
      border: none; border-radius: 6px; cursor: pointer;
      font-weight: 600;
    }
    .btn:hover { background: #1d4ed8; }
    .btn:disabled { background: #94a3b8; cursor: not-allowed; }
    textarea {
      width: 100%; height: 200px;
      padding: 8px; margin: 8px 0;
      border: 1px solid #e2e8f0; border-radius: 6px;
      font-size: 12px; font-family: monospace;
    }
    .status { font-size: 12px; color: #64748b; margin-bottom: 8px; }
  </style>
</head>
<body>
  <h3>Daily Standup</h3>
  <div class="status" id="status">Ready to generate</div>
  <textarea id="output" placeholder="Click generate to create your standup..."></textarea>
  <button class="btn" id="generate">Generate Standup</button>
  <button class="btn" id="copy" style="background: #059669;">Copy to Clipboard</button>
  <script src="popup.js"></script>
</body>
</html>

The popup provides straightforward controls: generate the standup from your activity data, then copy it to your clipboard for pasting into Slack or your team’s standup channel.

Handling Authentication Securely

Never store API tokens in your extension’s source code. Instead, use Chrome’s secure storage:

// background.js - Secure token storage
chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'sync' && changes.apiKeys) {
    // Validate tokens before use
    const keys = changes.apiKeys.newValue;
    if (keys.github && keys.github.length > 10) {
      console.log('GitHub token configured');
    }
  }
});

function getStoredToken(keyName) {
  return new Promise((resolve) => {
    chrome.storage.sync.get(keyName, (result) => {
      resolve(result[keyName] || null);
    });
  });
}

Users should configure their API tokens once through the extension’s options page, and those tokens persist across browser sessions through Chrome’s sync storage.

Performance and Rate Limiting

APIs have rate limits, and you don’t want to hit them during standup generation:

// background.js - Cache with expiration
const cache = new Map();

async function cachedFetch(url, options, cacheKey, ttlMinutes = 15) {
  const cached = cache.get(cacheKey);
  
  if (cached && Date.now() - cached.timestamp < ttlMinutes * 60 * 1000) {
    return cached.data;
  }
  
  const response = await fetch(url, options);
  const data = await response.json();
  
  cache.set(cacheKey, { data, timestamp: Date.now() });
  return data;
}

This caching layer ensures that repeated standup generations don’t trigger unnecessary API calls. The 15-minute TTL balances fresh data with API efficiency.

Deployment and Testing

Before distributing your extension, test it thoroughly:

  1. Load unpacked in Chrome via chrome://extensions/ with Developer mode enabled
  2. Verify each integration works with your actual accounts
  3. Test the popup on different screen sizes
  4. Check that copy functionality works across browsers

When ready, you can publish through the Chrome Web Store. Ensure your listing clearly explains what data your extension accesses and why.

Conclusion

Automating daily standups through a Chrome extension combines browser APIs, external service integrations, and thoughtful UI design. The core implementation involves retrieving activity data from your development tools, formatting it into a consistent structure, and providing easy copy-paste functionality.

Start with one integration—GitHub or Linear—and expand from there. Power users will appreciate the ability to customize their standup format, while team leads benefit from consistent update quality across the team.

The time invested in building this tool pays back quickly when you eliminate the manual effort of composing daily standups while ensuring accurate, data-driven updates.


Built by theluckystrike — More at zovo.one