Turn your coding agent into a Software Engineering agent

I’m a Claude Code plugin that gives your agentic workflows software engineering expertise. Ask me for an idea and get a fully working production codebase in one shot.

Get your license key for $5I built this site. Read the source

What I do that vanilla Claude Code doesn't

  1. I clarify by default I never assume you know exactly what you want before I build, because often you don't. Human ideas are messy; I turn yours into a rigorous specification first.
  2. I make minimal assumptions I will never go off and build a 40-file change you never asked for. I step back, reason, and plan before I touch code.
  3. Zero slop Vanilla agents pattern-match to their training data. I build a system around your idea, then layer on clean-code principles and tests that assert what a user sees, so you can hand the code off without drowning in tech debt.

I clarify by default

Clarifying questions

Build me a personal CRM.

Vanilla Claude Code

I built a personal CRM that runs on your own computer. It needs nothing but Python: no packages to install and no build step. All your data is kept in one SQLite file, `crm.db`.

**To run it:** `python3 server.py`, then open http://127.0.0.1:8765

**What it does:**
- **Dashboard:** shows who you're overdue to contact, reminders due this week, birthdays in the next 30 days (with the age they're turning, if you entered the year) and your recent activity. A **✓ Contacted** button marks that you got in touch today.
- **Contacts:** stores contact details, how you met, notes, tags and how often you want to stay in touch (weekly, monthly, quarterly and so on).
- **Contact page:** a history of calls, meetings and messages, plus dated reminders like "ask how the move went."
- **Finding people:** search across names, companies, notes and tags; click a tag to filter; sort by name, most overdue, longest since you last talked, or recently added. Press `/` to search and `n` to add a contact.
- **Import and export:** bring contacts in from a CSV file, and export everything as CSV or JSON.
- It follows your system's light or dark mode.

**Testing:** there are 15 tests covering the data rules and the web requests, and all of them pass. They include overdue follow-ups, Feb 29 birthdays, deleting a contact along with their history, CSV import and export, and blocking changes from other websites. I checked the JavaScript for syntax errors but haven't clicked through the pages in a browser.

The web request tests take about 35 seconds. Using curl, the server itself answered in under a millisecond; the delay comes from this sandbox blocking Python's own connections to the local server, so on your machine they should finish almost instantly.

**Safety:** there's no login, so by default only your own computer can reach it, and it rejects write requests from other websites. Don't use `--host 0.0.0.0` on a network you don't trust.

Files are `server.py`, the `static/` folder (the web pages), `test_server.py` and `README.md`. Some things I could add next: a Gmail or Calendar sync to log interactions automatically, a vCard import from your phone's contacts, or a login so you can host it somewhere.

Martin

Before we talk about what the CRM should do, I'd like to understand what's going wrong today.

1. What goes wrong right now with keeping up with the people in your life? For example: losing touch, forgetting details, or missing moments that matter.
2. Tell me about the most recent time it happened. Who was it, what happened, and how did you find out?

I make minimal assumptions

Problem understanding

Build me a personal CRM.

Vanilla Claude Code

I built a personal CRM that runs on your own computer. It needs nothing but Python: no packages to install and no build step. All your data is kept in one SQLite file, `crm.db`.

**To run it:** `python3 server.py`, then open http://127.0.0.1:8765

**What it does:**
- **Dashboard:** shows who you're overdue to contact, reminders due this week, birthdays in the next 30 days (with the age they're turning, if you entered the year) and your recent activity. A **✓ Contacted** button marks that you got in touch today.
- **Contacts:** stores contact details, how you met, notes, tags and how often you want to stay in touch (weekly, monthly, quarterly and so on).
- **Contact page:** a history of calls, meetings and messages, plus dated reminders like "ask how the move went."
- **Finding people:** search across names, companies, notes and tags; click a tag to filter; sort by name, most overdue, longest since you last talked, or recently added. Press `/` to search and `n` to add a contact.
- **Import and export:** bring contacts in from a CSV file, and export everything as CSV or JSON.
- It follows your system's light or dark mode.

Martin

Martin:
Before we talk about what the CRM should do, I'd like to understand what's going wrong today.

1. What goes wrong right now with keeping up with the people in your life? For example: losing touch, forgetting details, or missing moments that matter.
2. Tell me about the most recent time it happened. Who was it, what happened, and how did you find out?

You:
Mostly losing touch with people I meant to stay close to, and forgetting what we last talked about. Most recent: last month I ran into Dana at a conference. She was a client two years ago and in the spring she mentioned a new project she wanted help with. I never followed up, and when we talked she said she had hired someone else in July. I found out because she told me to my face. Right now everything lives in my head, in iMessage threads, and in a Google Sheet I stopped updating a year ago. Something like this happens about once a month.

Martin:
Thanks, the Dana story helps a lot. Two follow-ups:

1. Back in the spring, how did Dana tell you about the project: in person, over iMessage, or some other way? And what got in the way of following up? Did you mean to do it later and it slipped, or did you forget she'd mentioned it at all?
2. The Dana story is about a lost client. You also said you lose touch with people you meant to stay close to, and forget what you last talked about. Can you tell me about a recent time that happened with someone who isn't a client, like a friend or an old colleague? Who was it, and what happened?

You:
Dana told me in person at a coffee in April. I meant to send her a proposal the following week and it slipped; by June I had forgotten she mentioned it at all. Non-client example: my old teammate Marcus moved to Denver in 2024. We said we would do a monthly call. I realized in August that we had not spoken since February, and when I texted him I could not remember whether his daughter had been born yet, so I avoided asking. That is the other half of it: I do not remember what we last talked about, so I put off reaching out.

Martin:
Here's what I understand so far.

You meant to stay close to certain people, both clients and friends, and two things keep going wrong. You lose track of promises and plans to follow up, and you forget what you last talked about, which makes you put off reaching out. Right now all of this lives in your head, in iMessage threads, and in a Google Sheet you stopped updating a year ago. Something like this happens about once a month.

- **Dana:** She was a client two years ago. Over coffee in April she told you about a new project she wanted help with. You meant to send her a proposal the next week, but it slipped, and by June you'd forgotten she'd mentioned it. Last month you ran into her at a conference, and she told you she'd hired someone else in July.
- **Marcus:** He was your teammate and moved to Denver in 2024. You agreed to a monthly call. In August you realized you hadn't spoken since February. When you texted him, you couldn't remember whether his daughter had been born yet, so you didn't ask.

Is that right? Is anything missing or off?

Zero slop

Code quality

Build me a personal CRM.

Vanilla Claude Code

server.py (716 lines), lines 36–73:

SCHEMA = """
CREATE TABLE IF NOT EXISTS contacts (
    id             INTEGER PRIMARY KEY,
    name           TEXT NOT NULL,
    email          TEXT NOT NULL DEFAULT '',
    phone          TEXT NOT NULL DEFAULT '',
    company        TEXT NOT NULL DEFAULT '',
    role           TEXT NOT NULL DEFAULT '',
    location       TEXT NOT NULL DEFAULT '',
    birthday       TEXT NOT NULL DEFAULT '',   -- YYYY-MM-DD or --MM-DD (year unknown)
    how_met        TEXT NOT NULL DEFAULT '',
    notes          TEXT NOT NULL DEFAULT '',
    follow_up_days INTEGER,                    -- desired contact cadence; NULL = none
    created_at     TEXT NOT NULL,
    updated_at     TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS contact_tags (
    contact_id INTEGER NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    tag        TEXT NOT NULL,
    PRIMARY KEY (contact_id, tag)
);
CREATE TABLE IF NOT EXISTS interactions (
    id         INTEGER PRIMARY KEY,
    contact_id INTEGER NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    date       TEXT NOT NULL,
    kind       TEXT NOT NULL,
    summary    TEXT NOT NULL DEFAULT '',
    created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS reminders (
    id         INTEGER PRIMARY KEY,
    contact_id INTEGER NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
    due_date   TEXT NOT NULL,
    text       TEXT NOT NULL,
    done       INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_interactions_contact ON interactions(contact_id, date);

Martin

src/personal_crm/people.py (154 lines), lines 31–90:

@dataclass(frozen=True)
class Every:
    count: int
    unit: Literal["day", "week", "month"]

    def after(self, start: date) -> date:
        match self.unit:
            case "day":
                return start + timedelta(days=self.count)
            case "week":
                return start + timedelta(weeks=self.count)
            case "month":
                months_since_year_zero = start.year * 12 + start.month - 1 + self.count
                year, month_index = divmod(months_since_year_zero, 12)
                last_day = calendar.monthrange(year, month_index + 1)[1]
                return start.replace(year=year, month=month_index + 1, day=min(start.day, last_day))

    def __str__(self) -> str:
        return self.unit if self.count == 1 else f"{self.count} {self.unit}s"


@dataclass(frozen=True)
class Conversation:
    on: date
    note: str


@dataclass
class FollowUp:
    number: int
    due: date
    note: str
    done_on: date | None = None

    @property
    def is_open(self) -> bool:
        return self.done_on is None


@dataclass
class Person:
    name: str
    conversations: list[Conversation] = field(default_factory=list)
    follow_ups: list[FollowUp] = field(default_factory=list)
    every: Every | None = None
    every_set_on: date | None = None

    @property
    def next_due(self) -> date | None:
        if self.every is None or self.every_set_on is None:
            return None
        last = self.last_conversation
        return self.every.after(last.on if last else self.every_set_on)

    def talk_every(self, every: Every, today: date) -> None:
        self.every = every
        self.every_set_on = today

    @property
    def conversations_newest_first(self) -> list[Conversation]:

Zero slop

Testing and robustness

Build me a personal CRM.

Vanilla Claude Code

test_server.py, lines 15–45 (15 tests; they import server.py and call its Store class directly):


class StoreTests(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.store = server.Store(str(Path(self.tmp.name) / "t.db"))

    def tearDown(self):
        self.tmp.cleanup()

    def test_create_and_tags_normalized(self):
        c = self.store.create_contact({"name": " Ada ", "tags": "Work, friend, work"})
        self.assertEqual(c["name"], "Ada")
        self.assertEqual(c["tags"], ["friend", "work"])

    def test_name_required(self):
        with self.assertRaises(server.ApiError):
            self.store.create_contact({"name": "  "})

    def test_follow_up_overdue_uses_last_interaction(self):
        c = self.store.create_contact({"name": "Bo", "follow_up_days": 30})
        self.store.add_interaction(c["id"], {"date": (date.today() - timedelta(days=40)).isoformat(), "kind": "call"})
        c = self.store.get_contact(c["id"])
        self.assertEqual(c["days_overdue"], 10)
        dash = self.store.dashboard()
        self.assertEqual([x["id"] for x in dash["overdue"]], [c["id"]])

        self.store.add_interaction(c["id"], {"date": date.today().isoformat(), "kind": "email"})
        self.assertEqual(self.store.get_contact(c["id"])["days_overdue"], -30)
        self.assertEqual(self.store.dashboard()["overdue"], [])

    def test_birthdays(self):

Martin

tests/e2e/conftest.py (every test runs the real `crm` command as a subprocess):

@pytest.fixture
def crm_file(tmp_path):
    return tmp_path / "personal-crm.json"


@pytest.fixture
def crm(crm_file):
    def run(*args: str, today: str = "2026-09-12") -> subprocess.CompletedProcess[str]:
        environment = os.environ | {"CRM_FILE": str(crm_file), "CRM_TODAY": today}
        return subprocess.run(
            ["uv", "run", "crm", *args], capture_output=True, text=True, env=environment
        )

    return run

tests/e2e/test_follow_ups.py:

@pytest.fixture
def follow_up_for_dana(crm):
    crm("add", "Dana", today="2026-04-14")
    crm(
        "follow-up",
        "Dana",
        "2026-04-21",
        "--note",
        "Send proposal for her new project",
        today="2026-04-14",
    )


def test_the_follow_up_reminds_me_on_its_due_date(crm, follow_up_for_dana):
    reminders = crm(today="2026-04-21")

    assert (
        "  #1 Dana — Send proposal for her new project (due today)" in reminders.stdout.splitlines()
    )


def test_no_reminder_before_the_due_date(crm, follow_up_for_dana):
    reminders = crm(today="2026-04-20")

    assert reminders.stdout == "Nothing due today.\n"


def test_an_unfinished_follow_up_stays_in_front_of_me(crm, follow_up_for_dana):
    follow_ups = crm("follow-ups", today="2026-06-01")

    assert follow_ups.stdout == (
        "#1  Dana  overdue since 2026-04-21  Send proposal for her new project\n"
    )


def test_a_finished_follow_up_goes_away(crm, follow_up_for_dana):
    done = crm("done", "1", today="2026-04-21")
    follow_ups = crm("follow-ups", today="2026-04-21")

    assert done.stdout == "Marked follow-up #1 for Dana done.\n"
    assert follow_ups.stdout == "Nothing to follow up on\n"

Why Travis built me

Coding agents fall apart at non-trivial tasks. Ask Lovable, Replit, or vanilla Claude Code to build you a real product and you get thin prototypes, piles of code you never asked for, or hours of loop engineering on something that looked simple.

The same thing happens when humans build software. The ask is underspecified and unclear, so the biggest gap was never coding. The skill gap is figuring out what code to write.

Fifteen years championing clean code at some of the world's biggest companies taught Travis how to take messy, unclear ideas and turn them into clean code. That experience was distilled into a communication methodology, and the methodology was distilled into me. When you talk to me, you are talking to a coding agent with the experience of someone who has spent a career bringing ideas to life.

Tired of drowning in slop, staring at /plan output until you go cross-eyed, and throwing hours of code and wasted tokens away? I was built for you.

If an AI is going to replace me, I'm going to build the AI that's going to do so.

Travis

What happens after you pay

  1. You get an email from support@trymartin.dev with your license key and setup instructions.
  2. Run /plugin in Claude Code. Search for martin. Install me globally.
  3. Run claude --agent martin
  4. Say: Introduce Yourself

Want martin as a drop-in replacement for claude? Add this to your shell profile:

alias martin='claude --agent martin'
Get your license key for $5

FAQ

How are you different from Claude Code?
I'm a plugin on top of Claude Code. Not a replacement for it.
What are the drawbacks?
Longer sessions and more tokens than vanilla Claude Code. Run me with --model sonnet to spend less. I'm still alpha.
Do you work with Codex or Pi?
I'm optimized for Claude Code. Email support@trymartin.dev if you need something else.
Do I get updates?
Free for life with your license key.
What do you log?
Zero telemetry. The only thing kept is the email you bought with. It's encrypted at rest and humans read the inbox.
What license am I buying?
Polyform Internal Use 1.0.0. Use me and modify me. Don't distribute me. One key per seat at $5 each.