agentleFS
spirosbaxspirosbax/insaight/CLAUDE.md

insaight

CLAUDE.md32 starsChanged 2 months agoMITView on GitHub

Scrapes LinkedIn company pages and personal profiles via Apify, stores in SQLite, and exposes them to Claude via MCP for prospect research and cold outreach. Skills live in skills/<name>/SKILL.md — each is independently invocable and they chain conversationally (research → outreach → save). User config lives in ~/.insaight/config.md (created by get_config() on first call) — not in this file. The block below is the template; skills call insaight:get_config to read it. All tools must be loaded first via tool_search(query="insaight").

The file

We split this CLAUDE.md into the rules it gives an agent

5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All tools must be loaded first via tool_search(query="insaight").

    line 29
  2. Very specific:

    After REFLECT_EVERY outcomes (env var, default 10), record_outcome returns reflection_due: true → offer the insaight-reflect skill.

    line 102
  3. Very specific:

    Low post volume: If the account has < 10 posts, supplement with web_search("[company name] EV charging news 2025 2026").

    line 119
  4. Distinctive:

    Do your own thematic grouping from snippets.

    line 122
  5. Distinctive:

    If Notion MCP isn't connected at all, tell the user:

    line 181

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

Insaight — LinkedIn Intelligence Tool

Scrapes LinkedIn company pages and personal profiles via Apify, stores in SQLite, and exposes them to Claude via MCP for prospect research and cold outreach.

Skills live in skills/<name>/SKILL.md — each is independently invocable and they chain conversationally (research → outreach → save).


Setup

User config lives in ~/.insaight/config.md (created by get_config() on first call) — not in this file. The block below is the template; skills call insaight:get_config to read it.

NOTION_WORKSPACE:     YourWorkspaceName   # your Notion workspace / team name
NOTION_RESEARCH_PAGE: Prospect Research   # parent page for research briefs
NOTION_OUTREACH_LOG:  Sent Log            # your sent-messages archive (READ-ONLY)
COMPANY_NAME:         YourCompany         # your company (used by draft-post)
COMPANY_LINKEDIN:     your-company-slug   # your LinkedIn company page slug

Insaight MCP — Tool Reference

All tools must be loaded first via tool_search(query="insaight").

ToolPurposeKey params
insaight:list_accountsShow all tracked accounts + slugs—
insaight:get_statsDB-wide overview (counts, date range, categories)—
insaight:list_postsSlim index: metadata + 150-char snippet, NO full textaccount, days_ago, limit, min_engagement
insaight:search_postsFull-text keyword search → returns full contentquery, account, days_ago, limit
insaight:get_postsFetch full content for specific URNs (max 20)urns: string[]
insaight:scrape_profileScrape fresh posts from any LinkedIn URL via Apifyurl, max_posts
insaight:list_peopleList stored employees/leadership from DB (instant)account, role, limit
insaight:scrape_peopleScrape + store company leadership via Apifyurl, job_titles, max_items, full_mode
insaight:scrape_person_profileEnrich ONE person with full profile (experience, education, skills, etc.)url, with_email, company_url
insaight:scrape_post_commentsScrape comments on a LinkedIn post (with replies + author info)post_url, max_items, include_replies, profile_mode
insaight:list_commentsList stored comments for a post, ranked by likespost_urn or post_url, limit, min_likes, include_replies
insaight:log_outreachRecord a SENT message in the ledger (flags prior contact)target_url, message, target_name, channel, variant, hook_type
insaight:record_outcomeRecord replied/positive/meeting/ghosted; flags when reflection is dueoutreach_id or target_url, outcome, reply_snippet
insaight:list_outreachQuery the outreach ledger (prior-contact checks, pending review)outcome, target, limit, full
insaight:get_outreach_statsReply-rate breakdown by hook/variant/channel + reflection state—
insaight:get_memoryRead distilled style + playbook memory (draft skills read THIS)—
insaight:update_memoryRewrite a memory file — only after user approvalkind, content, mark_reflection_done
insaight:get_configUser config: Notion pages + company name/slug (~/.insaight/config.md)—

Efficient reading pattern

1. list_accounts()                    → find the correct slug
   └─ not found? → scrape_profile(url) to fetch and store fresh posts

2. list_posts(account=slug, limit=50) → survey what exists (slim, token-cheap)

3. get_posts(urns=[...])              → read full content for chosen posts only
   OR search_posts(query=...)         → targeted lookup when you know what to look for

4. list_people(account=slug)          → check stored leadership
   └─ empty? → scrape_people(url, job_titles=["CEO","Founder","CTO","Head of"])

5. scrape_person_profile(url=...)     → enrich ONE person with full profile
   (experience, education, skills, volunteer, languages, projects, recommendations)
   Use for commonality mining — overlapping companies, schools, volunteer work.

6. list_comments(post_url=...)        → read commenters on a post (free if already scraped)
   └─ empty? → scrape_post_comments(post_url, include_replies=True)
   Use for mining a thread for warm leads, decision-makers, competitor mentions.

Never call get_posts on all URNs at once. Select the most signal-rich posts to stay within the 20-URN limit per call.

Cost warnings

scrape_profile and scrape_people call Apify (costs tokens/money). Only use them when the account isn't tracked yet or data is stale. list_people is free/instant — always try it first.

Account slug format

  • Company page: acme-charging (matches the LinkedIn URL slug)
  • Personal profile: jane-doe-12345678 (matches the LinkedIn profile ID)

Engagement benchmarks (example: EV charging niche, NL/EU B2B — adjust for yours)

  • < 10 likes = low
  • 10–40 likes = normal
  • 40+ likes = high signal post, read in full

Outreach memory loop

The ledger + memory system replaces re-reading a raw sent-log every session:

  1. Log every send (log_outreach) with the exact sent text + variant + hook_type.
  2. Record every outcome (record_outcome) when the user reports a reply, meeting, or gives up (ghosted). The user decides when something is ghosted.
  3. After REFLECT_EVERY outcomes (env var, default 10), record_outcome returns reflection_due: true → offer the insaight-reflect skill.
  4. Reflection proposes updates to ~/.insaight/memory/style.md (voice) and ~/.insaight/memory/playbook.md (strategies with evidence counts) — the user approves before update_memory is called. Never overwrite memory silently.
  5. draft-outreach reads get_memory(), not raw history — constant token cost regardless of ledger size.

Playbook discipline: every claim carries its evidence ("question hooks: 4/9 replied"); below n=10 a pattern is a hypothesis, not a rule.

Tips

  • Personal profiles vs company pages: Personal profiles (founders, CEOs) often reveal more strategic signal than corporate pages. If both are tracked, read both.
  • Non-English content: Many European prospects post in their own language. Translate mentally and include in your analysis — don't skip non-English posts.
  • Low post volume: If the account has < 10 posts, supplement with web_search("[company name] EV charging news 2025 2026").
  • Category field: The category field in Insaight data is currently unpopulated. Do your own thematic grouping from snippets.
  • Time gaps: A long gap in posting (> 3 months) is itself a signal — note it.

Notion Integration

Two Notion locations matter for every skill run:

1. [NOTION_RESEARCH_PAGE] (read + write)

Parent page containing one sub-page per researched person/company.

  • Location: [NOTION_WORKSPACE] → [NOTION_RESEARCH_PAGE]
  • Page naming: [Name] — [YYYY-MM-DD]
  • Used by: research skills (read existing page as context, then write updated brief), save-notion (the writer)
  • Find it:
    notion:search(query="[NOTION_RESEARCH_PAGE]")

2. [NOTION_OUTREACH_LOG] (READ-ONLY — legacy fallback)

The outreach ledger + memory system (see "Outreach memory loop") has replaced this page. Sends live in the SQLite outreach table; style lives in ~/.insaight/memory/style.md. Only fall back to this Notion page when get_memory() reports nothing learned AND list_outreach() is empty (fresh install with pre-existing Notion history).

  • Location: [NOTION_WORKSPACE] → [NOTION_RESEARCH_PAGE] → [NOTION_OUTREACH_LOG]
  • NEVER write to this page. The user maintains it manually.
  • Find it:
    notion:search(query="[NOTION_OUTREACH_LOG]")

Workflow integration

SkillBefore runAfter run
research-person / research-companyCheck for existing research page (load as context). Check list_outreach(target=...) for prior messages to this target (warn user).Auto-save brief to [NOTION_RESEARCH_PAGE]. No prompt.
research-postCheck for existing research page (load as context). Cross-check named commenters via list_outreach.Auto-save brief to [NOTION_RESEARCH_PAGE]. No prompt.
draft-outreachget_memory() for style + playbook. list_outreach(target=...) for prior contact.Do not auto-save. When the user says they SENT it → log via track-outreach.
track-outreach—log_outreach on send; record_outcome on reply/ghost. Offer insaight-reflect when reflection_due.
reflectget_outreach_stats + list_outreach(full=true) + get_memory.Propose updates with evidence → on approval update_memory (last call: mark_reflection_done=true).
save-notion—Internal helper the research skills invoke. Also exposed as explicit "save to Notion" command.

Session caching

Fetch memory (get_memory) once per conversation and reuse it. Same for any legacy Notion pages you had to load — never re-fetch on iteration.

Notion MCP loading

The Notion MCP is separate from insaight. If notion:search / notion:fetch / notion:create-pages aren't available, load them first:

tool_search(query="notion search fetch create")

If Notion MCP isn't connected at all, tell the user:

"Notion MCP isn't connected. Connect it at claude.ai → Settings → Integrations, then retry. Research will proceed but won't auto-save."

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.