{"_id":"@anosf/crm","_rev":"2-8ac5c6950a8038b2cb8a9ec1198b3e74","name":"@anosf/crm","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@anosf/crm","version":"0.1.0","keywords":["crm","agent-native","self-hosted","open-source","mcp","model-context-protocol","ai-agents","sales","sqlite","fastify","typescript","llm-tools","claude","anosf"],"author":{"name":"ANOSF — Agent Native Open Source Foundation"},"license":"MIT","_id":"@anosf/crm@0.1.0","maintainers":[{"name":"yamz8","email":"yam@kernelius.com"}],"homepage":"https://github.com/anosf/crm#readme","bugs":{"url":"https://github.com/anosf/crm/issues"},"bin":{"crm":"src/cli/main.ts"},"dist":{"shasum":"35195b40c47cc12b757f4b0962f4ff2606dd2230","tarball":"https://registry.npmjs.org/@anosf/crm/-/crm-0.1.0.tgz","fileCount":59,"integrity":"sha512-o3BX3twGUc9W0Kf8eoobMGdABN0AIZhwv1zwpg810XTyUgp/HE45uJJfJ3WXG1lEq1NuqA2jjJVk5DJZEfjxmA==","signatures":[{"sig":"MEUCIQDzmHnoI2gVHVID72t5DTQcTZNx8ubvYXVtISygKkxYhwIgHE49qeBUBiZls/F717tdOa6JDHtO90WAkQUeNk5qjIQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":441406},"type":"module","engines":{"node":">=22.18.0"},"gitHead":"6f22f48acd6e162659a15bdb57d26a24de6faba4","scripts":{"cli":"node src/cli/main.ts","dev":"node --watch src/main.ts","mcp":"node src/mcp/stdio.ts","seed":"node src/cli/main.ts seed --demo","test":"node --test --test-concurrency=1 \"src/**/*.test.ts\"","check":"npm run format:check && npm run typecheck && npm run build:web && npm run test","smoke":"node scripts/smoke.mjs","start":"node src/main.ts","backup":"node src/cli/main.ts backup","format":"prettier --write .","migrate":"node src/cli/main.ts migrate","build:web":"node scripts/build-web.mjs","selfcheck":"node src/cli/main.ts selfcheck","typecheck":"tsc --noEmit","test:watch":"node --test --watch \"src/**/*.test.ts\"","format:check":"prettier --check .","prepublishOnly":"npm run check"},"_npmUser":{"name":"yamz8","email":"yam@kernelius.com"},"repository":{"url":"git+https://github.com/anosf/crm.git","type":"git"},"_npmVersion":"10.9.8","description":"Agent-native, self-hosted CRM — REST, MCP, and CLI on one service layer with a reversible audit trail.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"zod":"^4.4.3","fastify":"^5.11.3","better-sqlite3":"^13.0.3","@fastify/cookie":"^11.1.2","@fastify/static":"^10.1.3","@fastify/rate-limit":"^11.2.0","@modelcontextprotocol/sdk":"^1.30.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.28.1","prettier":"^3.9.6","typescript":"^5.9.3","@types/node":"^22.10.0","@types/better-sqlite3":"^7.6.13"},"_npmOperationalInternal":{"tmp":"tmp/crm_0.1.0_1786294644456_0.31877167868295864","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@anosf/crm","version":"0.1.1","description":"Agent-native, self-hosted CRM — REST, MCP, and CLI on one service layer with a reversible audit trail.","license":"MIT","type":"module","engines":{"node":">=22.18.0"},"bin":{"crm":"dist/cli/main.js"},"scripts":{"dev":"node --watch src/main.ts","start":"node src/main.ts","mcp":"node src/mcp/stdio.ts","cli":"node src/cli/main.ts","build:web":"node scripts/build-web.mjs","typecheck":"tsc --noEmit","format":"prettier --write .","format:check":"prettier --check .","test":"node --test --test-concurrency=1 \"src/**/*.test.ts\"","test:watch":"node --test --watch \"src/**/*.test.ts\"","smoke":"node scripts/smoke.mjs","check":"npm run format:check && npm run typecheck && npm run build:web && npm run test","seed":"node src/cli/main.ts seed --demo","migrate":"node src/cli/main.ts migrate","backup":"node src/cli/main.ts backup","selfcheck":"node src/cli/main.ts selfcheck","prepublishOnly":"npm run check && npm run build","build":"node scripts/build.mjs"},"dependencies":{"@fastify/cookie":"^11.1.2","@fastify/rate-limit":"^11.2.0","@fastify/static":"^10.1.3","@modelcontextprotocol/sdk":"^1.30.0","better-sqlite3":"^13.0.3","fastify":"^5.11.3","zod":"^4.4.3"},"devDependencies":{"@types/better-sqlite3":"^7.6.13","@types/node":"^22.10.0","esbuild":"^0.28.1","prettier":"^3.9.6","typescript":"^5.9.3"},"keywords":["crm","agent-native","self-hosted","open-source","mcp","model-context-protocol","ai-agents","sales","sqlite","fastify","typescript","llm-tools","claude","anosf"],"homepage":"https://github.com/anosf/crm#readme","repository":{"type":"git","url":"git+https://github.com/anosf/crm.git"},"bugs":{"url":"https://github.com/anosf/crm/issues"},"author":{"name":"ANOSF — Agent Native Open Source Foundation"},"publishConfig":{"access":"public"},"_id":"@anosf/crm@0.1.1","gitHead":"8410ae739aeb37a8c7c08c4028af4f09f1ad5e58","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-T6vL5zRlOwa4eGwVI0N65OoisbcoeHSZKaLH2g6F8uwLtMdY0ucMsbbRvakCOoBfxqQE7EaCTfBkgApJiw/luw==","shasum":"c6c4e946149b3b059ae3f5e45c361b5997d86b9b","tarball":"https://registry.npmjs.org/@anosf/crm/-/crm-0.1.1.tgz","fileCount":53,"unpackedSize":376493,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDDHX9YGoo9dO3IQzjRUeNyCTitW6OJd+GrNUVrtw7HgAiEArfhKM/lnyInU/WMZIisHSDbLM4s/MMz02UDXshr5048="}]},"_npmUser":{"name":"yamz8","email":"yam@kernelius.com"},"directories":{},"maintainers":[{"name":"yamz8","email":"yam@kernelius.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/crm_0.1.1_1786295009316_0.3113400396025374"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T16:57:24.302Z","modified":"2026-08-09T17:03:29.696Z","0.1.0":"2026-08-09T16:57:24.617Z","0.1.1":"2026-08-09T17:03:29.476Z"},"bugs":{"url":"https://github.com/anosf/crm/issues"},"author":{"name":"ANOSF — Agent Native Open Source Foundation"},"license":"MIT","homepage":"https://github.com/anosf/crm#readme","keywords":["crm","agent-native","self-hosted","open-source","mcp","model-context-protocol","ai-agents","sales","sqlite","fastify","typescript","llm-tools","claude","anosf"],"repository":{"type":"git","url":"git+https://github.com/anosf/crm.git"},"description":"Agent-native, self-hosted CRM — REST, MCP, and CLI on one service layer with a reversible audit trail.","maintainers":[{"name":"yamz8","email":"yam@kernelius.com"}],"readme":"# CRM\n\n[![CI](https://github.com/anosf/crm/actions/workflows/ci.yml/badge.svg)](https://github.com/anosf/crm/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A522.18-brightgreen.svg)](https://nodejs.org)\n[![MCP](https://img.shields.io/badge/MCP-24%20tools-8b5cf6.svg)](#designed-for-agents)\n\nAn **agent-native**, self-hosted CRM, built for two kinds of users at once: **people**,\nthrough a fast web UI, and **AI agents**, through a REST API, an MCP server, and a CLI.\n\nFrom [ANOSF](https://github.com/anosf) — the Agent Native Open Source Foundation, an umbrella\nfor open software designed for AI agents from the ground up rather than retrofitted for them.\n\nIt is not a CRM with an API bolted on. Every interface calls the same service layer, so an\nagent and a human get the same permissions, the same validation, and the same audit trail —\nand any change either of them makes can be reviewed and undone.\n\n```\n┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐\n│ Web UI  │  │  REST   │  │   MCP   │  │   CLI   │\n└────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘\n     └────────────┴────────────┴────────────┘\n                        │\n              service layer  ·  one permission model\n                             ·  one audit trail\n                             ·  one set of invariants\n                        │\n                    SQLite (WAL + FTS5)\n```\n\n---\n\n## Quick start\n\n```bash\ngit clone <this repo> crm && cd crm\nnpm install\nnpm run build:web\nnpm run seed          # default pipeline + demo records (optional)\nnpm start\n```\n\nOpen <http://localhost:4000> and create the first account.\n\n### Docker\n\n```bash\ncp .env.example .env\necho \"CRM_SECRET=$(openssl rand -hex 32)\" >> .env\ndocker compose up -d\n```\n\nData lives in a named volume at `/data`. Take a consistent, verified snapshot at any time\nwithout stopping the server:\n\n```bash\ndocker compose exec crm node src/cli/main.ts backup\n# → /data/backups/crm-2026-08-08T19-56-30.db  (integrity: ok)\n```\n\nDo not back the file up with `cp` — that can miss the write-ahead log and produce a subtly\ncorrupt copy. [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) covers TLS, reverse proxies, backup\nschedules, restores, and upgrades.\n\n---\n\n## What it does\n\n**Records** — contacts, companies, deals, activities, and tasks. Every record takes arbitrary\ncustom fields in a free-form `properties` object, so you never need a schema migration to\nstore something specific to your business.\n\n**Pipelines** — deals move through configurable stages with win probabilities. Moving a deal\nupdates its stage, its won/lost status, its close date, and its timeline in one atomic step.\n\n**Timeline** — calls, emails, meetings, and notes attach to any record, with the author\nrecorded — including which agent logged what.\n\n**Search** — one full-text query across every record type (SQLite FTS5), plus filters,\nsorting, and cursor pagination on every list.\n\n**Insights** — a dashboard of counts, pipeline value, and win rate, and a *work queue* that\nanswers \"what needs attention right now\": overdue tasks, deals that have gone quiet, and\ncontacts nobody has ever contacted.\n\n**Governance** — every mutation is written to an audit log with a complete before-image, and\nany single change can be reverted through the UI or the API.\n\n---\n\n## Designed for agents\n\nMost CRMs treat automation as a second-class integration surface. Here it is the primary one.\n\n| Problem an agent hits | What this does about it |\n| --- | --- |\n| \"What can I even do here?\" | `GET /api/v1/discover` returns a capability map: every record type, its JSON Schema, its filters, its sorts, and the workflows the API is designed around. Also `/llms.txt` and `/openapi.json`. |\n| Retries create duplicates | `Idempotency-Key` on every write. The same key with the same body replays the original response; the same key with a *different* body is rejected rather than silently ignored. |\n| Guessing field names | Unknown fields are rejected with the list of accepted ones. Errors carry a `hint` written for the caller, not for a log file. |\n| Six calls to understand one record | `GET /{type}/{id}/context` returns the record, its tags, its related records, its timeline, and its open tasks in one response. |\n| Clobbering concurrent edits | Every record has a `version`. Send `If-Match: <version>` to get a 409 instead of overwriting someone. |\n| \"Can I trust it with write access?\" | Scoped tokens (`contacts:write`, `deals:read`), a full audit trail per token, and one-click revert. |\n| Half-finished imports | `POST /{type}/bulk` runs up to 200 records in one transaction, all-or-nothing or with per-row errors. |\n\n### Connect Claude Code\n\n```bash\n# Create a token from the UI (Agents & API), or:\nnpm run cli -- token create --name claude-code --scopes \"contacts:write,companies:write,deals:write,activities:write,tasks:write,insights:read\"\n\nclaude mcp add crm \\\n  --env CRM_URL=http://localhost:4000 \\\n  --env CRM_TOKEN=crm_... \\\n  -- npx @anosf/crm mcp\n```\n\nThe npm package is published as `@anosf/crm` (npm refuses `crm` as too close to an\nexisting package); the repository, image, and installed command are all still `crm`.\n\nThe MCP server exposes 24 tools (`crm_search`, `crm_get_context`, `crm_move_deal`,\n`crm_work_queue`, `crm_audit`, `crm_revert`, …), four resources, and three prompts\n(`daily_review`, `prep_for_meeting`, `review_agent_changes`).\n\nThere is also a streamable-HTTP MCP endpoint at `/mcp` for clients that prefer it.\n\n### Or just use HTTP\n\n```bash\ncurl -H \"Authorization: Bearer $CRM_TOKEN\" \\\n  \"http://localhost:4000/api/v1/deals?filter[status]=open&filter[amount__gte]=100000&sort=-amount\"\n```\n\nAmounts are integers in minor units — `150000` is `$1,500.00`. Responses also include\n`amount_decimal` and `amount_formatted` so nobody has to guess.\n\n---\n\n## It checks itself\n\n`npm run check` runs formatting, types, the web build, and 132 tests. Beyond that, the running\ninstance can diagnose itself:\n\n```bash\nnpm run cli -- selfcheck            # or GET /api/v1/system/selfcheck\n```\n\n```\n✓ migrations               Schema is up to date\n✓ sqlite_integrity         SQLite reports no corruption\n✓ foreign_keys             No dangling foreign keys\n✓ search_index             Search index matches all records\n✓ deal_stage_consistency   Every deal status matches its stage outcome\n✓ activity_attachment      Every activity is attached to a record\n! instance_secret          CRM_SECRET is unset, so sessions use a well-known key\n  → Set CRM_SECRET to a random 32-byte hex string and restart.\n```\n\nEvery non-passing check explains what to do about it, and `?repair=true` fixes the ones that\ncan be fixed safely (rebuilding the search index, purging expired sessions). A test asserts\nthat no check is allowed to report a problem without a remedy.\n\n`scripts/smoke.mjs` runs 35 end-to-end assertions against a *running* server — the listener,\ncookies, static assets, and MCP as a real network service — covering what in-process tests\ncannot.\n\n---\n\n## Commands\n\n```bash\nnpm start                 # run the server\nnpm run dev               # run with auto-restart\nnpm run build:web         # bundle the web UI (esbuild)\nnpm run check             # format + types + build + tests\nnpm test                  # tests only\nnpm run smoke             # end-to-end against a running server\nnpm run backup            # verified snapshot of the database\nnpm run selfcheck         # diagnose the instance\nnpm run cli -- --help     # admin CLI\nnpm run mcp               # stdio MCP server\n```\n\nThe CLI covers setup and operations: `migrate`, `seed --demo`, `user create`, `token create`,\n`backup`, `selfcheck --repair`, `reindex`, `search`, `list`, `overview`, `work-queue`,\n`audit`, `info`, `version`.\n\n---\n\n## Architecture\n\n```\nsrc/\n  core/        config, prefixed ULIDs, structured errors\n  db/          SQLite connection + SQL migrations\n  domain/      the whole application: schemas, store, services, permissions\n  http/        Fastify routes, OpenAPI generation, discovery\n  mcp/         MCP tools, stdio server, HTTP transport\n  cli/         admin CLI\n  web/         browser UI (no framework)\n```\n\nA few decisions worth knowing about:\n\n**One schema definition, three consumers.** Each record type is defined once in Zod. That one\ndefinition drives request validation, the generated OpenAPI document, *and* the MCP tool input\nschemas. They cannot drift apart, because there is nothing to keep in sync.\n\n**MCP calls travel through the HTTP stack.** The `/mcp` endpoint executes tools by injecting\nrequests into the same Fastify instance that serves the REST API, in-process. Authentication,\nvalidation, permissions, idempotency, and audit logging are identical by construction rather\nthan by discipline.\n\n**Authorization lives in the domain layer, not in middleware.** `assertCan(ctx, resource,\naccess)` is called by services, so an MCP tool call and a CLI command get exactly the same\nanswer as a REST request.\n\n**Archive, don't delete.** `DELETE` archives by default and is reversible. Hard deletion exists\nbut has to be asked for explicitly, and the API says so when you use it.\n\n**SQLite on purpose.** WAL mode, FTS5 search, foreign keys enforced. One file to back up, no\ndatabase server to operate. It comfortably handles the scale a self-hosted CRM actually sees.\n\n**Node runs the TypeScript directly.** No transpile step on the server — Node 22's built-in\ntype stripping runs `src/**/*.ts` as-is, in development, in tests, and in production. Only the\nbrowser bundle is built.\n\n---\n\n## Configuration\n\nEvery setting has a working default except `CRM_SECRET`, which is required in production\nand derives session and token lookup keys. See [.env.example](.env.example).\n\n## Security notes\n\n- Passwords are hashed with scrypt (N=16384), and login runs a hash comparison even for\n  unknown accounts so timing does not reveal which addresses exist.\n- Session tokens and API tokens are stored only as HMACs keyed by the instance secret.\n- Cookie-authenticated writes must send `Content-Type: application/json`, which blocks\n  cross-site form posts. Bearer-token clients are unaffected, and no endpoint mutates on GET.\n- API tokens can never exceed the role of the user who minted them, and they do not outlive\n  that account: deleting or disabling a user revokes the tokens they created, and a token\n  whose creator is gone is refused rather than falling back to a default role.\n- Webhook payloads are signed: `x-crm-signature: sha256=HMAC(secret, \"<timestamp>.<body>\")`.\n- Webhook destinations are restricted to `http(s)` and re-resolved before every delivery;\n  private, loopback, and link-local addresses are refused and redirects are not followed, so a\n  webhook cannot be turned into a port scanner or a route to cloud metadata. Set\n  `WEBHOOK_ALLOW_PRIVATE=true` if you deliver to a sibling container you trust.\n\n`npm run cli -- selfcheck` flags the two states that matter here: unrestricted (`[\"*\"]`) tokens,\nand tokens that outlived the account which created them.\n\nFound a vulnerability? Please open a security advisory rather than a public issue.\n\n## Documentation\n\n| | |\n| --- | --- |\n| [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Production checklist, TLS, backups, restores, upgrades, scaling |\n| [AGENTS.md](AGENTS.md) | How agents should use the CRM, and how to work on the codebase |\n| [CONTRIBUTING.md](CONTRIBUTING.md) | Development setup and what a good change looks like |\n| [SECURITY.md](SECURITY.md) | Reporting a vulnerability, and what is in scope |\n| [CHANGELOG.md](CHANGELOG.md) | What changed in each release |\n\n## Contributing\n\n`npm run check` must pass. New behaviour needs a test; new endpoints need to appear in the\nOpenAPI document and in `/api/v1/discover` (there are tests for both). Start with\n[CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","readmeFilename":"README.md"}