conduyt 1.78.0 → 1.79.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
@@ -47,6 +47,7 @@ conduyt chat mark-read <channelId> <messageId> # readUpTo: marker moves to tha
47
47
  conduyt notifications list [--page n] [--per-page 1-200] # prints { data, meta }: meta.latestByType = { type: { latest, count } } over the last 24 h, next to unreadCount / unreadByType
48
48
  conduyt deals list --pipeline <id> # list deals
49
49
  conduyt deals list --health-summary --pipeline <id> # Pipeline health strip for the filtered set (totalOpenDeals, atRiskValue, checks[]); add --health-check stale --health-limit 20 for one check's deals, --health-cursor <nextCursor> to page, --health-rows-only for just the page
50
+ conduyt deals list --board-snapshot --pipeline <id> --board-columns <stageId>[:<limit>],... # every named column of one pipeline from ONE database snapshot (a moved deal is in exactly one column); prints { columns: [{ stageId, limit, deals }], meta: { board_snapshot: true } }; --page / --all / --stage / --stage-name / --health-* refused before any request; 503 board_snapshot_busy or gate_busy exits 1 with Retry-After
50
51
  conduyt deals health --pipeline <id> --check needs_action # GET /deals/inspection: the six checks over open deals; --check adds its deals, --cursor/--offset pages
51
52
  conduyt deals health-fix <dealId> --check stale --expected-updated-at <iso> --stage-id <uuid> # Pipeline health quick fix (PATCH /deals/{id} with checkOrigin): stale = --stage-id, unassigned = --assigned, missing_close_date = --close-date, missing_contact = --contact; 409 deal_changed / deal_check_cleared and 400 health_fix_mismatch exit 1 verbatim, never retried
52
53
  conduyt deals health-next-action <dealId> --check needs_action --expected-updated-at <iso> --title "Call back" --idempotency-key <key> # "Set next action" (POST /tasks with checkOrigin): one non-done quick-fix task per deal, 409 deal_has_next_action / deal_changed / deal_check_cleared exit 1 verbatim
package/dist/index.js CHANGED
@@ -516,6 +516,38 @@ function rejectBlankFlags(flags) {
516
516
  }
517
517
  }
518
518
  }
519
+ /** GET /deals board_snapshot=true limits (the app's lib/deal-board-snapshot-limits, contract at 5729bc7b4). */
520
+ const BOARD_SNAPSHOT_MAX_COLUMNS = 50;
521
+ const BOARD_SNAPSHOT_MAX_COLUMN_ROWS = 1000;
522
+ const BOARD_SNAPSHOT_MAX_TOTAL_ROWS = 5000;
523
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
524
+ /** --board-columns <stageId>[:<limit>],...: the server's own rules checked before any request (1-50 columns, UUID stage
525
+ * ids, no stage twice, limit a whole number 1-1000, default --limit, at most 5000 rows in total); the wire value. */
526
+ function parseBoardColumnsFlag(raw, defaultLimit) {
527
+ const entries = raw.split(",").map((e) => e.trim());
528
+ if (raw.trim() === "" || entries.some((e) => e === ""))
529
+ throw new Error("--board-columns must be <stageId>[:<limit>],... with no blank entry.");
530
+ if (entries.length > BOARD_SNAPSHOT_MAX_COLUMNS)
531
+ throw new Error(`--board-columns names at most ${BOARD_SNAPSHOT_MAX_COLUMNS} columns (got ${entries.length}).`);
532
+ const seen = new Set();
533
+ let total = 0;
534
+ const out = [];
535
+ for (const entry of entries) {
536
+ const parts = entry.split(":");
537
+ const id = parts[0].trim();
538
+ if (parts.length > 2 || !UUID_RE.test(id))
539
+ throw new Error(`--board-columns expects <stageId>[:<limit>] entries with a stage UUID; got ${JSON.stringify(entry)}.`);
540
+ const limit = parts.length === 2 ? strictIntFlag(parts[1], `--board-columns limit for ${id}`, 1, BOARD_SNAPSHOT_MAX_COLUMN_ROWS) : defaultLimit;
541
+ if (seen.has(id.toLowerCase()))
542
+ throw new Error(`--board-columns names stage ${id} more than once.`);
543
+ seen.add(id.toLowerCase());
544
+ total += limit;
545
+ out.push(parts.length === 2 ? `${id}:${limit}` : id);
546
+ }
547
+ if (total > BOARD_SNAPSHOT_MAX_TOTAL_ROWS)
548
+ throw new Error(`--board-columns asks for ${total} rows; one snapshot answers at most ${BOARD_SNAPSHOT_MAX_TOTAL_ROWS}. Read deeper columns with --stage.`);
549
+ return out.join(",");
550
+ }
519
551
  /** The six Pipeline health checks (GET /deals health_check, GET /deals/inspection check); the API validates the value. */
520
552
  const PIPELINE_HEALTH_CHECKS = ["high_value_idle", "stale", "unassigned", "needs_action", "missing_close_date", "missing_contact"];
521
553
  deals
@@ -534,6 +566,8 @@ deals
534
566
  .option("--health-offset <n>", "with --health-summary and --health-check: deals to skip (default 0); prefer --health-cursor")
535
567
  .option("--health-cursor <token>", "with --health-summary and --health-check: continue after this cursor, the nextCursor of the previous page, passed back UNCHANGED (opaque signed token; a blank, forged or foreign one is a 400 with health_cursor_blank / health_cursor_invalid / health_cursor_context / health_cursor_needs_check)")
536
568
  .option("--health-rows-only", "with --health-summary and --health-check: answer only that page of the check's deals ({check, offset, items, hasMore, nextCursor}), without the summary")
569
+ .option("--board-snapshot", "read every column in --board-columns from ONE database snapshot (a deal moved during the read is in exactly one column). Needs --pipeline or --pipeline-name and --board-columns; refused (exit 1, no request) with --page, --all, --stage, --stage-name or any --health-* flag. Prints { columns: [{ stageId, limit, deals }], meta: { board_snapshot: true } }: columns in the order named, each column's deals exactly what --stage <stageId> --limit <limit> rows-only answers, no totals; deals.length equal to limit means the column may hold more. A 503 board_snapshot_busy (the snapshot could not finish in its budget: read the columns one by one or retry) or gate_busy (any deals list call: the access check could not finish in time) exits 1 with the code and Retry-After; never retried")
570
+ .option("--board-columns <columns>", `with --board-snapshot: <stageId>[:<limit>],... in answer order; 1-${BOARD_SNAPSHOT_MAX_COLUMNS} live stages of the pipeline, no stage twice, limit 1-${BOARD_SNAPSHOT_MAX_COLUMN_ROWS} (default --limit, itself default 50), at most ${BOARD_SNAPSHOT_MAX_TOTAL_ROWS} rows in total`)
537
571
  .action(run(async (client, opts) => {
538
572
  // A provided-but-blank filter must error, not silently become "no filter":
539
573
  // buildQuery drops empty strings, so a blank flag would never reach the
@@ -551,6 +585,44 @@ deals
551
585
  throw new Error(`${flag} was provided but is blank. Omit it to skip the filter, or pass a real value.`);
552
586
  }
553
587
  }
588
+ // Board snapshot (#116 for app #111, contract 5729bc7b4): one pipeline's columns from ONE database snapshot. Its
589
+ // conflicts are refused here, before any request; the answer is { columns, meta } (no data.data / meta.total), so it
590
+ // never goes through the health branch or the --all / truncation logic below.
591
+ if (opts.boardSnapshot || opts.boardColumns !== undefined) {
592
+ if (!opts.boardSnapshot)
593
+ throw new Error("--board-columns needs --board-snapshot.");
594
+ const conflicts = [
595
+ ["--page", opts.page !== undefined], ["--all", opts.all === true],
596
+ ["--stage", opts.stage !== undefined], ["--stage-name", opts.stageName !== undefined],
597
+ ["--health-summary", opts.healthSummary === true], ["--health-check", opts.healthCheck !== undefined],
598
+ ["--health-limit", opts.healthLimit !== undefined], ["--health-offset", opts.healthOffset !== undefined],
599
+ ["--health-cursor", opts.healthCursor !== undefined], ["--health-rows-only", opts.healthRowsOnly === true],
600
+ ].filter(([, hit]) => hit).map(([flag]) => flag);
601
+ if (conflicts.length > 0) {
602
+ throw new Error(`--board-snapshot cannot be combined with ${conflicts.join(", ")}: it answers every named column's first rows, rows only, from one snapshot.`);
603
+ }
604
+ if (opts.pipeline === undefined && opts.pipelineName === undefined)
605
+ throw new Error("--board-snapshot needs --pipeline or --pipeline-name.");
606
+ if (opts.boardColumns === undefined)
607
+ throw new Error("--board-snapshot needs --board-columns <stageId>[:<limit>],...");
608
+ const perPage = opts.limit === undefined ? 50 : strictIntFlag(opts.limit, "--limit", 1, BOARD_SNAPSHOT_MAX_COLUMN_ROWS);
609
+ const columns = parseBoardColumnsFlag(opts.boardColumns, perPage);
610
+ const usp = new URLSearchParams();
611
+ if (opts.pipeline !== undefined)
612
+ usp.set("pipeline", opts.pipeline);
613
+ if (opts.pipelineName !== undefined)
614
+ usp.set("pipeline_name", opts.pipelineName);
615
+ if (opts.limit !== undefined)
616
+ usp.set("per_page", String(perPage));
617
+ usp.set("board_snapshot", "true");
618
+ usp.set("board_columns", columns);
619
+ const answer = await client.get(`/api/v1/deals?${usp.toString()}`);
620
+ const data = answer?.data;
621
+ if (!data || !Array.isArray(data.columns) || data.meta?.board_snapshot !== true) {
622
+ throw new Error("--board-snapshot: the API answered without { columns, meta: { board_snapshot: true } }. Aborting rather than print another shape.");
623
+ }
624
+ return answer;
625
+ }
554
626
  // Pipeline health (app 10-08): a different answer shape (no data.data / meta.total), so it never goes through the
555
627
  // --all / truncation logic below. The API validates the check, the paging and the cursor (400 + code).
556
628
  const healthFlags = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.78.0",
3
+ "version": "1.79.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",