conduyt 1.58.1 → 1.59.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
@@ -34,6 +34,7 @@ conduyt whoami # show the authenticated user
34
34
  conduyt contacts list --limit 25 # list contacts (supports --cursor, --query)
35
35
  conduyt contacts get <id> # fetch one contact
36
36
  conduyt deals list --pipeline <id> # list deals
37
+ conduyt deals bulk-update --file updates.json # update up to 100 deals at once ([{id, stageId, value, ...}]); per-item results, exit 1 if any failed
37
38
  conduyt pipelines # list pipelines and stages
38
39
  conduyt search "acme corp" # search across the CRM
39
40
  conduyt insights summary # run an AI insight query
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { dirname, join } from "node:path";
6
6
  import { Command } from "commander";
7
7
  import { ConduytClient, buildQuery } from "./client.js";
8
8
  import { saveConfig, resolveConfig, configPath } from "./config.js";
9
- import { print, fail } from "./output.js";
9
+ import { print, fail, unwrap } from "./output.js";
10
10
  // Exit cleanly when a downstream pipe closes early (e.g. `conduyt deals list |
11
11
  // head`). Without this, the stdout write after the reader is gone throws an
12
12
  // unhandled EPIPE and prints a Node stack trace — broken-pipe is normal Unix
@@ -561,6 +561,73 @@ deals
561
561
  assertUuid(id, "deal id");
562
562
  return client.del(`/api/v1/deals/${encodeURIComponent(id)}`);
563
563
  }));
564
+ deals
565
+ .command("bulk-update")
566
+ .description("Update up to 100 deals in one request (POST /bulk/deals/update). Pass a JSON array of { id, ...editable fields (title, stageId, value, currency, status, priority, assignedTo, expectedCloseDate, lostReason, customFields) } via --updates or --file. Each item is applied on its own; prints the summary and one result per item, and exits 1 when any item failed (the full result is still printed). Rate limited to 5 requests a minute")
567
+ .option("--updates <json>", "the updates as a JSON array (or @file)")
568
+ .option("--file <path>", "read the updates JSON array from a file")
569
+ .action(async (opts) => {
570
+ try {
571
+ if (opts.updates !== undefined && opts.file !== undefined)
572
+ throw new Error("Pass either --updates or --file, not both.");
573
+ let raw;
574
+ if (opts.file !== undefined) {
575
+ if (opts.file.trim() === "")
576
+ throw new Error("--file must be a path (got an empty string).");
577
+ try {
578
+ raw = readFileSync(opts.file, "utf8");
579
+ }
580
+ catch {
581
+ throw new Error(`--file could not be read: ${opts.file}`);
582
+ }
583
+ }
584
+ else if (opts.updates !== undefined) {
585
+ raw = opts.updates.startsWith("@") ? readFileSync(opts.updates.slice(1), "utf8") : opts.updates;
586
+ }
587
+ else {
588
+ throw new Error("The updates are required. Pass --updates '<json array>' or --file <path>.");
589
+ }
590
+ let updates;
591
+ try {
592
+ updates = JSON.parse(raw);
593
+ }
594
+ catch {
595
+ throw new Error("The updates must be valid JSON (an array of { id, ...fields }).");
596
+ }
597
+ if (!Array.isArray(updates))
598
+ throw new Error("The updates must be a JSON array of { id, ...fields } objects.");
599
+ const client = new ConduytClient();
600
+ const res = await client.post("/api/v1/bulk/deals/update", { updates });
601
+ print(res);
602
+ const data = unwrap(res);
603
+ const results = data && typeof data === "object" && Array.isArray(data.results) ? data.results : null;
604
+ const sm = data && typeof data === "object" && data.summary && typeof data.summary === "object" && !Array.isArray(data.summary) ? data.summary : null;
605
+ const n = updates.length;
606
+ const shapeOk = !!sm && !!results &&
607
+ typeof sm.total === "number" && typeof sm.succeeded === "number" && typeof sm.failed === "number" &&
608
+ sm.total === n && results.length === n &&
609
+ results.every((r) => r && typeof r === "object" && typeof r.success === "boolean" && typeof r.id === "string") &&
610
+ // The ids that came back must be exactly the ids submitted (order-independent, duplicates count);
611
+ // an item sent without a string id is answered as "unknown".
612
+ JSON.stringify(updates.map((u) => (u && typeof u.id === "string" ? u.id : "unknown")).sort()) ===
613
+ JSON.stringify(results.map((r) => r.id).sort());
614
+ if (!shapeOk) {
615
+ process.stderr.write(`Error: Unexpected response shape from the API: expected data.summary {total, succeeded, failed} and ${n} data.results with id and success.\n`);
616
+ process.exitCode = 1;
617
+ return;
618
+ }
619
+ const failedItems = results.filter((r) => r.success === false);
620
+ if (failedItems.length > 0 || Number(sm.failed) > 0) {
621
+ for (const r of failedItems)
622
+ process.stderr.write(`Failed ${r.id}: ${r.error ?? "unknown error"}\n`);
623
+ process.stderr.write(`Error: ${failedItems.length || sm.failed} of ${sm.total} updates failed.\n`);
624
+ process.exitCode = 1;
625
+ }
626
+ }
627
+ catch (err) {
628
+ fail(err);
629
+ }
630
+ });
564
631
  // ---- pipelines ----
565
632
  // Default action preserves the original published `conduyt pipelines` (list)
566
633
  // invocation now that pipelines is a group — without it, commander prints help
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.58.1",
3
+ "version": "1.59.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",