@dzhng/crm.cli 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -17
  3. package/dist/cli.js +30 -5
  4. package/package.json +1 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  ![crm.cli — Your CRM is a filesystem](assets/cover.png)
4
4
 
5
- **A headless, CLI-first CRM for developers who do sales.** 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.
5
+ **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.
6
6
 
7
7
  No server. No Docker. No accounts. No GUI. Just `npm install -g @dzhng/crm.cli` and go.
8
8
 
9
- > **Sponsored by [Duet](https://duet.so)** — 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 →](https://duet.so)
9
+ > **Created by [Duet](https://duet.so)** — 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 →](https://duet.so)
10
10
 
11
11
  ## Why crm.cli
12
12
 
@@ -207,12 +207,12 @@ Accepts ID, any email, any phone number, or any social handle (LinkedIn, X, Blue
207
207
  #### `crm contact edit <id-or-email-or-phone-or-handle>`
208
208
 
209
209
  ```bash
210
- crm contact edit jane@acme.com --name "Jane Smith"
210
+ crm contact edit jane@acme.com --name "Jane Smith" # by email
211
+ crm contact edit "+1-212-555-1234" --add-tag vip # by phone
212
+ crm contact edit janedoe --set title=CEO # by social handle (LinkedIn, X, etc.)
211
213
  crm contact edit ct_01J8Z... --add-email jane.personal@gmail.com --rm-email old@acme.com
212
214
  crm contact edit ct_01J8Z... --add-phone "+44-20-7946-0958" --rm-phone "+1-310-555-9876"
213
215
  crm contact edit ct_01J8Z... --add-company "Acme Ventures" --rm-company "Old Corp"
214
- crm contact edit ct_01J8Z... --set title=CEO --set source=referral
215
- crm contact edit jane@acme.com --add-tag vip --rm-tag cold
216
216
  ```
217
217
 
218
218
  | Flag | Description |
@@ -236,16 +236,20 @@ crm contact edit jane@acme.com --add-tag vip --rm-tag cold
236
236
  #### `crm contact rm <id-or-email-or-phone-or-handle>`
237
237
 
238
238
  ```bash
239
- crm contact rm jane@acme.com
240
- crm contact rm "+1-212-555-1234" --force # skip confirmation
239
+ crm contact rm jane@acme.com # by email
240
+ crm contact rm "+1-212-555-1234" --force # by phone (skip confirmation)
241
+ crm contact rm janedoe # by social handle (LinkedIn, X, etc.)
241
242
  ```
242
243
 
243
244
  Prompts for confirmation unless `--force` is passed. Removes the contact and unlinks from deals/companies (does not delete linked entities).
244
245
 
245
- #### `crm contact merge <id> <id>`
246
+ #### `crm contact merge <ref> <ref>`
246
247
 
247
248
  ```bash
248
- crm contact merge ct_01J8Z... ct_02K9A...
249
+ crm contact merge ct_01J8Z... ct_02K9A... # by ID
250
+ crm contact merge jane@acme.com jane.doe@gmail.com # by email
251
+ crm contact merge "+1-212-555-1234" "+44-20-7946-0958" # by phone
252
+ crm contact merge janedoe janetdoe # by social handle
249
253
  ```
250
254
 
251
255
  Merges two contacts. Keeps the first, absorbs data from the second. Emails, phones, companies, tags, custom fields, and activity history are combined. Deals linked to the second contact are relinked to the first. The first contact's name and social handles take priority. Prints the surviving contact ID.
@@ -291,9 +295,9 @@ crm company list --tag enterprise --sort name
291
295
  #### `crm company show <id-or-website-or-phone>`
292
296
 
293
297
  ```bash
294
- crm company show acme.com
295
- crm company show co_01J8Z...
296
- crm company show "+1-212-555-1234"
298
+ crm company show acme.com # by website
299
+ crm company show co_01J8Z... # by ID
300
+ crm company show "+1-212-555-1234" # by phone
297
301
  ```
298
302
 
299
303
  Accepts ID, any stored website, or any phone number. Shows company details plus all linked contacts and deals.
@@ -301,7 +305,8 @@ Accepts ID, any stored website, or any phone number. Shows company details plus
301
305
  #### `crm company edit <id-or-website-or-phone>`
302
306
 
303
307
  ```bash
304
- crm company edit acme.com --name "Acme Inc" --set industry=Fintech
308
+ crm company edit acme.com --name "Acme Inc" --set industry=Fintech # by website
309
+ crm company edit "+1-212-555-1234" --add-tag enterprise # by phone
305
310
  crm company edit co_01J8Z... --add-website acme.co.uk --add-phone "+44-20-7946-0958"
306
311
  crm company edit acme.com --rm-website old-acme.com --rm-phone "+1-415-555-0000"
307
312
  ```
@@ -320,12 +325,20 @@ crm company edit acme.com --rm-website old-acme.com --rm-phone "+1-415-555-0000"
320
325
 
321
326
  #### `crm company rm <id-or-website-or-phone>`
322
327
 
328
+ ```bash
329
+ crm company rm acme.com # by website
330
+ crm company rm "+1-212-555-1234" --force # by phone (skip confirmation)
331
+ crm company rm co_01J8Z... # by ID
332
+ ```
333
+
323
334
  Prompts for confirmation unless `--force` is passed. Unlinks contacts and deals but does not delete them.
324
335
 
325
- #### `crm company merge <id> <id>`
336
+ #### `crm company merge <ref> <ref>`
326
337
 
327
338
  ```bash
328
- crm company merge co_01J8Z... co_02K9A...
339
+ crm company merge co_01J8Z... co_02K9A... # by ID
340
+ crm company merge acme.com acme.co.uk # by website
341
+ crm company merge "+1-212-555-1234" "+44-20-7946-0958" # by phone
329
342
  ```
330
343
 
331
344
  Merges two companies. Keeps the first, absorbs data from the second. Websites, phones, tags, and custom fields are combined. All contacts and deals linked to the second company are relinked to the first. The first company's name takes priority. Prints the surviving company ID.
@@ -1299,9 +1312,9 @@ GitHub Actions pipeline:
1299
1312
 
1300
1313
  ---
1301
1314
 
1302
- ## Sponsor
1315
+ ## Created by Duet
1303
1316
 
1304
- crm.cli is sponsored by **[Duet](https://duet.so)** — a cloud agent workspace where every user gets a private cloud computer with a persistent, always-on AI agent. Set up crm.cli in your own Duet workspace and run it with Claude Code or Codex in the cloud — no local setup required.
1317
+ crm.cli is created by **[Duet](https://duet.so)** — a cloud agent workspace where every user gets a private cloud computer with a persistent, always-on AI agent. Set up crm.cli in your own Duet workspace and run it with Claude Code or Codex in the cloud — no local setup required.
1305
1318
 
1306
1319
  [Try Duet &rarr;](https://duet.so)
1307
1320
 
package/dist/cli.js CHANGED
@@ -1146,6 +1146,29 @@ function levenshtein(a, b) {
1146
1146
  }
1147
1147
  return dp[la][lb];
1148
1148
  }
1149
+ function diceCoefficient(a, b) {
1150
+ if (a === b) {
1151
+ return 1;
1152
+ }
1153
+ if (a.length < 2 || b.length < 2) {
1154
+ return 0;
1155
+ }
1156
+ const bigrams = (s) => {
1157
+ const m = new Map;
1158
+ for (let i = 0;i < s.length - 1; i++) {
1159
+ const bg = s.slice(i, i + 2);
1160
+ m.set(bg, (m.get(bg) || 0) + 1);
1161
+ }
1162
+ return m;
1163
+ };
1164
+ const aBi = bigrams(a);
1165
+ const bBi = bigrams(b);
1166
+ let overlap = 0;
1167
+ for (const [bg, count] of aBi) {
1168
+ overlap += Math.min(count, bBi.get(bg) || 0);
1169
+ }
1170
+ return 2 * overlap / (a.length - 1 + b.length - 1);
1171
+ }
1149
1172
 
1150
1173
  // src/db.ts
1151
1174
  var SCHEMA_SQL = `
@@ -2505,8 +2528,9 @@ function contactDupeReasons(a, b) {
2505
2528
  const bName = (b.name || "").toLowerCase();
2506
2529
  const nameDistance = levenshtein(aName, bName);
2507
2530
  const maxLen = Math.max(aName.length, bName.length);
2508
- const nameSimilarity = maxLen > 0 ? 1 - nameDistance / maxLen : 0;
2509
- if (nameSimilarity >= 0.5) {
2531
+ const levSimilarity = maxLen > 0 ? 1 - nameDistance / maxLen : 0;
2532
+ const nameSimilarity = Math.max(levSimilarity, diceCoefficient(aName, bName));
2533
+ if (nameSimilarity >= 0.6) {
2510
2534
  reasons.push("similar name");
2511
2535
  }
2512
2536
  const aEmails = safeJSON(a.emails);
@@ -2574,8 +2598,9 @@ function companyDupeReasons(a, b) {
2574
2598
  const bName = (b.name || "").toLowerCase();
2575
2599
  const nameDistance = levenshtein(aName, bName);
2576
2600
  const maxLen = Math.max(aName.length, bName.length);
2577
- const nameSimilarity = maxLen > 0 ? 1 - nameDistance / maxLen : 0;
2578
- if (nameSimilarity >= 0.5) {
2601
+ const levSimilarity = maxLen > 0 ? 1 - nameDistance / maxLen : 0;
2602
+ const nameSimilarity = Math.max(levSimilarity, diceCoefficient(aName, bName));
2603
+ if (nameSimilarity >= 0.6) {
2579
2604
  reasons.push("similar name");
2580
2605
  }
2581
2606
  const aWebsites = safeJSON(a.websites);
@@ -5447,7 +5472,7 @@ if (process.argv[1]?.endsWith("fuse-daemon.ts")) {
5447
5472
 
5448
5473
  // src/cli.ts
5449
5474
  var program = new Command;
5450
- program.name("crm").description("Headless CLI-first CRM").version("0.3.4");
5475
+ program.name("crm").description("Headless CLI-first CRM").version("0.3.6");
5451
5476
  program.exitOverride();
5452
5477
  registerContactCommands(program);
5453
5478
  registerCompanyCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhng/crm.cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Headless CLI-first CRM",
5
5
  "author": "David Zhang",
6
6
  "license": "MIT",