conduyt 1.76.0 → 1.77.0

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.
package/README.md CHANGED
@@ -35,6 +35,8 @@ conduyt contacts list --limit 25 # list contacts (supports --cursor, --qu
35
35
  conduyt contacts get <id> # fetch one contact (carries `revision`, `hasOutboundSms` and `hasOutboundEmail`: a proven send only)
36
36
  conduyt contacts update <id> --first Ann --expected-revision <n> # PATCH; a contact changed since revision n is refused: 409 contact_changed, exit 1, code and current revision printed
37
37
  conduyt contacts merge <primaryId> <secondaryId...> [--confirm [--yes]] # DESTRUCTIVE: a dry run by default (prints the impact); --confirm shows it, asks y/N, then merges (the losers are deleted); --yes skips the question; moved counts 409 and 503 merge_contacts_busy / merge_budget_exhausted + Retry-After exit 1, never retried
38
+ conduyt contacts duplicates <id> # GET /contacts/{id}/duplicate-proposals; prints { proposals, heldIdentifiers } (open and not_sure possible duplicates; read-only)
39
+ conduyt contacts duplicates decide <id> <proposalId> --as not_sure|different|merge # POST the decision; not_sure keeps both visible, different is final, merge only records a pair ALREADY merged (never merges); 409 already_decided / not_merged and 403 other_record_not_editable exit 1
38
40
  conduyt contacts collaborators add <contactId> <userId> [--expected-revision <n>] # POST /contacts/{id}/collaborators; prints { added, revision }
39
41
  conduyt contacts collaborators remove <contactId> <userId> [--expected-revision <n>] # DELETE with a body; prints { removed, revision }; 404 when not a collaborator
40
42
  conduyt chat channels # GET /chat/channels: your channels; also CREATES your auto-channel memberships the first time
package/dist/index.js CHANGED
@@ -255,6 +255,28 @@ contacts
255
255
  assertUuid(id, "contact id");
256
256
  return client.del(`/api/v1/contacts/${encodeURIComponent(id)}`);
257
257
  }));
258
+ // Identity rules v2 (board #69, app main 61bf61876): the possible-duplicate lines on a contact, and deciding one.
259
+ const PROPOSAL_DECISIONS = ["not_sure", "different", "merge"];
260
+ const duplicates = contacts
261
+ .command("duplicates <id>")
262
+ .description("List the possible duplicates of contact <id> (GET /contacts/{id}/duplicate-proposals, contacts:view, read-only): prints { proposals, heldIdentifiers }. Each proposal has id (the <proposalId> for `contacts duplicates decide`), status (open | not_sure), rule, raisedHere and `other` { id, name, createdAt, visible, editable }; a record you cannot view is \"a contact you cannot view\" with no id. heldIdentifiers are emails or phones this record was not given because another record owns them (value-free). Decide a pair with `conduyt contacts duplicates decide <id> <proposalId> --as not_sure|different|merge`")
263
+ .action(run(async (client, id) => {
264
+ assertUuid(id, "contact id");
265
+ return client.get(`/api/v1/contacts/${encodeURIComponent(id)}/duplicate-proposals`);
266
+ }));
267
+ duplicates
268
+ .command("decide <id> <proposalId>")
269
+ .description("Record a decision on one possible-duplicate pair (POST /contacts/{id}/duplicate-proposals/{proposalId}; contacts:edit on BOTH records). --as is required, exactly one of: not_sure (keep both records visible; the pair stays listed and can be decided again), different (the two are different people: never shown again, FINAL), merge (record that the pair is ALREADY merged; this command NEVER merges: run `conduyt contacts merge` first, else 409 not_merged). Prints { id, status, decidedAt }. Refusals exit 1 with the status, message and code and are never retried: 409 already_decided, 409 not_merged, 403 other_record_not_editable, 404, 400")
270
+ .option("--as <decision>", "not_sure | different | merge (required)")
271
+ .action(run(async (client, id, proposalId, opts) => {
272
+ assertUuid(id, "contact id");
273
+ assertUuid(proposalId, "proposal id");
274
+ if (opts.as === undefined)
275
+ throw new Error(`--as is required: one of ${PROPOSAL_DECISIONS.join(", ")}.`);
276
+ if (!PROPOSAL_DECISIONS.includes(opts.as))
277
+ throw new Error(`--as must be exactly one of ${PROPOSAL_DECISIONS.join(", ")} (got "${opts.as}").`);
278
+ return client.post(`/api/v1/contacts/${encodeURIComponent(id)}/duplicate-proposals/${encodeURIComponent(proposalId)}`, { decision: opts.as });
279
+ }));
258
280
  contacts
259
281
  .command("merge <primaryId> <secondaryIds...>")
260
282
  .description("DESTRUCTIVE merge of duplicate contacts (POST /contacts/merge; owner or admin): <primaryId> survives and receives the records of every <secondaryIds> contact, which are then DELETED (no undelete). By default a DRY RUN: nothing changes, it prints the impact { primary, secondaries, counts, totalRecords, guardCounts, filledFields, identifierOverflow, blockers }. --confirm repeats the dry run, shows the impact (the records that move, and separately the guard counts: chat messages, quick notes, identifiers, outbox rows, which are not in the records total), asks y/N and then merges, sending the values that dry run returned (expectedCounts, expectedGuardCounts, expectedIdentifiers). LOSER ORDER: losers are merged in the order given: for each field the survivor lacks, the first loser in this order with a value wins; reorder to change precedence; the dry run's filledFields shows which fields will be filled. WHAT IS PINNED: the server refuses (409) if the record counts, guard counts or identifier plan moved; other field edits made after this preview (a loser's name, company, source, address) are not detected; --yes skips the question (scripts; needs --confirm). A refusal exits 1 with the status, the API's message and its code and is NEVER retried: 409 \"Merge impact changed\" (preview again), 409 queued work, 400 / 404, and 503 merge_contacts_busy / merge_budget_exhausted with `[Retry-After: <N>s]` (nothing changed: wait that long, then run again).")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.76.0",
3
+ "version": "1.77.0",
4
4
  "description": "Command-line interface for Conduyt CRM — manage contacts, deals, pipelines, and run insight queries from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",