@audienti/cli 0.1.36 → 0.1.37

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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.37] - 2026-08-23
8
+
9
+ ### Added
10
+
11
+ - Add motion start/end scheduling and maximum-company discovery-cap configuration to motion API readback and CLI create/update/show workflows.
12
+
13
+ ### Changed
14
+
15
+ - Show conversion totals, stage SLA, and oldest stage age in the default `audienti analytics stages` output so it matches the dashboard read model without requiring `--json`.
16
+
7
17
  ## [0.1.36] - 2026-08-22
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -143,6 +143,7 @@ audienti motions quick-start --url https://example.com --wait --confirm
143
143
  audienti motions abm-companies <motn_id> add --file abm-domains.txt
144
144
  audienti motions abm-companies <motn_id> list
145
145
  audienti motions update <motn_id> --status paused
146
+ audienti motions update <motn_id> --start-date 2026-09-01 --end-date 2026-09-30 --maximum-company-count 25
146
147
  audienti motions update <motn_id> --own-post-engagement true
147
148
  audienti motions update <motn_id> --payload motion-signals.json
148
149
  audienti motions activate <motn_id>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -110,11 +110,13 @@ audienti motions quick-start --url https://example.com --wait --confirm --json
110
110
  audienti motions abm-companies <motn_id> add --file abm-domains.txt --json
111
111
  audienti motions abm-companies <motn_id> list --json
112
112
  audienti motions update <motn_id> --status paused --json
113
+ audienti motions update <motn_id> --start-date 2026-09-01 --end-date 2026-09-30 --maximum-company-count 25 --json
113
114
  audienti motions activate <motn_id> --json
114
115
  audienti motions delete <motn_id> --confirm yes --json
115
116
  audienti operator next --json
116
117
  audienti operator next --plan
117
118
  audienti analytics prospects --window 24h --json
119
+ audienti analytics stages --window 30d
118
120
  audienti analytics visibility --window 24h --user me --json
119
121
  audienti analytics content --window week --json
120
122
  audienti tools list --json
@@ -135,6 +137,13 @@ condition and action data as the list UI. Inspect the ordered rules first, use
135
137
  as an asynchronous relaunch: it queues the existing routing job and does not
136
138
  mean every list member has finished routing.
137
139
 
140
+ Motion start and end dates are optional lifecycle controls. A due start can
141
+ activate a configured preparing or paused motion; an end date is the final
142
+ active day and pauses the motion afterward. `--maximum-company-count` caps
143
+ durable company admissions only: research and people discovery for admitted
144
+ companies, prospect intake, and automation continue. Use `none` to clear any
145
+ of these settings.
146
+
138
147
  `audienti writer test-run <prsp_id>` starts a report-backed writing session and
139
148
  builds the campaign timeline without drafting every message. The printed
140
149
  `Report: rprt_...` id is the session handle. Pass `--report <rprt_id>` when
package/src/cli.js CHANGED
@@ -72,7 +72,7 @@ const OFFERS_DELETE_USAGE = "Usage: audienti offers delete <offr_id> --confirm <
72
72
  const WRITER_TEST_RUN_USAGE = "Usage: audienti writer test-run <prsp_id> [--json] [--mode <plan|report|step>] [--branch <both|no-accept|accepted>] [--step <step_key|row_number>] [--report <rprt_id>] [--no-wait] [--timeout-seconds <n>] [--poll-interval-seconds <n>] [--account <acct_id>]";
73
73
  const WRITER_TEST_RUN_SHOW_USAGE = "Usage: audienti writer test-run show <prsp_id> <rprt_id> [--json] [--account <acct_id>]";
74
74
  const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--json] [--account <acct_id>]";
75
- const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] | --payload <file.json>) [--json] [--account <acct_id>]";
75
+ const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <YYYY-MM-DD|none>] [--end-date <YYYY-MM-DD|none>] [--maximum-company-count <n|none>] | --payload <file.json>) [--json] [--account <acct_id>]";
76
76
  const CONTENT_PROGRAMS_USAGE = "Usage: audienti content programs [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
77
77
  const CONTENT_PLAN_USAGE = "Usage: audienti content plan <cprg_id> [--week <n>] [--due] [--json] [--account <acct_id>]";
78
78
  const CONTENT_SHOW_USAGE = "Usage: audienti content show <cpwi_id> [--json] [--account <acct_id>]";
@@ -1881,7 +1881,10 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
1881
1881
  payload: { type: "string" },
1882
1882
  status: { type: "string" },
1883
1883
  tags: { type: "string" },
1884
- "own-post-engagement": { type: "string" }
1884
+ "own-post-engagement": { type: "string" },
1885
+ "start-date": { type: "string" },
1886
+ "end-date": { type: "string" },
1887
+ "maximum-company-count": { type: "string" }
1885
1888
  });
1886
1889
  const hasUpdateField = values.payload || motionSimpleFieldsPresent(values);
1887
1890
  if (positionals.length !== 1 || !hasUpdateField) {
@@ -1899,7 +1902,7 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
1899
1902
  async function motionUpdatePayload(values) {
1900
1903
  if (values.payload) {
1901
1904
  if (motionSimpleFieldsPresent(values)) {
1902
- throw new CommandError("Choose one motion input mode: either --payload <file.json> or the simple --status/--tags/--own-post-engagement flags.");
1905
+ throw new CommandError("Choose one motion input mode: either --payload <file.json> or the simple motion update flags.");
1903
1906
  }
1904
1907
 
1905
1908
  const payload = await readJsonPayload(values.payload);
@@ -1907,15 +1910,54 @@ async function motionUpdatePayload(values) {
1907
1910
  return payload;
1908
1911
  }
1909
1912
 
1910
- return compactObject({
1913
+ const payload = compactObject({
1911
1914
  status: normalizeMotionStatus(values.status),
1912
1915
  play_tags: values.tags !== undefined ? tagList(values.tags) : undefined,
1913
1916
  own_post_engagement: values["own-post-engagement"] !== undefined ? parseBooleanString(values["own-post-engagement"], "--own-post-engagement") : undefined
1914
1917
  });
1918
+ const nullableSettings = {
1919
+ starts_on: normalizeMotionDate(values["start-date"], "--start-date"),
1920
+ ends_on: normalizeMotionDate(values["end-date"], "--end-date"),
1921
+ maximum_company_count: normalizeMotionMaximumCompanyCount(values["maximum-company-count"])
1922
+ };
1923
+ for (const [key, value] of Object.entries(nullableSettings)) {
1924
+ if (value !== undefined) payload[key] = value;
1925
+ }
1926
+
1927
+ return payload;
1915
1928
  }
1916
1929
 
1917
1930
  function motionSimpleFieldsPresent(values) {
1918
- return Boolean(values.status) || values.tags !== undefined || values["own-post-engagement"] !== undefined;
1931
+ return Boolean(values.status) ||
1932
+ values.tags !== undefined ||
1933
+ values["own-post-engagement"] !== undefined ||
1934
+ values["start-date"] !== undefined ||
1935
+ values["end-date"] !== undefined ||
1936
+ values["maximum-company-count"] !== undefined;
1937
+ }
1938
+
1939
+ function normalizeMotionDate(value, flagName) {
1940
+ if (value === undefined) return undefined;
1941
+
1942
+ const normalized = String(value || "").trim().toLowerCase();
1943
+ if (normalized === "none") return null;
1944
+ const match = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/);
1945
+ if (!match) throw new CommandError(`${flagName} must be YYYY-MM-DD or none.`);
1946
+
1947
+ const [, year, month, day] = match.map(Number);
1948
+ const parsed = new Date(Date.UTC(year, month - 1, day));
1949
+ if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {
1950
+ throw new CommandError(`${flagName} must be YYYY-MM-DD or none.`);
1951
+ }
1952
+
1953
+ return normalized;
1954
+ }
1955
+
1956
+ function normalizeMotionMaximumCompanyCount(value) {
1957
+ if (value === undefined) return undefined;
1958
+ if (String(value || "").trim().toLowerCase() === "none") return null;
1959
+
1960
+ return normalizeOptionalPositiveInteger(value, "--maximum-company-count");
1919
1961
  }
1920
1962
 
1921
1963
  function validateMotionSignalRows(payload) {
@@ -4588,6 +4630,9 @@ function renderMotion(motion, context) {
4588
4630
  writeLine(context.stdout, `Motion: ${display(motion?.name)} (${display(motion?.prefix_id)})`);
4589
4631
  writeLine(context.stdout, `Status: ${display(motion?.status)}`);
4590
4632
  writeLine(context.stdout, `Kind: ${display(motion?.kind)}`);
4633
+ writeLine(context.stdout, `Start date: ${display(motion?.starts_on, "not set")}`);
4634
+ writeLine(context.stdout, `End date: ${display(motion?.ends_on, "not set")}`);
4635
+ writeLine(context.stdout, `Maximum companies: ${display(motion?.maximum_company_count, "not set")}`);
4591
4636
  renderExecutableConfiguration(motion?.executable_configuration, context);
4592
4637
  if (motion?.offer?.name) writeLine(context.stdout, `Offer: ${motion.offer.name} (${display(motion.offer.prefix_id)})`);
4593
4638
  if (motion?.icp?.name) writeLine(context.stdout, `ICP: ${motion.icp.name} (${display(motion.icp.prefix_id)})`);
@@ -5780,6 +5825,7 @@ function writeStageConversionTable(conversionGrid, context) {
5780
5825
  const rows = Array.isArray(conversionGrid?.rows) ? conversionGrid.rows : [];
5781
5826
  const definitions = Array.isArray(conversionGrid?.stage_definitions) ? conversionGrid.stage_definitions : [];
5782
5827
  const columns = definitions.filter((definition) => definition?.key);
5828
+ const totalValues = conversionGrid?.total_values || {};
5783
5829
 
5784
5830
  writeLine(context.stdout, "");
5785
5831
  writeLine(context.stdout, display(conversionGrid?.window_description, "Conversion cohorts"));
@@ -5790,6 +5836,12 @@ function writeStageConversionTable(conversionGrid, context) {
5790
5836
  display(row?.label),
5791
5837
  ...columns.map((column) => stageMetricValueLabel(row?.values?.[column.key]))
5792
5838
  ]);
5839
+ if (Object.keys(totalValues).length > 0) {
5840
+ tableRows.push([
5841
+ "TOTAL",
5842
+ ...columns.map((column) => stageMetricValueLabel(totalValues[column.key]))
5843
+ ]);
5844
+ }
5793
5845
 
5794
5846
  writeAlignedTable(context, headers, tableRows);
5795
5847
  }
@@ -5807,20 +5859,22 @@ function writeStageAgingTable(stageAging, context) {
5807
5859
  );
5808
5860
  writeAlignedTable(
5809
5861
  context,
5810
- ["STAGE", "CURRENT", "DUE SOON", "OVERDUE", "OVERDUE %", "MED AGE", "OLDEST IDLE"],
5862
+ ["STAGE", "SLA", "CURRENT", "DUE SOON", "OVERDUE", "OVERDUE %", "MED AGE", "OLDEST AGE", "OLDEST IDLE"],
5811
5863
  rows.map(stageAgingRow),
5812
- { numericColumns: [false, true, true, true, true, true, true] }
5864
+ { numericColumns: [false, true, true, true, true, true, true, true, true] }
5813
5865
  );
5814
5866
  }
5815
5867
 
5816
5868
  function stageAgingRow(row) {
5817
5869
  return [
5818
5870
  display(row?.label || row?.key),
5871
+ dayCountLabel(row?.due_after_days),
5819
5872
  display(row?.current_count, 0),
5820
5873
  display(row?.due_soon_count, 0),
5821
5874
  display(row?.overdue_count, 0),
5822
5875
  percentageLabel(row?.overdue_rate),
5823
5876
  dayCountLabel(row?.median_stage_age_days),
5877
+ dayCountLabel(row?.oldest_stage_age_days),
5824
5878
  dayCountLabel(row?.oldest_idle_days)
5825
5879
  ];
5826
5880
  }
@@ -6576,7 +6630,7 @@ const HELP_TOPICS = new Map([
6576
6630
  " audienti motions abm-companies <motn_id> list",
6577
6631
  " audienti motions abm-companies <motn_id> add <domain_or_linkedin_url>...",
6578
6632
  " audienti motions create --payload <file.json>",
6579
- " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>]",
6633
+ " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <date|none>] [--end-date <date|none>] [--maximum-company-count <n|none>]",
6580
6634
  " audienti motions update <motn_id> --payload <file.json>",
6581
6635
  " audienti motions add-tag <motn_id> <tag>",
6582
6636
  " audienti motions remove-tag <motn_id> <tag>",
@@ -7899,7 +7953,7 @@ const HELP_TOPICS = new Map([
7899
7953
  " audienti motions abm-companies <motn_id> add (<domain_or_linkedin_url>... | --file <txt|json>) [--json]",
7900
7954
  " audienti motions abm-companies <motn_id> remove <row_id> [--json]",
7901
7955
  " audienti motions create --payload <file.json> [--json]",
7902
- " audienti motions update <motn_id> ([--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] | --payload <file.json>) [--json]",
7956
+ ` ${MOTIONS_UPDATE_USAGE.slice("Usage: ".length).replace(" [--account <acct_id>]", "")}`,
7903
7957
  " audienti motions add-tag <motn_id> <tag> [--json]",
7904
7958
  " audienti motions remove-tag <motn_id> <tag> [--json]",
7905
7959
  " audienti motions activate <motn_id> [--json]",
@@ -8177,6 +8231,9 @@ const HELP_TOPICS = new Map([
8177
8231
  " icp_id: icpp_ prefix id | optional",
8178
8232
  " list_id: list_ prefix id | optional",
8179
8233
  " play_tags: [string] | optional",
8234
+ " starts_on: YYYY-MM-DD | null | optional scheduled activation date",
8235
+ " ends_on: YYYY-MM-DD | null | optional final active date",
8236
+ " maximum_company_count: positive integer | null | optional new-company discovery cap",
8180
8237
  " signal_rows: [{ scope: company | person | both, question: string, company_signal_category: string | optional, role_terms: string | optional, posting_language: hiring-only string | optional, topics: string | optional }] | optional for outbound motions",
8181
8238
  " signal_questions: newline-delimited company::, person::, or both:: legacy question text | optional for outbound motions",
8182
8239
  " inbound_channels: [linkedin | reddit] | optional; defaults to [linkedin] for inbound motions",
@@ -8191,6 +8248,9 @@ const HELP_TOPICS = new Map([
8191
8248
  " \"offer_id\": \"offr_abc123\",",
8192
8249
  " \"principal_account_user_id\": 42,",
8193
8250
  " \"list_id\": \"list_abc123\",",
8251
+ " \"starts_on\": \"2026-09-01\",",
8252
+ " \"ends_on\": \"2026-09-30\",",
8253
+ " \"maximum_company_count\": 25,",
8194
8254
  " \"play_tags\": [\"sarit\", \"pj\"]",
8195
8255
  " }",
8196
8256
  "",
@@ -8214,8 +8274,8 @@ const HELP_TOPICS = new Map([
8214
8274
  " name: new motion name",
8215
8275
  "",
8216
8276
  "Behavior:",
8217
- " Copies the motion kind, offer, ICP, principal, premise, approach, targeting profile, suppression policy, secondary roles, and active signal rows.",
8218
- " The clone starts as draft with a new empty backing list.",
8277
+ " Copies the motion kind, offer, ICP, principal, premise, approach, maximum company count, targeting profile, suppression policy, secondary roles, and active signal rows.",
8278
+ " The clone starts as draft with a new empty backing list and no inherited schedule dates.",
8219
8279
  "",
8220
8280
  "API:",
8221
8281
  " POST /api/v1/accounts/:account_id/motions/:id/clone.json",
@@ -8241,10 +8301,14 @@ const HELP_TOPICS = new Map([
8241
8301
  " motn_id: motn_ prefix id",
8242
8302
  " status: draft | preparing | active | paused | archived | optional",
8243
8303
  " tags: comma-separated tag list | optional",
8304
+ " start-date: YYYY-MM-DD | none | optional",
8305
+ " end-date: YYYY-MM-DD | none | optional",
8306
+ " maximum-company-count: positive integer | none | optional",
8244
8307
  " payload: file.json | optional full or partial motion object using the account API shape",
8245
8308
  "",
8246
8309
  "Behavior:",
8247
- " Choose either --payload or simple flags. Updates only the provided fields. Sending --tags replaces the motion's full tag set.",
8310
+ " Choose either --payload or simple flags. Updates only the provided fields. Use none to clear a schedule date or company cap. Sending --tags replaces the motion's full tag set.",
8311
+ " The company cap stops only new company discovery; existing company research, people discovery, prospect intake, and automation continue.",
8248
8312
  " Payload mode supports outbound signal_rows or legacy signal_questions; supplied rows replace the motion's active ready signals.",
8249
8313
  " posting_language is only supported on company-scope hiring signal rows; use topics for person discussion evidence.",
8250
8314
  "",