by dzhng
An open-source, headless CRM built for agents. No UI. No dashboard. Just a CLI and a FUSE-mounted filesystem.
# Add to your Claude Code skills
git clone https://github.com/dzhng/crm.cliLast scanned: 6/22/2026
{
"issues": [
{
"type": "npm-audit",
"message": "@esbuild-kit/core-utils: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "@esbuild-kit/esm-loader: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "drizzle-kit: Vulnerability found",
"severity": "medium"
},
{
"type": "npm-audit",
"message": "esbuild: esbuild enables any website to send any requests to the development server and read the response",
"severity": "medium"
},
{
"file": "README.md",
"line": 51,
"type": "remote-install",
"message": "Install command (remote install script piped to a shell — review the source before running): \"curl -fsSL https://raw.githubusercontent.com/dzhng/crm.cli/main/install.sh | sh\"",
"severity": "low"
},
{
"file": "skills/SKILL.md",
"line": 4,
"type": "remote-install",
"message": "Install command (remote install script piped to a shell — review the source before running): \"curl -fsSL https://raw.githubusercontent.com/dzhng/crm.cli/main/install.sh | sh\"",
"severity": "medium"
}
],
"status": "PASSED",
"scannedAt": "2026-06-22T09:50:47.164Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}crm.cli is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by dzhng. An open-source, headless CRM built for agents. No UI. No dashboard. Just a CLI and a FUSE-mounted filesystem. It has 100 GitHub stars.
Yes. crm.cli passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/dzhng/crm.cli" and add it to your Claude Code skills directory (see the Installation section above).
crm.cli is primarily written in TypeScript. It is open-source under dzhng on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh crm.cli against similar tools.
No comments yet. Be the first to share your thoughts!

A headless, CLI-first CRM for AI native companies. Contacts, deals, and pipeline in a single SQLite file — queryable from your terminal, composable with Unix tools, and mountable as a virtual filesystem so any tool that reads files (Claude Code, Codex, grep, jq, vim) has full CRM access without any integration.
No server. No Docker. No accounts. No GUI. Just npm install -g @dzhng/crm.cli and go.
Created by Duet — a cloud agent workspace with persistent AI. Set up crm.cli in your own private cloud computer and run it with Claude Code or Codex — no local setup required. Try Duet →
Existing CRMs are GUI-first tools built for sales teams. If you're a technical founder, indie hacker, or engineer running BD, you're probably managing contacts in a spreadsheet you grep through. crm.cli is built for you.
Your CRM is a filesystem. Mount it with crm mount ~/crm and every tool that reads files — AI agents, shell scripts, editors — gets full CRM access for free. No MCP servers, no API keys, no integration code. The filesystem is the universal API.
crm mount ~/crm
ls ~/crm/contacts/
cat ~/crm/contacts/jane-doe.json | jq .name
# Point Claude Code at ~/crm and ask it to research your pipeline
Deep data normalization. A CSV has zero setup cost. crm.cli justifies its existence with structured intelligence: E.164 phone normalization (look up by any format), website normalization, social handle extraction (paste a LinkedIn URL, it stores the handle), entity merge with reference relinking, and fuzzy duplicate detection. This is what a spreadsheet can never provide.
crm contact add --name "Jane Doe" \
--email jane@acme.com \
--phone "+1-212-555-1234" \
--linkedin linkedin.com/in/janedoe \
--company Acme
# Phone stored as E.164, LinkedIn URL → handle, company auto-linked
Pipe-friendly by default. Every command outputs structured data. Pipe to jq, grep, awk, or feed into scripts. --format json on everything.
crm deal list --stage qualified --format json | jq '.[] | .value'
crm find "that fintech CTO from London"
crm report pipeline
crm dupes --threshold 0.5
npm install -g @dzhng/crm.cli
# or: bun install -g @dzhng/crm.cli
# Or install the compiled binary
curl -fsSL https://raw.githubusercontent.com/dzhng/crm.cli/main/install.sh | sh
Point your agent at the skills folder to give it full CRM knowledge — install instructions, every command, the filesystem interface, and tips for agents:
# Claude Code / Duet — install the skill from GitHub
claude skills add https://github.com/dzhng/crm.cli/tree/main/skills
# Or copy skills/SKILL.md into your agent's skills directory
cp skills/SKILL.md ~/.your-agent/skills/crm-cli/SKILL.md
Everything lives in a single SQLite file. Default: ~/.crm/crm.db.
crm --db ./my-project.db contact list # use a specific database
export CRM_DB=./team.db # or set via env var
No server. No Docker. No accounts. Back it up by copying the file.
Config is loaded from crm.toml. Resolution order (first match wins):
--config <path> flag (explicit)CRM_CONFIG env var./crm.toml → ../crm.toml → ../../crm.toml → ... → /crm.toml~/.crm/config.toml (global fallback)This means you can drop a crm.toml in your project root and it applies to everyone working in that directory — just like .gitignore or biome.jsonc.
# Project-scoped config
echo '[pipeline]
stages = ["discovery", "demo", "trial", "closed-won", "closed-lost"]' > ./crm.toml
# Global config (applies everywhere unless overridden)
mkdir -p ~/.crm
cat > ~/.crm/config.toml << 'EOF'
[database]
path = "~/.crm/crm.db"
[pipeline]
stages = ["lead", "qualified", "proposal", "negotiation", "closed-won", "closed-lost"]
won_stage = "closed-won"
lost_stage = "closed-lost"
[defaults]
format = "table" # table | json | csv | tsv | ids
[phone]
default_country = "US" # ISO 3166-1 alpha-2; for numbers without country code
display = "international" # international | national | e164
[hooks]
# pre-contact-add = "echo 'adding contact'"
# post-contact-add = "echo 'contact added'"
# pre-deal-stage-change = "echo 'stage changing'"
[mount]
default_path = "~/crm" # where `crm mount` mounts by default
readonly = false # set true to prevent writes via FUSE
max_recent_activity = 10 # activities shown per entity in FUSE
search_limit = 20 # max results from search/find
EOF
Settings in a closer crm.toml override the global config. The --config flag overrides everything.
| Flag | Env Var | Description |
|---|---|---|
--db <path> |
CRM_DB |
Path to SQLite database |
--format <fmt> |
CRM_FORMAT |
Output format: table, json, csv, tsv, ids |
--no-color |
NO_COLOR |
Disable colored output |
--config <path> |
CRM_CONFIG |
Path to config file |
--version |
— | Print version |
People you interact with.
crm contact addcrm contact add --name "Jane Doe" --email jane@acme.com
crm contact add --name "Jane Doe" --email jane@acme.com --email jane.doe@gmail.com --phone "+1-212-555-1234" --phone "+44-20-7946-0958" --company Acme --company "Acme Ventures" --tag hot-lead --tag enterprise
crm contact add --name "Jane Doe" --email jane@acme.com --linkedin janedoe --x janedoe --set title=CTO --set source=conference --set notes="Met at SaaStr"
crm contact add --name "Jane Doe" --linkedin https://linkedin.com/in/janedoe # URL input also works — handle is extracted
| Flag | Required | Description |
|---|---|---|
--name |
yes | Full name |
--email |
no | Email address (repeatable — multiple allowed) |
--phone |
no | Phone number (repeatable — multiple allowed) |
--company |
no | Company name (repeatable — links to existing or creates stub) |
--tag |
no | Tag (repeatable — multiple allowed) |
--linkedin |
no | LinkedIn handle or URL (stored as handle, e.g. janedoe) |
--x |
no | X / Twitter handle or URL (stored as handle, e.g. janedoe) |
--bluesky |
no | Bluesky handle or URL (stored as handle, e.g. janedoe.bsky.social) |
--telegram |
no | Telegram handle or URL (stored as handle, e.g. janedoe) |
--set |
no | Custom field as key=value (repeatable — multiple allowed) |
Prints the created contact ID to stdout.
Social handles enforce uniqueness — no two contacts can share the same handle on a given platform. All of these input formats are accepted and normalized to the raw handle:
| Input | Stored as |
|---|---|
janedoe |
janedoe |
@janedoe |
janedoe |
https://linkedin.com/in/janedoe |
janedoe |
linkedin.com/in/janedoe |
janedoe |
www.linkedin.com/in/janedoe |
janedoe |
x.com/janedoe |
janedoe |
twitter.com/janedoe |
janedoe |
bsky.app/profile/user.bsky.social |
user.bsky.social |
t.me/janedoe |
janedoe |
The same normalization applies to lookups, edits, and duplicate detection — crm contact show x.com/janedoe matches a contact stored with handle janedoe.
crm contact listcrm contact list
crm contact list --tag hot-lead
crm contact list --company Acme --format json
crm contact list --filter "title~=CTO AND source=conference"
crm contact list --limit 10 --offset 20
| Flag | Description |
|---|---|
--tag |
Filter by tag (multiple = AND) |
--company |
Filter by company name (matches any linked company) |
--filter |
Filter expression (see Filtering) — works on both core and custom fields |
--sort |
Sort field: name, created, updated |
--reverse |
Reverse sort order |
--limit |
Max results (default: no limit) |
--offset |
Skip N results |
crm contact show <id-or-email-or-phone-or-handle>crm contact show ct_01J8Z...
crm contact show jane@acme.com
crm contact show "+1-212-555-1234"
crm contact show janedoe # matches any social handle
crm contact show linkedin.com/in/janedoe # URL also works — extracts handle
Accepts ID, any email, any phone number, or any social handle (LinkedIn, X, Bluesky, Telegram). URLs are also accepted — the handle is extracted before lookup. Shows full contact details including linked companies, deals