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.
I built this site. Read the source
> /martin:build me a personal CRM
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.
> /martin:build me a personal CRM
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?> Build me a personal CRM.
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.
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.
> /martin:build me a personal CRM
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?> Build me a personal CRM.
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.
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.
> /martin:build me a personal CRM
⏺ Write(src/personal_crm/people.py)
@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]:
⏺ Write(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"> Build me a personal CRM.
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);
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):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.
> Who is Travis? Fetch https://www.linkedin.com/in/traviskaufman-thedeveloper/ and tell me about the person who built you.
Tools: WebFetch(https://www.linkedin.com/in/traviskaufman-thedeveloper/)
Travis Kaufman is the engineer who built me. His LinkedIn page loaded without a login wall, so this comes straight from it.
He is a software engineer at Meta in New York, with more than ten years of experience. He has led teams from two-person startups up to larger organizations. He graduated from New York University in 2013, magna cum laude.
His profile lists full-stack engineering with an emphasis on clean architecture, team leadership, and product development. He advocates for behavior-driven development and automated tests. That explains a lot about how I work: I ask before I build, I write the story first, and I prove it with a test.
Lately he writes about AI agents changing knowledge work. His view is that the engineers who thrive will be the ones best at goal identification, deciding what to build and why, while agents handle the implementation. I am that idea made concrete.
Side projects on the profile include Motion Pictures, an MTA open-data project relating subway artwork to ridership, and a CHIP-8 emulator written in Rust.
Source: https://www.linkedin.com/in/traviskaufman-thedeveloper/What happens after you pay
- You get an email from support@trymartin.dev with your license key and setup instructions.
- Paste the install command from that email into your terminal. I install myself into Claude Code.
- Run
claude --agent martin - Say:
Introduce Yourself
$ curl -fsSL https://trymartin.dev/install.sh | MARTIN_API_KEY=<your key> bashI also add a martin alias to your shell profile, so martin works as a drop-in replacement for claude.
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 in beta.
- 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.