Nightly competitor scan → Slack digest
Wake up to a 5-bullet diff of every competitor page that changed overnight.
- •Linear shipped a new pricing tier ($49/mo Team) — added seat-based caps.
- •Clerk rewrote the homepage hero; H1 changed to "Ship in a weekend".
- •PlanetScale published a changelog: 4 new MCP tools, incl. `workspace.search`.
- •2 competitors added an "AI" nav link since yesterday.
- •No pricing changes across 14 tracked pages.
Manually checking competitor pricing, changelogs, and landing pages is a tax. Most days nothing meaningful changes, but you still have to look.
A single Slack message every morning. If competitors shipped something, you see it in the first bullet. If they didn't, the message says so in one line.
Ingredients & skills
- ANTHROPIC_API_KEY
- SLACK_WEBHOOK_URL
- Anthropic
- Slack
- fetch-mcp
How it works
A cron-scheduled Claude agent fetches a list of competitor URLs, diffs them against yesterday's snapshot, and posts a five-bullet summary to Slack at 07:00 in your timezone.
1 — Create the locker
In ClaudeLoops, create a locker called `competitor-radar` and add the two secrets. They are scoped to this stack only.
locker create competitor-radar
locker set competitor-radar ANTHROPIC_API_KEY=sk-ant-...
locker set competitor-radar SLACK_WEBHOOK_URL=https://hooks.slack.com/...2 — Drop in the agent
Single file. Reads `competitors.json`, fetches each page through the fetch-mcp tool, asks Claude to diff against the previous snapshot, posts the result.
import Anthropic from "@anthropic-ai/sdk";
import { readFile, writeFile } from "node:fs/promises";
const client = new Anthropic();
const targets: string[] = JSON.parse(await readFile("competitors.json", "utf8"));
const snapshots = Object.fromEntries(
await Promise.all(targets.map(async (url) => [url, await (await fetch(url)).text()]))
);
const previous = JSON.parse(await readFile("snapshot.json", "utf8").catch(() => "{}"));
const msg = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 800,
messages: [{
role: "user",
content: `Diff today vs yesterday for each URL. 5 bullets max, only meaningful changes.\n\nTODAY:\n${JSON.stringify(snapshots)}\n\nYESTERDAY:\n${JSON.stringify(previous)}`,
}],
});
await writeFile("snapshot.json", JSON.stringify(snapshots));
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
body: JSON.stringify({ text: (msg.content[0] as any).text }),
});3 — Schedule it
Use the platform scheduler — no separate cron host. The locker is bound at runtime.
locker schedule competitor-radar agent.ts --cron '0 7 * * *' --tz America/Los_AngelesThe button above runs the same command with your saved config. This is the raw CLI form.
npx claudeloops deploy competitor-radar