@wrangled/cli 0.3.64 → 0.3.66

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/dist/guide.js CHANGED
@@ -63,7 +63,7 @@ ${brand.name} writes and sends no emails or texts; the client's own tool (GoHigh
63
63
 
64
64
  ## Which Facebook ads and pages won: winners and flops
65
65
 
66
- \`${cli} winners\` says which Facebook ads won and which flopped, for the dates asked for and for the 30 days ending on their last day, judged on the client's goal. Each client has an ordered list of goals, and winners uses the first one whose data is connected: \`sales\` (first-touch sales from every connected payment source: each sale goes to the Facebook ad that first brought the buyer in, and payments typed into GoHighLevel by hand never count toward an ad), \`sales:triple_whale\` (the orders Triple Whale credits to each ad), \`booked_calls\` (calls booked by someone whose first touch was the ad), \`meta_results\` (Facebook's own count of the result types set on the Meta source, such as applications, \`submit_application_website\`) or \`first_leads\`. The default list is Triple Whale's orders, then first-touch sales, then first leads, which is how winners judged before goals could be set. \`${cli} winners goals\` shows the list, whether each goal has data and which one is used now; \`${cli} winners goals set booked_calls first_leads\` sets it, and \`${cli} winners goals reset\` goes back to the default. To judge on Stripe sales, connect Stripe, then put \`sales\` first. The report starts with a sentence saying which goal it judged on, and in the JSON every "sales" field (\`numbers.sales\`, \`costPerSale\`, \`accountCostPerSale\`, \`adSales\`, \`judgedOn: "sales"\`) holds that goal, named in \`goal\`, and every "signups" field the fallback goal, named in \`fallbackGoal\`. An ad with none of the goal that has spent less than twice the account's cost per one is too new to judge on it, so it is judged on the fallback goal, the next connected goal of another kind, and listed apart. Landing pages and ad-and-page pairs are always judged on first-touch sales and first leads. Each ad says in a sentence why it is on its list. Each currency also says how many of its Facebook sales could be tied to a specific ad (\`adSales\` of \`facebookSales\` in the JSON): a sale whose link names no single ad counts toward Facebook but toward no ad. It judges the landing pages the ads sent people to the same way, and each ad on each page as an ad-and-page pair: a page or pair counts only once the page reader has read the page and it got at least 100 visits in the dates, and pairs are listed only for an ad that sent people to two or more pages, or a page that got people from two or more ads, each naming what the same ad cost on its other pages. \`--week <YYYY-MM-DD>\` picks any day of a Monday-to-Sunday week (default: the latest complete week), \`--days <n>\` the last n complete days ending yesterday, and \`--from <YYYY-MM-DD> --to <YYYY-MM-DD>\` any dates; use one of them, at most 90 days. Check as often as suits the client, daily or every few days: a short window holds few sales, so more ads are judged on first leads, and the report says so. \`--top <n>\` how many ads, pages or pairs each list shows (default 5, at most 20), \`--ad-format image|video|carousel\` ranks only ads with that format tag (run \`${cli} ads tag-formats\` first; the account's cost per sale still counts every ad), and \`--format json\` gives the report as data.
66
+ \`${cli} winners\` says which Facebook ads won and which flopped, for the dates asked for and for the 30 days ending on their last day, judged on the client's goal. Each client has an ordered list of goals, and winners uses the first one whose data is connected: \`sales\` (first-touch sales from every connected payment source: each sale goes to the Facebook ad that first brought the buyer in, and payments typed into GoHighLevel by hand never count toward an ad), \`sales:triple_whale\` (the orders Triple Whale credits to each ad), \`booked_calls\` (calls booked by someone whose first touch was the ad), \`meta_results\` (Facebook's own count of the result types set on the Meta source, such as applications, \`submit_application_website\`) or \`first_leads\`. The default list is Triple Whale's orders, then first-touch sales, then first leads, which is how winners judged before goals could be set. \`${cli} winners goals\` shows the list, whether each goal has data and which one is used now; \`${cli} winners goals set booked_calls first_leads\` sets it, and \`${cli} winners goals reset\` goes back to the default. To judge on Stripe sales, connect Stripe, then put \`sales\` first. The report starts with a sentence saying which goal it judged on, and in the JSON every "sales" field (\`numbers.sales\`, \`costPerSale\`, \`accountCostPerSale\`, \`adSales\`, \`judgedOn: "sales"\`) holds that goal, named in \`goal\`, and every "signups" field the fallback goal, named in \`fallbackGoal\`. An ad with none of the goal that has spent less than twice the account's cost per one is too new to judge on it, so it is judged on the fallback goal, the next connected goal of another kind, and listed apart. Landing pages and ad-and-page pairs are always judged on first-touch sales and first leads. Each ad says in a sentence why it is on its list. When a currency's ads run in two or more Facebook ad accounts, each ad is judged against its own account's cost per sale, and the section says so and lists each account (\`adAccounts\` in the JSON, and \`accountId\` on each ad). Each currency also says how many of its Facebook sales could be tied to a specific ad (\`adSales\` of \`facebookSales\` in the JSON): a sale whose link names no single ad counts toward Facebook but toward no ad. It judges the landing pages the ads sent people to the same way, and each ad on each page as an ad-and-page pair: a page or pair counts only once the page reader has read the page and it got at least 100 visits in the dates, and pairs are listed only for an ad that sent people to two or more pages, or a page that got people from two or more ads, each naming what the same ad cost on its other pages. \`--week <YYYY-MM-DD>\` picks any day of a Monday-to-Sunday week (default: the latest complete week), \`--days <n>\` the last n complete days ending yesterday, and \`--from <YYYY-MM-DD> --to <YYYY-MM-DD>\` any dates; use one of them, at most 90 days. Check as often as suits the client, daily or every few days: a short window holds few sales, so more ads are judged on first leads, and the report says so. \`--top <n>\` how many ads, pages or pairs each list shows (default 5, at most 20), \`--ad-format image|video|carousel\` ranks only ads with that format tag (run \`${cli} ads tag-formats\` first; the account's cost per sale still counts every ad), and \`--format json\` gives the report as data.
67
67
 
68
68
  ## What winners and flops taught: lessons
69
69
 
@@ -85,7 +85,7 @@ A funnel test writes down one change to a page or funnel step, tried against wha
85
85
 
86
86
  Write it as JSON, such as \`${JSON.stringify(funnelTestExample)}\`, and run \`${cli} tests draft --file test.json\`. It gets Adzara-style labels from the change and this month (\`hyp-<slug>\` and \`test-<yyyy-mm>-<slug>\`); give \`hypothesis_id\` and \`test_id\` to reuse Adzara's own, so the results of the ads Adzara publishes for the test (\`ad_test_results_daily\`, \`ad_hypothesis_results_daily\`) join on the same label. Approving a test happens outside ${brand.name} for now. \`${cli} tests start <test_id>\` marks a draft as running from today, and changes nothing live: the page still goes live through its own approval, and the ads through Adzara. \`${cli} tests abandon <test_id> --note "<why>"\` drops a draft or running test. \`${cli} tests [--status <status>] [--funnel <funnel_key>] [--limit <n>]\` lists them, newest first (50 unless \`--limit\`, at most 200), and \`${cli} tests show <test_id>\` shows one.
87
87
 
88
- \`${cli} tests results <test_id>\` reads a started test: each side's people, how many moved on to the next step, sales, revenue, spend and the test's metric, over the days since it started (a \`before_after\` test compares the same number of days before it started). People are counted by their first visit: the landing page they arrived on, or the Adzara ad (by \`asset_version\` and the test's \`test_id\`) that brought them; a page no one's first visit was recorded on is counted by its site visitors instead. Then it says plainly whether the test can be called yet: "Not enough people yet: 210 of the 400 each side needs.", "Video at the top is ahead, 36% vs 30%, and that gap is big enough to trust." or "No real difference after 900 people each side". A gap counts when a two-proportion test says it is real at 95%; cost per sale and revenue per visitor are judged on sales per visitor under them.
88
+ \`${cli} tests results <test_id>\` reads a started test: each side's people, how many moved on to the next step, how many of those people bought (each once, however many times they bought) and what they paid, spend and the test's metric, over the days since it started (a \`before_after\` test compares the same number of days before it started). People are counted by their first visit: the landing page they arrived on, or the Adzara ad (by \`asset_version\` and the test's \`test_id\`) that brought them; a page no one's first visit was recorded on is counted by its site visitors instead. Then it says plainly whether the test can be called yet: "Not enough people yet: 210 of the 400 each side needs.", "Video at the top is ahead, 36% vs 30%, and that gap is big enough to trust." or "No real difference after 900 people each side". A gap counts when a two-proportion test says it is real at 95%; cost per sale and revenue per visitor are judged on sales per visitor under them.
89
89
 
90
90
  \`${cli} tests decide <test_id> --decision <d> --note "<why>"\` records the decision on a running test: \`scale\` (the new version won; use it everywhere), \`continue\` (keep running), \`iterate\` (promising; it also drafts a sharper version linked to this one), \`hold\` (pause it; something outside the test got in the way) or \`stop\` (it lost or made no difference). Each decision writes a lesson with the test, its pages and its step as proof. Deciding changes nothing live: switching a page or ads still goes through approval.
91
91
 
@@ -225,7 +225,7 @@ The \`meta_ads\` publisher puts an approved ad on Facebook, always paused, in an
225
225
 
226
226
  \`${cli} meta ad create --account act_<id> --adset <id> --file ad.png --text "<primary text>" --headline "<headline>" --link <https url> --concept <key> --angle reframe --hook-kind question --ad-format image\` does all of that in one step: it saves the files and words as an ad with a new SKU, approves it and publishes it with \`meta_ads\` into the ad set, then prints the SKU, the publication, the Meta ad id and its status. Repeat \`--file\` for more pictures; a big file is uploaded first. \`--cta SHOP_NOW\` sets the button; \`--concept\`, \`--angle\`, \`--hook-kind\` and \`--ad-format\` name the ad and are required, and \`--style\` adds the style. The same rules apply and are all checked before anything is saved, so a refusal leaves nothing behind. Your own key sends to Facebook without \`--run\`, but the account and campaign must also be on the workspace's control list; from a hand's run in practice mode it is recorded and nothing is sent. The ad starts paused; \`--status active --reason "<why>"\` switches it on 15 seconds after it is made, as a change in the change log that \`${cli} meta changes undo <change-id>\` reverses, and needs room under the day's limit of on/off changes. Without \`--idempotency-key\` the key comes from the files and the flags, so the same command run twice makes one ad.
227
227
 
228
- Google Ads has the same controls. \`${cli} google-ads ads pause <adgroup-id>~<ad-id> --reason "<why>"\` (ids from \`gads_ads\`) and \`${cli} google-ads adgroup pause|resume <adgroup-id>\` turn ads and ad groups off or on, and \`${cli} google-ads campaign budget <campaign-id> --percent 10 --reason "<why>"\` (or \`--daily 60.50\`, in the account's currency) changes a campaign's daily budget. The account and campaign must be on the workspace's Google Ads control list, which an Admin sets on the Settings page, and the same daily limits, largest budget step, total daily budget limit and approval rule apply; a shared budget is never changed. With approval on (the default), a hand proposes \`gads_budget_change\` and the change goes out once a person approves it. Only your own key writes to Google Ads: no hand does yet. \`${cli} google-ads changes\` lists every change with who asked and why, \`${cli} google-ads changes undo <id>\` puts one back, and \`${cli} google-ads changes reconcile <id>\` settles one whose answer was lost.
228
+ Google Ads has the same controls. \`${cli} google-ads ads pause <adgroup-id>~<ad-id> --reason "<why>"\` (ids from \`gads_ads\`) and \`${cli} google-ads adgroup pause|resume <adgroup-id>\` turn ads and ad groups off or on, and \`${cli} google-ads campaign budget <campaign-id> --percent 10 --reason "<why>"\` (or \`--daily 60.50\`, in the account's currency) changes a campaign's daily budget. The account and campaign must be on the workspace's Google Ads control list, which an Admin sets on the Settings page, and the same daily limits, largest budget step, total daily budget limit and approval rule apply; a shared budget is never changed. With approval on (the default), a hand proposes \`gads_budget_change\` and the change goes out once a person approves it. Only your own key writes to Google Ads: no hand does yet. \`${cli} google-ads changes\` lists every change with who asked and why, \`${cli} google-ads changes undo <id>\` puts one back, and \`${cli} google-ads changes reconcile <id>\` settles one whose answer was lost. \`${cli} changes\` lists the Meta and Google Ads changes together in one log, each with its platform.
229
229
 
230
230
  \`${cli} meta adset publish --plan '<json>' --idempotency-key <key> --run <run-id>\` makes one new Meta ad set, always paused, for ads to go into. A copy, \`{"kind": "copy", "accountId": "act_<id>", "campaignId": "<id>", "sourceAdsetId": "<id>", "persona": "<persona key>", "note": "<audience note>"}\`, copies a proven ad set without its ads; the source must be on the hand's list of ad sets that may be copied. A new one from settings is \`{"kind": "create", "accountId": ..., "campaignId": ..., "persona": ..., "note": ..., "settings": {"optimizationGoal": "...", "billingEvent": "...", "targeting": {...}, "dailyBudget": 5000}}\`, with the budget in the currency's smallest unit (5000 is 50.00 USD) and left out when the campaign holds it. ${brand.name} names it \`<persona> | <note> | <YYYY-MM-DD>\`, the day (UTC) it is first recorded; \`persona\` must be on the client's persona list (or \`whole_list\` or \`existing_customers\`) and \`note\` has no \`|\`. The account and campaign must be on the publish list, the name unused in the campaign, and the day under the limit of new ad sets (3 unless an operator sets another). Ad sets and ads share one account's turn: one unfinished send at a time, and at least 15 seconds apart. ${brand.name} reads the new ad set back and checks it: paused, in the right campaign, with the right name, and for a copy, targeting equal to the source's. Its \`verification.ok\` must be true before you add ads; an ad set that fails the check stays paused and takes none. In practice mode nothing is sent and the ad set is recorded as \`not_sent_practice\`, and ads published into it are practice too. \`${cli} meta adset show <id>\` prints it with its budget. One left \`sending\` or \`unknown\` is never sent again; \`${cli} meta adset reconcile <id>\` looks for it in the campaign by its exact name.
231
231
 
@@ -270,7 +270,7 @@ To change the map, write the whole map as JSON (\`{"offers": [...], "funnels": [
270
270
 
271
271
  \`${cli} funnels numbers\` counts each step of each funnel in the confirmed map over \`--from\` and \`--to\` (default the last 30 days; \`--funnel <key>\` for one funnel): how many reached it, and how many of those who reached the step before in those days moved on to it within the window, with the drop-off and the median days it took. A step whose rules name a person (webinar, GoHighLevel, payments) counts people and follows the same people from step to step; a step that cannot (ad clicks, page visitors) counts events, and its comparison is a rate of counts. A step whose data is not connected says unknown, never 0. Numbers are worked out after each sync by the rules of the map version confirmed at the time, so a renamed step keeps its numbers. For SQL, read \`saved_funnel_steps\`, \`funnel_step_daily\` and \`funnel_conversion\`.
272
272
 
273
- \`${cli} funnels leaks\` (\`--funnel <key>\` for one funnel, \`--to <date>\` for the last day) finds where each funnel loses people now: each step's rate from the step before over the last 28 days and the last 7, against its own normal rate over the 56 days before. A step doing worse says about how many fewer people moved on (the recent cohort times the rate it fell by) and what they were worth: an estimate, per currency and never added across them, from the offer prices of this step and the later purchase steps at the recent rates. The worst steps are listed first, by money, then people. A step with fewer than 100 people at the step before in either window says too few to tell and is never ranked; the 7-day rate needs 20. For a step counted by people, the recent cohort is also split by each person's first touch as of the day they reached the step before: channel, then the top 5 Meta campaigns and ads. People who reached the step before near the end of the window may not have moved on yet, so a slow step can look slightly worse. Start here before writing a conversion test idea.
273
+ \`${cli} funnels leaks\` (\`--funnel <key>\` for one funnel, \`--to <date>\` for the last day) finds where each funnel loses people now: each step's rate from the step before over the last 28 days and the last 7, against its own normal rate over the 56 days before. A step doing worse says about how many fewer people moved on (the recent cohort times the rate it fell by) and what they were worth: an estimate, per currency and never added across them, from the offer prices of this step and the later purchase steps at the recent rates. The worst steps are listed first, by money, then people. A step with fewer than 100 people at the step before in either window says too few to tell and is never ranked; the 7-day rate needs 20. For a step counted by people, the recent cohort is also split by each person's first touch as of the day they reached the step before: channel, then the top 5 Meta campaigns and ads. For a step people take days to reach, shown "within N days", only people who reached the step before at least N days before each window ended count, and only those who moved on within N days, in both windows, and its last 7 days end N days early, so a slow step compares fairly. Start here before writing a conversion test idea.
274
274
 
275
275
  \`${cli} funnels evidence\` gathers what the workspace's own data says it sells, to draft a map from: the prices people paid in the last 365 days on each platform with buyer counts, the most common orders in which one person paid (\`1500 → 497 → 3500\`, with the median days between), GoHighLevel funnels and their steps, calendars with bookings and shows, pipeline stages, the most used tags, webinars with sign-ups, viewers and offer clicks, the landing pages ads link to, and active Facebook campaigns with 30-day spend. A part whose source is not connected says so. Ids, counts and amounts only. The \`map-offers\` playbook (\`${cli} skill install map-offers\` installs it as the \`${MAP_OFFERS_SKILL_NAME}\` skill, with a worked example map beside it; \`${cli} docs map-offers\` prints it) walks an agent from the evidence to a draft.
276
276
 
@@ -336,7 +336,7 @@ A team dashboard is a page of charts the team opens for one client, such as dail
336
336
  ${JSON.stringify(dashboardExample)}
337
337
  \`\`\`
338
338
 
339
- Each panel is one SQL query and how to draw it: \`chart\` is \`line\`, \`bar\`, \`area\`, \`table\` or \`number\`. A line, bar or area panel names its \`x\` column and its \`y\` columns; a number panel one \`y\` column. \`format\` marks columns as \`money\`, \`count\` or \`percent\`, and \`width\` 2 takes the full row. \`dateRange.default\` is \`7d\`, \`30d\`, \`90d\`, \`this-month\` or \`last-month\`, and \`picker\` shows a date picker. Write \`{{from}}\` and \`{{to}}\` where the page's first and last day go; the server binds them as dates in the client's reporting time zone, and refuses any other \`{{...}}\`. A dashboard has at most 20 panels and 20 KB of SQL in each.
339
+ Each panel is one SQL query and how to draw it: \`chart\` is \`line\`, \`bar\`, \`area\`, \`table\` or \`number\`. A line, bar or area panel names its \`x\` column and its \`y\` columns; a number panel one \`y\` column. \`format\` marks columns as \`money\`, \`count\` or \`percent\`, and \`width\` 2 takes the full row. A \`percent\` value is a share written as a fraction, so 0.25 shows as 25%: send 0.25, not 25. \`dateRange.default\` is \`7d\`, \`30d\`, \`90d\`, \`this-month\` or \`last-month\`, and \`picker\` shows a date picker. Write \`{{from}}\` and \`{{to}}\` where the page's first and last day go; the server binds them as dates in the client's reporting time zone, and refuses any other \`{{...}}\`. A dashboard has at most 20 panels and 20 KB of SQL in each.
340
340
 
341
341
  Before saving, the server runs every panel once over the default range as this workspace's reader. A panel that fails, or lacks a column it names, refuses the whole spec with the panel's id and, when it knows one, a hint at the right name. The answer has \`url\`, the link that opens the dashboard for this client: give it to the person who asked.
342
342
 
@@ -439,6 +439,7 @@ ${cli} meta adset budget <id> | meta campaign budget <id> (--percent <n> | --dai
439
439
  ${cli} google-ads ads pause|resume <adgroup-id~ad-id>... | google-ads adgroup pause|resume <adgroup-id>... --reason "<why>" turn Google ads or ad groups on or off
440
440
  ${cli} google-ads campaign budget <id> (--percent <n> | --daily <amount>) --reason "<why>" change a Google Ads daily budget within the workspace's limits
441
441
  ${cli} google-ads changes [--since <date>] | google-ads changes undo <id> --reason "<why>" | google-ads changes reconcile <id> the Google Ads change log
442
+ ${cli} changes [--since <date>] one change log of Meta and Google Ads changes together, newest first
442
443
  ${cli} content publish-record <sku> --file <publication.json> record a publish before sending it
443
444
  ${cli} content publish-finish <publication-id> --status sending|published|failed|not_sent_practice [--input '<json>']
444
445
  ${cli} content track-link <sku> <url> [--channel facebook|email|sms|web] [--campaign <id>] tag a link with its SKU
package/dist/main.js CHANGED
@@ -25,6 +25,7 @@ import { installSkillFolder, operatorConnection, packSkill, parseSkillRef, readS
25
25
  const DEFAULT_WAIT_SECONDS = 300;
26
26
  const META_CHANGE_COLUMNS = ["id", "level", "object", "change", "status", "when", "by", "reason"];
27
27
  const GOOGLE_ADS_CHANGE_COLUMNS = ["id", "account", "level", "object", "change", "status", "when", "by", "reason"];
28
+ const AD_CHANGE_COLUMNS = ["platform", ...GOOGLE_ADS_CHANGE_COLUMNS];
28
29
  const SERVER_WAIT_CAP = 50;
29
30
  const VOICE_USAGE = "voice [--kind opener|objection|pain|desire|phrase] [--since <YYYY-MM-DD>] [--sort common|useful] [--limit <n>] [--json]";
30
31
  const CONTENT_PUBLISH_USAGE = "content publish <sku | item-id> (--to <publisher> --target '<json>' | --to wordpress:<site> [--status draft|publish] | --to webflow:<site> [--status draft|publish] [--url-prefix <https://...>] | --to strapi:<site> [--status draft|publish]) --idempotency-key <key> [--plan <plan-id>] [--version <n>] [--run <run-id>]";
@@ -1391,6 +1392,20 @@ const commands = {
1391
1392
  printGoogleAdsChangeResult({ changes: [change], unchanged: [] }, options.format);
1392
1393
  },
1393
1394
  },
1395
+ changes: {
1396
+ usage: "changes [--since <YYYY-MM-DD>] one log of every platform's ad changes, Meta and Google Ads together, newest first",
1397
+ async run(args, options) {
1398
+ if (args.length !== 0)
1399
+ throw new CliError("Usage: changes [--since <YYYY-MM-DD>]", exitCodes.usage);
1400
+ const query = new URLSearchParams();
1401
+ if (options.since)
1402
+ query.set("since", options.since);
1403
+ const { changes } = await api(await resolveConnection(options), `/api/v1/changes${query.size ? `?${query}` : ""}`);
1404
+ if (options.format === "json")
1405
+ return printJson({ changes });
1406
+ console.log(changes.length ? renderTable(AD_CHANGE_COLUMNS, changes.map(adChangeRow)) : "No ad changes yet.");
1407
+ },
1408
+ },
1394
1409
  "content unpublish": {
1395
1410
  usage: "content unpublish <deployment-id> [--force]",
1396
1411
  async run(args, options) {
@@ -3093,7 +3108,7 @@ function printTest(t) {
3093
3108
  lines.push(`Note: ${t.decision_note}`);
3094
3109
  console.log(lines.join("\n"));
3095
3110
  }
3096
- const RESULT_COLUMNS = ["side", "label", "counted by", "people", "moved on", "sales", "revenue", "spend", "metric", "dates"];
3111
+ const RESULT_COLUMNS = ["side", "label", "counted by", "people", "moved on", "buyers", "revenue", "spend", "metric", "dates"];
3097
3112
  function printResults(r) {
3098
3113
  const money = (n, currency) => (n === null ? "" : `${n.toFixed(2)}${currency ? ` ${currency}` : ""}`);
3099
3114
  const rows = r.arms.map((a) => ({
@@ -3102,8 +3117,8 @@ function printResults(r) {
3102
3117
  "counted by": a.counted_by,
3103
3118
  people: a.people,
3104
3119
  "moved on": a.moved_on ?? "",
3105
- sales: a.sales_first_touch ?? "",
3106
- revenue: money(a.revenue_first_touch, a.currency),
3120
+ buyers: a.buyers ?? "",
3121
+ revenue: money(a.revenue, a.currency),
3107
3122
  spend: money(a.spend, a.currency),
3108
3123
  metric: a.metric_value === null ? "" : r.test.metric === "cost_per_sale" || r.test.metric === "revenue_per_visitor" ? money(a.metric_value, a.currency) : `${(a.metric_value * 100).toFixed(1)}%`,
3109
3124
  dates: `${a.window.from} to ${a.window.to}`,
@@ -3335,7 +3350,7 @@ Options:
3335
3350
  --tool <tool> with media list, only this tool's outputs, such as kie:nano-banana
3336
3351
  --kind image|video with media list, only images or only videos
3337
3352
  --since, --until <YYYY-MM-DD | ISO time> with media list, made at or after --since and before --until (a day: through
3338
- its end, in UTC); with meta changes or google-ads changes, only changes from --since on
3353
+ its end, in UTC); with changes, meta changes or google-ads changes, only changes from --since on
3339
3354
  --cursor <media-id> with media list, the next page: the id it printed after "More"
3340
3355
  --platform, --external-id where content status launched says it was published, and that platform's id for it
3341
3356
  --object-type <type> what that platform's sync calls it (campaign_message or flow_message on klaviyo); needed where
@@ -4318,6 +4333,9 @@ function googleAdsChangeRow(c) {
4318
4333
  reason: excerpt(c.reason, 60),
4319
4334
  };
4320
4335
  }
4336
+ function adChangeRow(c) {
4337
+ return c.platform === "meta" ? { platform: c.platform, account: c.accountId, ...metaChangeRow(c) } : { platform: c.platform, ...googleAdsChangeRow(c) };
4338
+ }
4321
4339
  function printGoogleAdsChangeResult(result, format) {
4322
4340
  if (format === "json")
4323
4341
  return printJson(result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrangled/cli",
3
- "version": "0.3.64",
3
+ "version": "0.3.66",
4
4
  "description": "Command-line access to your marketing data warehouse, for people and coding agents",
5
5
  "type": "module",
6
6
  "bin": {