conduyt 1.43.0 → 1.44.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.
Files changed (2) hide show
  1. package/dist/index.js +136 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1305,6 +1305,142 @@ automations
1305
1305
  return client.get(path);
1306
1306
  }));
1307
1307
  // ---- companies ----
1308
+ // ─── Run Batch (#80) ───────────────────────────────────────────────────────
1309
+ const batch = program.command("batch").description("Run Batch: one logged task on many contacts (enroll, delete, add-tag, remove-tag, assign, distribute, set-source, set-status), with progress and cancel; admins and owners only");
1310
+ batch
1311
+ .command("create")
1312
+ .description("Queue a Run Batch. Pick the contacts with --ids, or freeze EVERY contact a list filter matches with the same flags as `contacts list` plus --expected (the total the list showed; the server refuses with 409 when the set moved). One task per run: enroll (--workflow), delete, add-tag/remove-tag (--tag-id), assign (--user), distribute (--users, round robin), set-source (--source), set-status (--status). No undo; `batch cancel` stops a run.")
1313
+ .requiredOption("--name <name>", "a name for the run")
1314
+ .requiredOption("--task <task>", "enroll | delete | add-tag | remove-tag | assign | distribute | set-source | set-status")
1315
+ .option("--workflow <automationId>", "enroll: the published workflow id")
1316
+ .option("--tag-id <tagId>", "add-tag / remove-tag: the tag id")
1317
+ .option("--user <userId>", "assign: one rep")
1318
+ .option("--users <userIds>", "distribute: comma-separated rep ids (2–50), rotated in order")
1319
+ .option("--source <source>", "set-source: the new lead source (overwrites)")
1320
+ .option("--status <status>", "set-status: one of the account's lead statuses")
1321
+ .option("--comment <text>", "why this run (stored with it)")
1322
+ .option("--ids <contactIds>", "comma-separated contact ids (instead of a filter)")
1323
+ .option("--search <text>", "filter: free-text search")
1324
+ .option("--tag <name>", "filter: one tag")
1325
+ .option("--tags <names>", "filter: comma-separated tags")
1326
+ .option("--filter-source <source>", "filter: lead source")
1327
+ .option("--assigned-to <userId>", "filter: assignee user id")
1328
+ .option("--smart-view <id>", "filter: Smart View id")
1329
+ .option("--smart-list <id>", "filter: Smart List id")
1330
+ .option("--import-id <id>", "filter: import job id")
1331
+ .option("--master-status <statuses>", "filter: comma-separated lead statuses to include")
1332
+ .option("--filter <json>", "filter: a JSON object of list query parameters (merged over the filter flags)")
1333
+ .option("--expected <n>", "filter: the total the list showed for this filter (required with a filter)")
1334
+ .action(run(async (client, opts) => {
1335
+ const TASKS = { enroll: "enroll", delete: "delete", "add-tag": "add_tag", "remove-tag": "remove_tag", assign: "assign", distribute: "assign", "set-source": "set_source", "set-status": "set_status" };
1336
+ const task = String(opts.task ?? "");
1337
+ const operation = TASKS[task];
1338
+ if (!operation)
1339
+ throw new Error("--task must be one of: enroll, delete, add-tag, remove-tag, assign, distribute, set-source, set-status");
1340
+ const params = {};
1341
+ if (task === "add-tag" || task === "remove-tag") {
1342
+ if (!opts.tagId)
1343
+ throw new Error(`--tag-id is required for ${task}`);
1344
+ params.tagId = opts.tagId;
1345
+ }
1346
+ else if (task === "assign") {
1347
+ if (!opts.user)
1348
+ throw new Error("--user is required for assign (use distribute with --users for a pool)");
1349
+ params.userId = opts.user;
1350
+ }
1351
+ else if (task === "distribute") {
1352
+ const users = csvArg(opts.users ?? "", "--users");
1353
+ if (users.length < 2)
1354
+ throw new Error("--users needs at least 2 rep ids for distribute");
1355
+ params.userIds = users;
1356
+ }
1357
+ else if (task === "set-source") {
1358
+ if (!opts.source?.trim())
1359
+ throw new Error("--source is required for set-source");
1360
+ params.source = opts.source.trim();
1361
+ }
1362
+ else if (task === "set-status") {
1363
+ if (!opts.status?.trim())
1364
+ throw new Error("--status is required for set-status");
1365
+ params.status = opts.status.trim();
1366
+ }
1367
+ else if (task === "enroll" && !opts.workflow) {
1368
+ throw new Error("--workflow is required for enroll");
1369
+ }
1370
+ const body = { name: opts.name, operation, ...(task === "enroll" ? { automationId: opts.workflow } : {}), ...(Object.keys(params).length ? { params } : {}), ...(opts.comment ? { comment: opts.comment } : {}) };
1371
+ if (opts.ids) {
1372
+ body.contactIds = csvArg(opts.ids, "--ids");
1373
+ }
1374
+ else {
1375
+ const filter = {};
1376
+ if (typeof opts.search === "string")
1377
+ filter.search = opts.search;
1378
+ if (typeof opts.tag === "string")
1379
+ filter.tag = opts.tag;
1380
+ if (typeof opts.tags === "string")
1381
+ filter.tags = opts.tags;
1382
+ if (typeof opts.filterSource === "string")
1383
+ filter.source = opts.filterSource;
1384
+ if (typeof opts.assignedTo === "string")
1385
+ filter.assigned_to = opts.assignedTo;
1386
+ if (typeof opts.smartView === "string")
1387
+ filter.smart_view = opts.smartView;
1388
+ if (typeof opts.smartList === "string")
1389
+ filter.smartListId = opts.smartList;
1390
+ if (typeof opts.importId === "string")
1391
+ filter.import_id = opts.importId;
1392
+ if (typeof opts.masterStatus === "string")
1393
+ filter.master_status = opts.masterStatus.split(",").map((s) => s.trim()).filter(Boolean);
1394
+ if (typeof opts.filter === "string") {
1395
+ const parsed = jsonArg(opts.filter, "--filter");
1396
+ for (const [k, v] of Object.entries(parsed)) {
1397
+ if (typeof v === "string" || (Array.isArray(v) && v.every((x) => typeof x === "string")))
1398
+ filter[k] = v;
1399
+ else
1400
+ throw new Error(`--filter.${k} must be a string or an array of strings.`);
1401
+ }
1402
+ }
1403
+ const hasValue = Object.values(filter).some((v) => (Array.isArray(v) ? v.some((x) => x.trim()) : v.trim()));
1404
+ if (!hasValue)
1405
+ throw new Error("Give --ids, or at least one filter value; a run over every contact is not a bulk action.");
1406
+ const expected = Number(opts.expected);
1407
+ if (!Number.isInteger(expected) || expected < 1)
1408
+ throw new Error("--expected must be the positive whole number the list showed for this filter.");
1409
+ body.filter = filter;
1410
+ body.expectedTotal = expected;
1411
+ }
1412
+ return client.post("/api/v1/batch-operations", body);
1413
+ }));
1414
+ batch
1415
+ .command("list")
1416
+ .description("List Run Batch runs (newest first) with their task, status and progress")
1417
+ .option("--status <status>", "queued | running | completed | cancelled | failed")
1418
+ .option("--page <n>", "page number", "1")
1419
+ .option("--limit <n>", "rows per page (max 50)", "20")
1420
+ .action(run(async (client, opts) => {
1421
+ const qp = new URLSearchParams({ page: opts.page, limit: opts.limit });
1422
+ if (opts.status)
1423
+ qp.set("status", opts.status);
1424
+ return client.get(`/api/v1/batch-operations?${qp.toString()}`);
1425
+ }));
1426
+ batch
1427
+ .command("get <id>")
1428
+ .description("One run with its per-contact items (success / error / skipped, with the reason)")
1429
+ .option("--item-status <status>", "all | pending | success | error | skipped", "all")
1430
+ .option("--page <n>", "item page", "1")
1431
+ .option("--limit <n>", "items per page (max 50)", "20")
1432
+ .action(run(async (client, id, opts) => {
1433
+ assertUuid(id);
1434
+ const qp = new URLSearchParams({ itemStatus: opts.itemStatus, page: opts.page, limit: opts.limit });
1435
+ return client.get(`/api/v1/batch-operations/${id}?${qp.toString()}`);
1436
+ }));
1437
+ batch
1438
+ .command("cancel <id>")
1439
+ .description("Stop a queued or running run; contacts already processed stay as they are (no undo)")
1440
+ .action(run(async (client, id) => {
1441
+ assertUuid(id);
1442
+ return client.post(`/api/v1/batch-operations/${id}/cancel`, {});
1443
+ }));
1308
1444
  const companies = program.command("companies").description("Manage companies");
1309
1445
  companies
1310
1446
  .command("list")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.43.0",
3
+ "version": "1.44.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",