Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .claude/skills/build-list/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
name: build-list
description: Build a ranked, CRM-deduped outbound prospect list from an ICP description. Use for "/build-list", "find me leads matching X", "build a prospect list for Y", or when the user wants a scored outbound list. Orchestrates discovery + the lead-pipeline module + enrichment.
---

# Build List

Turn an ICP description into a ranked, deduped, CRM-aware outbound list using the
`lead-pipeline` module. You run the judgement/IO steps (discover, enrich); the
script runs the deterministic steps (clean, dedup, score). The cardinal rule:

> **Dedup against the CRM before enriching.** Never spend enrichment effort or
> credits on accounts the user already knows. Known accounts become warm intros.

## Preconditions

- A CRM to dedup against: `data/crm-merge/contacts.json`. If it is missing, tell
the user to run `/merge-contacts` first (dedup is the whole point); offer to
proceed without it only if they insist, noting every lead will be `proceed`.
- An ICP rubric at `modules/lead-pipeline/icp.json`. If only `icp.example.json`
exists, copy it and adjust it to the user's request before scoring.

## Steps

1. **Read the ICP from the request.** Pull out: industry/segment, geography,
company-size band, and target buyer titles. Confirm anything ambiguous in one
short question rather than guessing.

2. **Discover candidates → raw CSV.** Use the web-search MCP (e.g. exa
`web_search_exa` / `linkedin_search_exa`) to find matching accounts. Write a
CSV to `reports/lead-pipeline/00-raw.csv` with whatever you found across these
columns: `company, domain, contact_name, contact_email, title, country`
(partial rows are fine; later stages fill gaps). If no search MCP is
available, ask the user for a raw CSV and skip to step 3.

3. **Clean** (script):
```bash
python3 modules/lead-pipeline/pipeline.py clean \
--in reports/lead-pipeline/00-raw.csv \
--out reports/lead-pipeline/01-cleaned.csv --country <codes>
```

4. **Dedup against the CRM** (script):
```bash
python3 modules/lead-pipeline/pipeline.py dedup \
--in reports/lead-pipeline/01-cleaned.csv \
--crm data/crm-merge/contacts.json \
--out reports/lead-pipeline/02-deduped.csv
```
Report the split: how many `proceed` (cold), `warm` (known account, route via
`crm_known_contact`), `skip` (already known, drop from outbound).

5. **Enrich the survivors only.** For rows tagged `proceed` or `warm` that lack a
domain / email / decision-maker, enrich with whatever you have, in this order:
- web search MCP for the company domain + LinkedIn (high hit-rate, cheap),
- an enrichment provider MCP (e.g. Clay) for verified emails — check remaining
credits first, and only for rows still missing an email,
- public business registries for named decision-makers at small firms when
enrichment misses.
Never enrich `skip` rows. Write the enriched result back to
`reports/lead-pipeline/02-deduped.csv` (or a `02b-enriched.csv`).

6. **Score** (script):
```bash
python3 modules/lead-pipeline/pipeline.py score \
--in reports/lead-pipeline/02-deduped.csv \
--icp modules/lead-pipeline/icp.json \
--out reports/lead-pipeline/03-scored.csv --top 50
```

7. **Hand back** the ranked top-N (path `reports/lead-pipeline/04-top-*.csv`),
the disposition split, and the tier distribution. Call out the `warm`
accounts explicitly: those are the highest-value, lowest-risk plays.

## Guardrails

- Do not send anything. This builds a list; outreach is a separate, reviewed step
(see the `linkedin-outreach` module).
- Respect data-protection law and platform terms. Flag if the user's target list
looks like scraped personal data being repurposed for cold mail.
- If a stage's numbers look wrong (e.g. zero `skip`/`warm` with a populated CRM),
inspect the stage CSV before continuing — that is why each stage is a file.
- Be honest about enrichment misses; report the hit-rate rather than fabricating
emails.
50 changes: 50 additions & 0 deletions .claude/skills/merge-contacts/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: merge-contacts
description: Merge the user's contact exports (Google Takeout, LinkedIn export, vault People notes) into one local CRM. Use for "/merge-contacts", "build my CRM", "dedup my contacts", or when another workflow needs a clean contacts source of truth.
---

# Merge Contacts

Build the user's local CRM source of truth with the `crm-merge` module. The
module does the work; your job is to find their exports, run it with the right
flags, and report what merged. Never invent data, never call an external CRM.

## Steps

1. **Find the exports.** Check, in order:
- A Google Takeout zip in `~/Downloads` (name contains "takeout" or
"google"). The script auto-finds the newest one, but confirm it exists.
- A LinkedIn export folder (contains `Connections.csv`, ideally
`messages.csv`). Ask the user for the path if you cannot find it.
- `vault/People/` for per-person notes (optional).
If none exist, point the user at the setup steps in
`modules/crm-merge/README.md` (both exports are self-service downloads) and stop.

2. **Get their name** for message-frequency counting. Ask how their name appears
in LinkedIn messages (the "me" side), or read it from `config/user.config.yaml`.

3. **Run the build:**
```bash
python3 modules/crm-merge/build.py \
--takeout-zip <path-or-omit-to-autofind> \
--linkedin-dir <folder> \
--me "<their name>" \
--out data/crm-merge
```
Omit any source they do not have; one source alone is fine.

4. **Report** the counts the script prints: total unique people, how many appear
in 2+ sources (the high-confidence merges), and how many link to a vault note.
Flag if a source loaded zero records, since that usually means a wrong path.

5. **Point them onward.** Tell them `data/crm-merge/browser.html` is openable in
any browser, and that `contacts.json` is what `/build-list` dedups against.

## Watch out for

- **Over-merging on a common name:** if the user says distinct people got fused,
lower `--name-collision-threshold` and re-run.
- **Zero LinkedIn messages counted:** the `--me` name probably does not match the
export. Check the `FROM`/`TO` values in `messages.csv` and pass the exact form.
- **Privacy:** outputs are git-ignored; do not commit them or paste contact data
into anything outward-facing.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ logs/
.tmp/
reports/
modules/*/generated/
# Your ICP rubric (copied from icp.example.json) is your market definition, not the kit's
modules/lead-pipeline/icp.json

# Python
__pycache__/
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ All notable changes to Compabob are recorded here. Format follows

### Added

- **`crm-merge` module + `/merge-contacts` skill** — folds Google Contacts (Takeout
vCards), a LinkedIn data export (Connections + messages), and your vault
`People/` notes into one local source of truth: a SQLite DB, a JSON file, and a
self-contained offline HTML browser. Identity resolution uses union-find on
three keys (verified email, LinkedIn slug, normalized name) with a common-name
collision guard, so duplicates collapse without over-merging generic names.
Relationship strength (LinkedIn message count) ranks records. No credentials,
all stdlib, idempotent. Outputs are git-ignored.
- **`lead-pipeline` module + `/build-list` skill** — turns a raw candidate list
into a ranked, CRM-aware outbound list through staged steps that write one CSV
per stage (discover → clean → **dedup-against-CRM** → enrich → score → top-N).
The deterministic stages (`clean`, `dedup`, `score`) run as a stdlib script;
discovery and enrichment run as assistant steps over optional MCP servers (exa
free tier minimum; an enrichment provider optional). Dedup tags each lead
`proceed` (cold), `warm` (you already know someone at the account, route the
intro), or `skip` (already a known contact), so you never enrich or cold-email
a relationship. ICP scoring is a tunable JSON rubric (`icp.example.json`).
- **`/chart-tufte` skill** — self-grade rubric for any quantitative chart,
grounded in Edward Tufte's *Visual Display of Quantitative Information*.
Nine criteria, ten genres, seven remedies, plus a 114-line
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Compabob

[English](README.md) | [Español](docs/i18n/README.es.md)

[![CI](https://github.com/chacosoldier/compabob/actions/workflows/smoke.yml/badge.svg)](https://github.com/chacosoldier/compabob/actions/workflows/smoke.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/chacosoldier/compabob?style=social)](https://github.com/chacosoldier/compabob/stargazers)
Expand Down Expand Up @@ -223,6 +225,8 @@ Skills are slash-command workflows. Type the command; the assistant runs the pro
| `/chart-tufte` | Self-grade any chart against Tufte's *Visual Display of Quantitative Information* before showing it |
| `/mcp-debug` | Health-check, trace, or audit your MCP servers when tools fail silently |
| `/memory-debt` | Resolve memory updates that earlier sessions proposed but never wrote |
| `/merge-contacts` | Fold your Google + LinkedIn + vault contacts into one local CRM, deduped |
| `/build-list` | Build a ranked outbound list: discover, dedup against your CRM, enrich, score |

## Modules (opt-in)

Expand All @@ -234,6 +238,8 @@ The core runs with zero external services. Modules add capability when you want
| `telegram` | Available | A Telegram bot: inbound messages drafted for your approval, never auto-sent |
| `integrations` | Available | MCP tools: browser automation, web search, Gmail, Calendar, utilities |
| `linkedin-outreach` | Available | Drafts one LinkedIn connection-invitation card a day from a queue, for your review and manual send |
| `crm-merge` | Available | Folds Google + LinkedIn + vault contacts into one local CRM (SQLite + offline HTML browser), deduped across sources. No creds |
| `lead-pipeline` | Available | Builds a ranked outbound list: discover, clean, dedup against your CRM, enrich, score. One CSV per stage |
| `memory-search` | Available | Real index over `memory/` and `vault/` so retrieval ranks by relevance (FTS5 by default; semantic via Ollama if installed) |
| `extra-agents` | Roadmap | An agent gallery: designer, evaluator, sales coach, project manager |
| `whatsapp` | Roadmap | WhatsApp channel — not built (account-ban risk); use Telegram instead |
Expand Down
40 changes: 40 additions & 0 deletions docs/i18n/README.es.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Compabob

[English](../../README.md) | [Español](README.es.md)

## Por qué

Una sesión nueva de Claude Code es una generalista competente que no sabe nada de ti. Cada vez tienes que volver a explicarle quién eres, cómo quieres las respuestas y en qué estás trabajando. No tiene noción de tus prioridades, ni barreras de seguridad sobre lo que se envía, ni un lugar donde guardar lo que aprende.

Compabob es la estructura que faltaba. El ciclo fundamental, desde el primer día:

> Le pides que tome notas de la reunión de planificación de hoy. Las archiva en tu base de conocimiento. La semana siguiente, antes del seguimiento, escribes `/meeting-prep` y te devuelve los asistentes, lo que se decidió y los temas pendientes, sin que tengas que buscar nada.

Ese ciclo de segundo cerebro es el valor que obtienes de inmediato. Compabob es un andamio, no un asistente terminado: todo lo demás se multiplica a medida que lo haces tuyo.

## Qué es (y qué no es)

- **Es**: una plantilla con opinión propia para un solo usuario. Arquitectura, convenciones y un conjunto de agentes, hooks y skills que requirieron verdadera iteración para dar en el clavo, empaquetados para que no empieces desde cero.
- **No es**: un producto, un SaaS ni una caja mágica sin configuración. No hay registro, ni telemetría, ni ventas adicionales. Funciona completamente en tu máquina bajo tu propia suscripción de Claude Code.

## Estado de mantenimiento

**Mantenimiento activo.** Este kit se actualiza con el tiempo a medida que sus mantenedores aprenden qué funciona y conforme Claude Code evoluciona, así que espera que siga mejorando. Sigues siendo dueño absoluto de tu copia: haz fork, cámbialo todo, aléjate todo lo que quieras. Las issues y PRs son bienvenidas; las revisiones se hacen con el tiempo disponible, ya que el kit se mantiene junto con otros trabajos.

## Verlo en acción

![Ejecutando ./setup.sh: Compabob hace algunas preguntas y luego crea tu espacio de trabajo en aproximadamente un minuto.](../assets/demo.png)

Un solo script. Te pregunta quién eres y qué haces, crea tu vault, memoria y configuración a partir de las semillas incluidas, aplica un preset de personalidad y ya estás listo para empezar.

## Inicio rápido

¿Te manejas con la terminal? Tres comandos:

```bash
git clone https://github.com/chacosoldier/compabob.git
cd compabob
./setup.sh
```

> Esta es una traducción parcial (las primeras seis secciones) del [README en inglés](../../README.md). Para la guía completa, incluyendo el tutorial paso a paso, las skills, los módulos y el resto, sigue el documento original. Traducción inicial aportada por [@MD-Mushfiqur123](https://github.com/MD-Mushfiqur123) en [#17](https://github.com/chacosoldier/compabob/pull/17).
2 changes: 2 additions & 0 deletions modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The core kit runs with zero external services. Modules add capability when you w
| [`telegram/`](telegram/) | **Available** | A Telegram bot. Inbound messages are drafted for your approval; it never auto-sends. Bot-token based, polling. |
| [`integrations/`](integrations/) | **Available** | An MCP integration picker: web + browser tools, Google Workspace, web search, and utilities. Run `scripts/install-integrations.sh`. |
| [`linkedin-outreach/`](linkedin-outreach/) | **Available** | Daily LinkedIn connection-invitation drafting from a queue you keep: it decides hook vs. no-note and writes a review card. You send it; it never sends. |
| [`crm-merge/`](crm-merge/) | **Available** | Folds Google Contacts + LinkedIn export + vault notes into one local CRM (SQLite + JSON + offline HTML browser), deduping people across sources. No creds. Run by hand or via the `/merge-contacts` skill. |
| [`lead-pipeline/`](lead-pipeline/) | **Available** | Builds a ranked outbound list: discover, clean, **dedup against your CRM**, enrich, score. One CSV per stage. Run by hand or via the `/build-list` skill. exa MCP optional for discovery. |
| [`memory-search/`](memory-search/) | **Available** | A search index over memory and the vault: ranked keyword search out of the box, semantic (search by meaning) with Ollama. Built by the `/index-memory` skill. |
| [`extra-agents/`](extra-agents/) | Roadmap | A gallery of additional agents (designer, evaluator, sales coach, project manager) to copy into `.claude/agents/`. |
| [`whatsapp/`](whatsapp/) | Roadmap | A WhatsApp channel. Not built: unofficial bridges risk an account ban. Use the Telegram module instead. |
Expand Down
103 changes: 103 additions & 0 deletions modules/crm-merge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Module: CRM merge

Folds your scattered contact exports into **one local source of truth**: a
SQLite database, a JSON file, and a self-contained HTML browser you can open
offline. No external CRM, no API keys, no data leaving your machine.

It merges three sources, deduping people who appear in more than one:

- **Google Contacts** (from a Google Takeout zip)
- **LinkedIn Connections + message history** (from a LinkedIn data export)
- **Your vault `People/` notes** (optional)

## Why own your contacts

Your network lives split across LinkedIn, your phone, your inbox, and your CRM
vendor, each with a partial, slightly-wrong copy. The moment you want to "email
everyone I know at a fintech" or "check if we already know someone at this
account," you are stitching exports by hand. This module does that stitch once,
locally, and gives you a file you control. It is also the **dedup backbone**
the `lead-pipeline` module needs: you cannot "dedup a prospect list against your
CRM" until you have a clean CRM.

## The hard part it gets right: identity resolution

The same person shows up as `j.smith@acme.com` in Google, `linkedin.com/in/jsmith`
on LinkedIn, and `John Smith.md` in your notes. Naive dedup either misses these
(three rows for one person) or over-merges (every "John Smith" fused into one).

This module uses **union-find on three identity keys** — verified email,
LinkedIn slug, and normalized name — so any shared key links records
transitively into one person. A **common-name guard** stops it merging on a name
that too many records share (the generic-name trap), while exact keys (email,
slug) always merge. This is the entity-resolution problem every CRM-hygiene
project hits, and the part most hand-rolled scripts get wrong.

The merged record keeps the fullest name, the union of emails/phones, and the
strongest relationship signal (LinkedIn message count) to rank who you actually
know well.

## Setup

No credentials. You supply two self-service exports:

1. **Google Takeout** — https://takeout.google.com → select **Contacts**
(vCard format) → download the zip. Leave it in `~/Downloads` and the script
finds it, or pass `--takeout-zip <path>`.
2. **LinkedIn export** — LinkedIn → Settings & Privacy → Data privacy →
*Get a copy of your data* → include **Connections** and **Messages**. Unzip
it into a folder and pass `--linkedin-dir <folder>`. (LinkedIn emails the
archive; it can take minutes to a day.)
3. **Vault notes** — optional; defaults to `vault/People/`.

Either source alone works; you do not need all three.

## Use it

```bash
python3 modules/crm-merge/build.py \
--takeout-zip ~/Downloads/takeout-20260608.zip \
--linkedin-dir ~/Downloads/linkedin-export \
--me "Your Full Name" \
--out data/crm-merge
```

`--me` is how the script tells you from the other party in your LinkedIn
messages, so it can count message frequency per contact. Pass the name(s)
exactly as they appear in the export, comma-separated if you have more than one.

Outputs land in `data/crm-merge/` (git-ignored):

- `contacts.db` — query it with any SQLite tool, or let the `crm-relationships`
agent read it.
- `contacts.json` — what the `lead-pipeline` module dedups against.
- `browser.html` — open in a browser: search, filter by source, click a person
for full detail. Fully offline, all data inlined.

Re-run any time. It is idempotent: it rebuilds from scratch, so re-exporting and
re-running keeps your source of truth current.

## Or just ask your assistant

Run the `/merge-contacts` skill in a session and it will check which exports you
have, run the build with the right flags, and report what merged.

## Tuning the merge

If you have an unusually common name in your network and see over-merging, lower
`--name-collision-threshold` (default 30). If distinct people are not merging,
it is almost always because neither shares an email nor a LinkedIn slug and their
names differ; add the missing identifier to one source and re-run.

## Pairs with

- **`crm-relationships` agent** — reads `contacts.db` to answer "who do I know
at X" and to track interactions.
- **`lead-pipeline` module** — dedups new prospect lists against `contacts.json`
so you never enrich or cold-email someone you already know.

## A note on privacy

Everything stays local. The HTML browser inlines your data into one file with no
network calls; treat that file like the contact list it is. Outputs are
git-ignored by default so you never commit your network to a repo.
Loading
Loading