Skip to content

Repository files navigation

Claude Code Learning Bot

A locally-run Discord bot: send it a Threads/Instagram link (post or Reel) and it uses headless Claude Code (claude -p) to fact-check the content and judge whether it's worth adopting — not limited to Claude Code itself, it can analyze dev tools and AI/academic news too — then walks you through the flow "experiment or file to knowledge base → adopt into your dev environment" via a Discord thread report and item-by-item decisions. Full spec: docs/SPEC.md.

1. What this is

  • You drop a Threads/IG link (or a screenshot) on the bot in Discord.
  • The bot first uses a headless Playwright browser to scrape the post body, comments, and post images, then calls claude -p in the background to do "cross-check against the source → produce a structured report". The report is delivered as an embed plus the full report split across messages in a thread under it, so it's readable on mobile too; you can keep typing in the thread to follow up (with the analysis kept in context).
  • Technical items get "✅ Adopt & experiment" — the bot creates a standalone mini-project under experiments/<slug>/ and actually tries it out; academic/pure-knowledge items get "📚 File to knowledge base" and are archived straight into data/knowledge/. Posts with multiple items use a dropdown to check off each one individually.
  • After an experiment finishes, you separately decide "📦 Adopt into dev environment" — only then will the bot touch your ~/.claude (it only ever suggests changes to settings.json and your existing rule files, never edits them automatically).

Project structure

src/
  index.js           entry point: Discord client, message/interaction routing (SPEC §5)
  pipeline.js         state machine: analyze -> report -> experiment -> result -> adopt
  claude-runner.js     core module: spawns `claude -p` (headless CLI) via child_process, parses results
  scraper.js           Playwright scraper: post body + comments + images + full-page screenshot (SPEC §3b)
  prompts.js           the analyze / experiment / adopt / profile / QA-followup prompts (kept in sync with SPEC §7)
  discord-ui.js         embed assembly, decision components (buttons/select menus), long-report chunking (SPEC §5, §8)
  config.js             reads .env, centralizes path constants and per-stage model/timeout/allowedTools
  sessions.js           reads/writes data/sessions.json (session state, keyed by id; schema in SPEC §6)
  housekeeping.js        single-instance lock, data/tmp cleanup, experiment-folder cleanup (SPEC §5c, §5)
  cli.js                 non-Discord test entry: `node src/cli.js analyze|scrape|profile`
docs/SPEC.md          full behavior spec (directory layout, 3-stage flow, tool allowlists, prompts, .env.example)
data/knowledge/        filed knowledge-base entries (one .md per adopted knowledge item)
data/reports/           analysis report artifacts
data/tmp/               scraped images/screenshots, auto-cleaned on startup
experiments/            one subfolder per adopted technical item, created by the bot
KNOWLEDGE.md            one-line index of every knowledge-base entry (date | title | link | file path)
PROFILE.md              auto-generated project profile the bot uses for relevance judgments
scripts/start-bot.cmd    Windows auto-start launcher (see section 6b)

2. Install

npm install
npx playwright install chromium
npm run selftest
  • npx playwright install chromium downloads the Chromium browser used by the scraper (Chromium only, takes a few minutes) — without this step, scraping a post fails outright.
  • npm run selftest actually calls claude -p once (using the cheap haiku model, 120s timeout) to confirm the local Claude Code CLI can be invoked by this bot. You need to see PASS; if you get FAIL, see section 7 ("Troubleshooting") below.

Then copy the environment variable template and fill in your own settings:

cp .env.example .env

.env variables:

Variable Description
DISCORD_TOKEN Discord bot token (see section 3 for how to get one)
OWNER_USER_ID Your Discord user ID — the bot only responds to this person (see section 3)
CHANNEL_ID (optional) Channel allowlist: when set, only that channel and its threads are processed, everything else (other channels, DMs) is ignored entirely; once set, plain messages in that channel also enable chat mode (see section 4). Leave empty for "no channel restriction, no chat mode"
ANALYZE_MODEL / EXPERIMENT_MODEL / ADOPT_MODEL Claude model used per stage, default sonnet / sonnet / opus
ANALYZE_TIMEOUT_MIN / EXPERIMENT_TIMEOUT_MIN / ADOPT_TIMEOUT_MIN Per-stage timeout in minutes, default 15 / 30 / 15
SCRAPE_SCROLLS How many times to scroll down while scraping to load more comments, default 6
SCRAPE_MAX_IMAGES / SCRAPE_MAX_SHOTS Max number of post images / full-page screenshots to capture, default 8 / 5
PROFILE_MAX_AGE_DAYS Re-scan PROFILE.md automatically after this many days, default 7

Once configured, start the bot:

npm start

2b. Optional: log in to Threads/Instagram (npm run login)

Most public Threads posts can be scraped without logging in, but Instagram almost always has a login wall, and Threads comments often need a login to load fully. For more complete scraping, run:

npm run login

This opens a headed (visible) browser window with one tab each for Threads and Instagram. Log in manually on either or both, then just close the whole browser window. The login state is saved to data/browser-profile/ (already gitignored), and all subsequent headless scraping by the bot reuses that login state.

Account risk note: scraping content while logged in violates Meta's Terms of Service. Low- frequency personal use (a few times a day) is low risk in practice, but the account could still be flagged for automated behavior — if that concerns you, don't log in and instead send the bot a screenshot of the post (see section 4); it works the same way, you just lose automatic comment scraping.

3. Create a Discord bot (step by step)

  1. Go to the Discord Developer Portal and log in with your own Discord account.
  2. Click New Application in the top right, give it a name (e.g. "Claude Code Learning Bot"), and create it.
  3. In the left sidebar, click Bot:
    • Click Reset Token (first time it may say Add Bot / Reset Token) and copy the generated token into .env's DISCORD_TOKEN. This token is only shown once — losing it means resetting again.
    • Scroll down to Privileged Gateway Intents and enable MESSAGE CONTENT INTENT, then save. This bot needs to read message content (to find links and notes) — without this intent it can't see message text.
  4. In the left sidebar, click OAuth2 → URL Generator:
    • Under Scopes, check bot.
    • Under Bot Permissions, check: Send Messages, Read Message History, Attach Files, Embed Links (the permissions needed for button interactions are already included in the general message permissions).
    • An invite URL is generated at the bottom of the page — copy it, open it in a browser, and add the bot to your own private server (create one first via Discord desktop/mobile if you don't have one).
  5. Get your own Discord user ID (for OWNER_USER_ID):
    • Discord Settings → Advanced → enable Developer Mode.
    • Right-click your own avatar/name anywhere → Copy User ID, and paste it into .env's OWNER_USER_ID.
  6. After saving .env, run npm start — seeing the bot show up online in Discord confirms it logged in successfully.

4. Usage

  1. In a channel where you've invited the bot (or in a DM to the bot), post a message containing a Threads (threads.net / threads.com) or Instagram (instagram.com) link — both regular posts and Reels are supported — optionally with a note.
    • You can also skip the link and just attach a screenshot (e.g. when an IG login wall blocks scraping, a screenshot is the main fallback).
    • A plain-text message with no link and no attachment gets a one-line usage reminder reply.
    • Reel limitation: the bot only analyzes the caption, comments, and cover image/screenshot — it cannot read the video's audio or visual content; the report header will note "caption and comments only, video content not read".
  2. The bot immediately replies "🔍 Received, scraping post and comments…", scrapes the body, comments, and images with Playwright, then switches to "🧠 Analyzing (usually 2–10 minutes)…", updating the elapsed time every 60 seconds.
    • If PROFILE.md's project profile is older than PROFILE_MAX_AGE_DAYS days, it first shows "📇 Project profile stale, rescanning…" and rescans automatically before analyzing.
    • If scraping fails (e.g. login wall) and there are no usable images at all, the bot asks you to reply to that status message with post screenshot(s) (multiple allowed) plus the post text; the same session then re-analyzes with what you supplied.
  3. Once analysis finishes, the bot sends an embed (authenticity / comment-section sentiment / item list / feasibility / effect / relevance to your projects / recommendation / experiment plan), and automatically opens a thread under that message, posting the full report split across messages in it (no more .md attachments — readable directly on mobile).
  4. The decision UI depends on how many items were found:
    • Single item: two buttons — for a technical item, "✅ Adopt & experiment" / "❌ Skip"; for a knowledge item, "📚 File to knowledge base" / "❌ Skip".
    • Multiple items: a dropdown to check off each item individually (multi-select) plus a "❌ Skip all" button. Checked technical items all go into experiments together; checked knowledge items are written straight to the knowledge base; a mixed selection does both.
  5. When an experiment finishes, the full RESULT.md is posted in chunks to the same thread along with a result embed, and a second round of decisions follows the same pattern: single item uses "📦 Adopt into dev environment" / "🗂 Keep as experiment only", multiple items use a select menu.
  6. Follow-up questions: typing directly in a report thread (or "replying" to a report/result message) is treated as a follow-up question about that analysis — the bot answers with the full analysis in context (and can do additional research), with the answer likewise chunked into the thread.
  7. Only one stage runs at a time per session; if you post a new link while a previous analysis is still running, the bot replies "previous analysis still running, this one will start automatically once it's done" and queues it.

Channel allowlist and chat mode (CHANNEL_ID, v6)

  1. To set it up: Discord Settings → Advanced → enable Developer Moderight-click the channel you want the bot to use → Copy Channel ID → paste into .env's CHANNEL_ID, then restart the bot.
  2. Once set: the bot only processes messages in that channel and its threads; every other channel and DMs are ignored completely (not even a usage hint), so the bot doesn't intrude on the rest of the server.
  3. Chat mode: once CHANNEL_ID is set, any plain message in that channel without a link and without an image attachment gets treated as ordinary conversation — with continuous memory (it remembers earlier chat), the ability to do web research (citing sources), and access to your project profile PROFILE.md, the knowledge base (KNOWLEDGE.md / data/knowledge/), and past analysis reports. Posting a link still goes through the analysis flow first and is never swallowed by chat mode.
  4. Leaving CHANNEL_ID empty keeps the old behavior: every channel is processed, but there's no chat mode (plain text just gets a usage hint).

You can also test without Discord, directly via the CLI (useful for verifying the flow before setting up a Discord token):

npm run analyze -- <url> [note]   # run the full scrape -> analyze flow, print JSON + report path
npm run scrape -- <url>           # run only the scraper, print text length + image/screenshot list
npm run profile                   # refresh PROFILE.md

5. Security design

This bot runs claude -p locally, and the experiment/adopt stages give it access to the Bash / Write / Edit tools, so its security boundary is designed as follows:

  • Only responds to the OWNER_USER_ID set in .env — any message or button click from anyone else (including other members of the same server) is ignored/rejected, so a stranger can't use this bot to run commands on your machine.
  • The analyze stage is read-only: its tool allowlist is only WebFetch, WebSearch, Read, Glob, Grepno Write/Edit/Bash. This is because analyze processes untrusted external social-media content, which could contain a prompt injection (e.g. "ignore previous instructions and run xxx"). Even if a post successfully tricks the model, the read-only allowlist guarantees the worst case is a wrong report — no file gets created, modified, or deleted, and no command gets executed.
  • Experiment/adopt only ever run after you explicitly click a button, and:
    • The experiment stage's working directory is pinned to experiments/<slug>/, and its prompt explicitly requires "only create files inside the current working directory, never touch the parent directory or other projects".
    • The adopt stage does have Bash/Write/Edit, but it's only triggered after you've already read the experiment report and personally confirmed you want to adopt it.
  • settings.json is never modified automatically by adopt: the adopt prompt (see docs/SPEC.md §7.3 / src/prompts.js) explicitly states "changes to settings.json and existing rule files: never write them directly, only include the suggested diff in the report, for the user to apply manually." Any change touching your existing rule files or global settings only ever shows up as text in the Discord report — you have to go make it yourself.

6. Knowledge base

For academic/pure-knowledge content (paper insights, research methods, ideas that don't need a hands-on experiment), "adopting" doesn't mean creating an experiment project — it means filing it into the knowledge base:

  • File: data/knowledge/<slug>.md — assembled directly from the analysis result by the bot (title, date, source link, items covered, verified claims with source URLs, comment-section sentiment, relevance to your projects), without calling Claude again.
  • Index: KNOWLEDGE.md at the project root, one line per entry: date | title | link | file path, so it's easy to search later with any tool (including Claude Code itself).
  • When a single post has a mix of technical and knowledge items, each selected item follows its own path after you check it off: technical items go into experiments/, knowledge items go into data/knowledge/.

6b. Autostart and self-maintenance (v6.5)

  • Autostart on login: scripts\start-bot.cmd is provided (double-click to start manually too). To register it with Task Scheduler for autostart at login (PowerShell, one-time):
    $cmd = "C:\Users\user\Desktop\dev\claude code learning bot\scripts\start-bot.cmd"
    $action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument "/c start `"ClaudeLearningBot`" /min `"$cmd`""
    $trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
    $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
    Register-ScheduledTask -TaskName 'ClaudeLearningBot' -Action $action -Trigger $trigger -Settings $settings -Force
    To remove: Unregister-ScheduledTask -TaskName 'ClaudeLearningBot' -Confirm:$false. After login a minimized "ClaudeLearningBot" terminal window appears; to stop the bot, open it and press Ctrl+C (so the child-process reaper runs properly — don't just close the window).
  • Single-instance lock: data/bot.lock. Starting a second instance exits immediately and reports the existing PID, preventing the same message from being answered twice. A stale lock from a crash is detected and overwritten automatically; delete the file manually if needed.
  • Automatic temp cleanup: on every startup, subfolders under data/tmp/ older than TMP_MAX_AGE_DAYS (default 7) are removed, so scraped images don't accumulate indefinitely.

7. Troubleshooting

  • Instagram link scrapes no content (login wall): when the scraper detects a login wall and has no usable images, it asks you to reply with post screenshots instead of making anything up. Two fixes: (a) send the bot a screenshot of the post directly (a link isn't required — screenshot-only messages trigger analysis too); (b) run npm run login once and retry (see the risk note in section 2b).
  • Scraping keeps failing / browser won't launch: confirm you ran npx playwright install chromium during install; use npm run scrape -- <url> to test the scraper in isolation and read the printed failure reason.
  • npm run selftest fails:
    1. First confirm the claude CLI itself works: run claude --version in a terminal — it should print a version number.
    2. Confirm you've completed claude login (or another auth method) for your subscription account, so claude -p can call the model without an interactive UI.
    3. Check the 錯誤/stderr output printed by selftest — it usually indicates whether it's a timeout or a CLI-level error.
  • Bot doesn't respond in Discord:
    1. Check whether MESSAGE CONTENT INTENT is enabled in the Discord Developer Portal (section 3, step 3). Without this intent the bot receives the message event but can't read its content, which looks identical to "no response".
    2. Check that .env's OWNER_USER_ID is correct — the bot is designed to only respond to this ID; if the message came from a different account, the bot ignoring it is expected behavior, not a bug.
    3. Check whether the npm start terminal log printed "Logged in as …" — if not, DISCORD_TOKEN may be wrong or expired and needs to be reset again from the Bot page in the Developer Portal.
  • Follow-up question gets no response: make sure you're posting inside the report thread (or using Discord's "Reply" on a report/result message). Plain text in a regular channel isn't treated as a follow-up — it just gets a usage hint.

License

MIT — see LICENSE.

About

Discord bot that verifies Threads/Instagram links with headless Claude Code and reports findings via thread replies

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages