conduyt 1.72.0 → 1.73.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 +66 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { readFileSync, writeFileSync } from "node:fs";
3
3
  import { addPoolFilterSet, listPoolFilterSets, removePoolFilterSet, updatePoolFilterSet } from "./pool-filter-sets.js";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { dirname, join } from "node:path";
6
- import { Command, CommanderError } from "commander";
6
+ import { Command, CommanderError, Option } from "commander";
7
7
  import { ConduytClient, buildQuery } from "./client.js";
8
8
  import { minimumGradeOf, minimumGradePatch, parseGradeFlag } from "./message-check-minimum.js";
9
9
  import { splitList, stageRulePatch, validateCardFields, validateRuleChange } from "./board-settings.js";
@@ -2670,57 +2670,104 @@ companies
2670
2670
  }));
2671
2671
  // ---- tags ----
2672
2672
  // ---- saved filters (personal, Velocify-style) ----
2673
+ // Smart View icons (app branch fable/smart-view-icons): the 30 current ids in picker order. The API also accepts 17 legacy ids as
2674
+ // deprecated inputs (saved as the mapped new id); they are deliberately not listed here.
2675
+ const SMART_VIEW_ICON_IDS = [
2676
+ "heart", "star", "trophy", "badge", "target", "eye", "clock", "hourglass", "siren", "bolt",
2677
+ "fire", "rain", "funnel", "pyramid", "flag", "pin", "push-pin", "thumbs-up", "thumbs-down", "coffee",
2678
+ "rocket", "number-1", "number-2", "number-3", "number-4", "number-5", "number-6", "number-7", "number-8", "number-9",
2679
+ ];
2680
+ const SMART_VIEW_ICON_HELP = (omitted) => `the view's icon, one of (picker order): ${SMART_VIEW_ICON_IDS.join(", ")}. ${omitted}. Legacy icon ids are accepted but saved as the new id; anything else is 400 invalid_icon`;
2681
+ const SAVED_FILTER_ENTITY_ERROR = "saved filters are contact Smart Views; --entity is no longer supported";
2682
+ function rejectEntity(body) {
2683
+ if ("entity" in body || "entityType" in body)
2684
+ throw new Error(SAVED_FILTER_ENTITY_ERROR);
2685
+ }
2686
+ /** saved-filters update/delete take a Smart View id, but the generic routes also edit/delete SHARED views for admin keys: read the view first and
2687
+ * refuse anything that is not one of the caller's personal views (the GET is the only request made in the refusal path). A missing view throws the server's reason. */
2688
+ async function requirePersonalSmartView(client, id) {
2689
+ const res = (await client.get(`/api/v1/smart-views/${encodeURIComponent(id)}`));
2690
+ if (res?.data?.isPersonal !== true) {
2691
+ throw new Error("this is a shared Smart View; saved filters are personal views only (use `conduyt api PATCH /smart-views/<id> --body '{...}'` or `conduyt api DELETE /smart-views/<id>`)");
2692
+ }
2693
+ }
2673
2694
  const savedFilters = program
2674
2695
  .command("saved-filters")
2675
2696
  .description("Manage YOUR personal saved contact filters (the web app's Saved dropdown; requires the 'saved-filters' key scope)");
2676
2697
  savedFilters
2677
2698
  .command("list")
2678
- .description("List your saved filters")
2679
- .option("--entity <type>", "entity type (default contact)")
2680
- .action(run(async (client, opts) => client.get(`/api/v1/saved-filters${buildQuery({ entity_type: opts.entity })}`)));
2699
+ .description("List your saved filters: your PERSONAL Smart Views (GET /smart-views, every page, filtered to isPersonal). The printed id is the Smart View id that update and delete take")
2700
+ .action(run(async (client) => {
2701
+ for (let attempt = 0; attempt < 3; attempt += 1) {
2702
+ const first = (await client.get("/api/v1/smart-views?counts=false"));
2703
+ const rows = [...first.data.data];
2704
+ let meta = first.data.meta;
2705
+ let shifted = false;
2706
+ for (let page = 2; meta.has_more && page <= 50; page += 1) {
2707
+ const next = (await client.get(`/api/v1/smart-views?page=${page}&counts=false`));
2708
+ if (next.data.meta.order_stamp !== first.data.meta.order_stamp) {
2709
+ shifted = true;
2710
+ break;
2711
+ }
2712
+ rows.push(...next.data.data);
2713
+ meta = next.data.meta;
2714
+ }
2715
+ if (!shifted)
2716
+ return rows.filter((v) => v.isPersonal === true);
2717
+ }
2718
+ throw new Error("Smart Views kept changing while they were listed; try again.");
2719
+ }));
2681
2720
  savedFilters
2682
2721
  .command("create")
2683
- .description("Save a named filter (grouped payload via --filters JSON)")
2722
+ .description("Save a named filter (a personal Smart View, private to you; grouped payload via --filters JSON)")
2684
2723
  .option("--name <name>", "display name (required, unique per user, max 60 chars)")
2685
2724
  .option("--filters <json>", `grouped filter payload JSON, e.g. [{"conjunction":"AND","rules":[{"field":"source","operator":"equals","value":"facebook"}]}]`)
2686
2725
  .option("--sort-field <field>", "sort field (default createdAt)")
2687
2726
  .option("--sort-order <dir>", "asc | desc (default desc)")
2688
- .option("--entity <type>", "entity type (default contact)")
2689
- .option("--json <json>", "full JSON body, merged over the flags")
2727
+ .addOption(new Option("--entity <type>", "removed").hideHelp()) // not in the help; passing it exits with SAVED_FILTER_ENTITY_ERROR instead of being ignored
2728
+ .option("--icon <id>", SMART_VIEW_ICON_HELP("Omitted: heart (the default)"))
2729
+ .option("--json <json>", "full JSON body, merged over the flags (may carry `icon`: same ids, same default and same 400 invalid_icon as --icon)")
2690
2730
  .action(run(async (client, opts) => {
2731
+ if (opts.entity !== undefined)
2732
+ throw new Error(SAVED_FILTER_ENTITY_ERROR);
2691
2733
  const body = {};
2692
2734
  if (opts.name !== undefined)
2693
2735
  body.name = opts.name;
2736
+ if (opts.icon !== undefined)
2737
+ body.icon = opts.icon;
2694
2738
  if (opts.filters !== undefined)
2695
2739
  body.filters = jsonArrayArg(opts.filters, "--filters");
2696
2740
  if (opts.sortField !== undefined)
2697
2741
  body.sortField = opts.sortField;
2698
2742
  if (opts.sortOrder !== undefined)
2699
2743
  body.sortOrder = opts.sortOrder;
2700
- if (opts.entity !== undefined)
2701
- body.entityType = opts.entity;
2702
2744
  if (opts.json !== undefined)
2703
2745
  Object.assign(body, jsonArg(opts.json, "--json"));
2746
+ rejectEntity(body);
2704
2747
  // A --json merge can clobber filters with a non-array; re-assert the
2705
2748
  // shape after ALL merges so a malformed payload fails locally.
2706
2749
  if (body.filters !== undefined && !Array.isArray(body.filters)) {
2707
2750
  throw new Error("filters must be a JSON ARRAY (grouped payload or flat rules).");
2708
2751
  }
2709
- return client.post("/api/v1/saved-filters", body);
2752
+ // a saved filter IS a personal Smart View (saved-filters write routes retired 2026-09-24, 410): personal:true keeps it private for every key
2753
+ return client.post("/api/v1/smart-views", { ...body, personal: true });
2710
2754
  }));
2711
2755
  savedFilters
2712
2756
  .command("update <id>")
2713
- .description("Rename a saved filter or replace its filters/sort (owner-only)")
2757
+ .description("Rename a saved filter (a personal Smart View) or replace its filters/sort (owner-only). The id must be one of YOUR personal views (see `saved-filters list`); a shared Smart View id is refused after one GET, nothing is changed (edit a shared view with `conduyt api PATCH /smart-views/<id> --body '{...}'`)")
2714
2758
  .option("--name <name>", "new display name")
2715
2759
  .option("--filters <json>", "grouped filter payload JSON")
2716
2760
  .option("--sort-field <field>", "sort field")
2717
2761
  .option("--sort-order <dir>", "asc | desc")
2718
- .option("--json <json>", "full JSON body, merged over the flags")
2762
+ .option("--icon <id>", SMART_VIEW_ICON_HELP("Omitted: unchanged"))
2763
+ .option("--json <json>", "full JSON body, merged over the flags (may carry `icon`: same ids and same 400 invalid_icon as --icon)")
2719
2764
  .action(run(async (client, id, opts) => {
2720
2765
  assertUuid(id, "saved filter id");
2721
2766
  const body = {};
2722
2767
  if (opts.name !== undefined)
2723
2768
  body.name = opts.name;
2769
+ if (opts.icon !== undefined)
2770
+ body.icon = opts.icon;
2724
2771
  if (opts.filters !== undefined)
2725
2772
  body.filters = jsonArrayArg(opts.filters, "--filters");
2726
2773
  if (opts.sortField !== undefined)
@@ -2729,18 +2776,21 @@ savedFilters
2729
2776
  body.sortOrder = opts.sortOrder;
2730
2777
  if (opts.json !== undefined)
2731
2778
  Object.assign(body, jsonArg(opts.json, "--json"));
2779
+ rejectEntity(body);
2732
2780
  // Same post-merge shape assertion as create — see above.
2733
2781
  if (body.filters !== undefined && !Array.isArray(body.filters)) {
2734
2782
  throw new Error("filters must be a JSON ARRAY (grouped payload or flat rules).");
2735
2783
  }
2736
- return client.patch(`/api/v1/saved-filters/${encodeURIComponent(id)}`, body);
2784
+ await requirePersonalSmartView(client, id);
2785
+ return client.patch(`/api/v1/smart-views/${encodeURIComponent(id)}`, body);
2737
2786
  }));
2738
2787
  savedFilters
2739
2788
  .command("delete <id>")
2740
- .description("Delete one of your saved filters (owner-only)")
2789
+ .description("Delete one of your saved filters (a personal Smart View; owner-only). The id must be one of YOUR personal views (see `saved-filters list`); a shared Smart View id is refused after one GET, nothing is deleted (delete a shared view with `conduyt api DELETE /smart-views/<id>`)")
2741
2790
  .action(run(async (client, id) => {
2742
2791
  assertUuid(id, "saved filter id");
2743
- return client.del(`/api/v1/saved-filters/${encodeURIComponent(id)}`);
2792
+ await requirePersonalSmartView(client, id);
2793
+ return client.del(`/api/v1/smart-views/${encodeURIComponent(id)}`);
2744
2794
  }));
2745
2795
  const tags = program.command("tags").description("Manage tags");
2746
2796
  tags
@@ -5233,7 +5283,7 @@ reports
5233
5283
  .command("deliveries <sid>")
5234
5284
  .description("The delivery history of a schedule with download URLs")
5235
5285
  .action(run(async (client, sid) => { assertUuid(sid, "schedule id"); return client.get(`/api/v1/reports/subscriptions/${sid}/deliveries`); }));
5236
- const smartViews = program.command("smart-views").description("Smart Views and their Smart Dialing priorities");
5286
+ const smartViews = program.command("smart-views").description(`Smart Views and their Smart Dialing priorities. Creating or updating a view (conduyt api POST /smart-views, PATCH /smart-views/<id>, or saved-filters create/update) takes an optional icon, one of (picker order): ${SMART_VIEW_ICON_IDS.join(", ")}. heart is the default on create. Legacy icon ids are accepted but saved as the new id; anything else is 400 invalid_icon`);
5237
5287
  smartViews
5238
5288
  .command("list")
5239
5289
  .description("List Smart Views in sidebar order (= Smart Dialing priority) with lead counts, audience (groupId / members / isPersonal) and dialing settings")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.72.0",
3
+ "version": "1.73.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",