@wrangled/cli 0.3.31 → 0.3.32

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
@@ -193,11 +193,11 @@ The confirmed definition with key \`ad_name_key\` is the client's name key: the
193
193
 
194
194
  ## Ad tags
195
195
 
196
- Ads not made here (built by hand or by an agency, names in no pattern) get the marketing grammar's tags (docs/marketing-grammar.md) by Meta ad id. \`${cli} ads tags\` lists them (\`--ad <id>\`, \`--status proposed|confirmed|rejected|replaced\`, \`--limit <n>\`, \`--json\`); by default the live ones, proposed and confirmed. A proposed tag is usable at once, but not yet confirmed; only an Owner or Admin confirms, rejects or changes tags, on the app's Ad tags page.
196
+ Ads not made here (built by hand or by an agency, names in no pattern) get the marketing grammar's tags (docs/marketing-grammar.md) by Meta ad id. \`${cli} ads tags\` lists them (\`--ad <id>\`, \`--status proposed|confirmed|rejected|replaced\`, \`--limit <n>\`, \`--json\`); by default the live ones, proposed and confirmed. A proposed tag is usable at once, but not yet confirmed; only an Owner or Admin confirms, rejects or changes tags, on the app's Tag check page.
197
197
 
198
198
  To suggest tags, write them as JSON and run \`${cli} ads tag --file tags.json\` (or pipe it on stdin), up to 500 ads at a time: \`{"ads": [{"ad_id": "120214567890123", "tags": {"angle": "discovery", "desire": "money", "hook_spot": "spoken", "hook_text": "$29,000 is the smallest lead", "hook_kind": "big_win", "format": "video", "style": "presenter", "tag_reasons": {"worry": "none"}}, "confidence": "medium", "evidence": {"ad_name": "County lead v2", "transcript_start": "$29,000 is the smallest lead"}}]}\`. The tag fields are \`marketing\`, \`offer_key\`, \`landing_funnel_key\`, \`landing_step_key\`, \`persona\`, \`awareness\`, \`concept\`, \`angle\`, \`desire\`, \`hook_spot\`, \`hook_text\`, \`hook_kind\`, \`hook2_spot\`, \`hook2_kind\`, \`format\`, \`style\`, \`speaker\`, \`cta\`, \`proof\` and \`worry\`; leave out what you did not judge. A part with no value goes in \`tag_reasons\` as \`none\` (nothing on its list applies) or \`unclear\` (too little to judge, never \`high\` confidence). Confidence is \`high\`, \`medium\` or \`low\`. A value off its list is refused with the allowed values. New tags replace an ad's proposed ones, which stay as \`replaced\`; an ad whose tags a person confirmed is kept as it is.
199
199
 
200
- To have the server tag ads for you, run \`${cli} ads autotag\` (\`--limit <n>\`, 1 to 100, default 25; \`--ad <id>\` for one ad; \`--json\`). It takes the biggest spenders with no tags yet, so each run moves on to the next ones (with \`--ad\`, that ad, its proposed tags asked about again and confirmed ones kept), reads each one's copy, video transcript and picture description, and asks TypeSafe's Jev the grammar's questions through the tool gateway, so a run's tool cap applies. Ads with the same copy, headlines, button, link and media are asked about once and share the tags; asking again about an unchanged ad costs nothing. It costs a tiny fraction of a cent per ad. The tags are proposed, with each part's confidence in the evidence; read them with \`${cli} ads tags\`. The \`back-tag\` playbook (\`${cli} skill install back-tag\` installs it as the \`${BACK_TAG_SKILL_NAME}\` skill; \`${cli} docs back-tag\` prints it) runs it in batches over a client's old ads and reports how much of the spend is tagged; a person then checks the ads carrying the top 20% of spend on the app's Ad tags page.
200
+ To have the server tag ads for you, run \`${cli} ads autotag\` (\`--limit <n>\`, 1 to 100, default 25; \`--ad <id>\` for one ad; \`--json\`). It takes the biggest spenders with no tags yet, so each run moves on to the next ones (with \`--ad\`, that ad, its proposed tags asked about again and confirmed ones kept), reads each one's copy, video transcript and picture description, and asks TypeSafe's Jev the grammar's questions through the tool gateway, so a run's tool cap applies. Ads with the same copy, headlines, button, link and media are asked about once and share the tags; asking again about an unchanged ad costs nothing. It costs a tiny fraction of a cent per ad. The tags are proposed, with each part's confidence in the evidence; read them with \`${cli} ads tags\`. The \`back-tag\` playbook (\`${cli} skill install back-tag\` installs it as the \`${BACK_TAG_SKILL_NAME}\` skill; \`${cli} docs back-tag\` prints it) runs it in batches over a client's old ads and reports how much of the spend is tagged; a person then checks the ads carrying the top 20% of spend on the app's Tag check page.
201
201
 
202
202
  For what worked, query the \`ad_performance_tagged\` view: every Meta ad's daily results with its live tags next to them, \`tags_source\` (\`ad_tags\`, or null when untagged) and \`tags_status\`.
203
203
 
@@ -267,6 +267,7 @@ ${cli} knowledge tag <entry-id> --hook-kind <v> --confidence <c> tag a library
267
267
  ${cli} guide what this workspace's data holds, how fresh it is, and what is missing: read it first
268
268
  ${cli} brief [--from <date>] [--to <date>] [--channel <channel>] [--campaign <id>] [--top <n>] the offer, voice, best and worst ads, pages, results and audience: start here before writing ads
269
269
  ${cli} pages brief --step <key> [--funnel <key>] | --page <ref> the offer, step, current page, best and worst pages of its kind, its ads and how to hand a page back: start here before writing a landing page
270
+ ${cli} pages results [--from <date>] [--to <date>] [--page <ref>] each landing page's, and each ad and page pair's, real first-touch sales, cost per sale and sales per page view
270
271
  ${cli} winners [--week <date>] [--top <n>] which Facebook ads won and flopped this week and over 30 days, judged on first-touch sales
271
272
  ${cli} winners send-to-exodus [--week <date>] [--dry-run] send the week's winners, with their reasons, lessons and customer words, to Exodus; safe to send again
272
273
  ${cli} warehouse schema [--summary] [--table <name> ...] [--search <words>] list tables and columns; --summary for one row per table
package/dist/main.js CHANGED
@@ -658,6 +658,17 @@ const commands = {
658
658
  process.stdout.write(brief.text);
659
659
  },
660
660
  },
661
+ "pages results": {
662
+ usage: "pages results [--from <YYYY-MM-DD>] [--to <YYYY-MM-DD>] [--page <address | page key | item id>] (which landing pages, and which ad and page pairs, bring real first-touch sales)",
663
+ async run(args, options) {
664
+ if (args.length > 0)
665
+ throw new CliError("Usage: pages results [--from <date>] [--to <date>] [--page <address | page key | item id>]", exitCodes.usage);
666
+ const results = await api(await resolveConnection(options.apiUrl), `/api/v1/pages/results${reportQuery(options, { page: options.page })}`);
667
+ if (options.format === "json")
668
+ return printJson(results);
669
+ printPageResults(results);
670
+ },
671
+ },
661
672
  winners: {
662
673
  usage: "winners [--week <YYYY-MM-DD>] [--top <n>] (which Facebook ads won and flopped, judged on real first-touch sales)",
663
674
  async run(args, options) {
@@ -1999,6 +2010,30 @@ function reportQuery(options, extra) {
1999
2010
  query.set(name, value);
2000
2011
  return query.size ? `?${query}` : "";
2001
2012
  }
2013
+ const PAGE_NUMBERS = ["spend", "page views", "leads", "sales", "by ad link", "revenue", "cost/sale", "sales/view"];
2014
+ function pageNumbers(r) {
2015
+ return {
2016
+ page: r.pageKey === null ? "(no page named)" : r.pageKey.replace(/^web:page:/, ""),
2017
+ currency: r.currency,
2018
+ spend: r.spend,
2019
+ "page views": r.landingPageViews,
2020
+ leads: r.leads,
2021
+ sales: r.sales,
2022
+ "by ad link": r.salesByAdLink,
2023
+ revenue: r.revenue,
2024
+ "cost/sale": r.costPerSale,
2025
+ "sales/view": r.salesPerLandingPageView,
2026
+ };
2027
+ }
2028
+ function printPageResults(results) {
2029
+ if (results.pages.length === 0)
2030
+ return console.log(`No page results from ${results.from} to ${results.to}.`);
2031
+ console.log(`Landing pages, most first-touch sales first, ${results.from} to ${results.to}:`);
2032
+ console.log(renderTable(["page", "headline", "currency", ...PAGE_NUMBERS], results.pages.map((r) => ({ ...pageNumbers(r), headline: r.headline === null ? "" : excerpt(r.headline, 40) }))));
2033
+ console.log("\nAd and page pairs:");
2034
+ console.log(renderTable(["page", "ad", "ad name", "currency", ...PAGE_NUMBERS], results.adPages.map((r) => ({ ...pageNumbers(r), ad: r.adId ?? "(no Meta ad)", "ad name": r.adName === null ? "" : excerpt(r.adName, 30) }))));
2035
+ console.log('\n"by ad link" counts sales whose buyer\'s landing page was not recorded, credited to the page the ad links to now.');
2036
+ }
2002
2037
  /** The rows, then the totals, which are one row per currency. */
2003
2038
  /** What confirmed leave-out rules took out of `figures`, one line per rule and currency. */
2004
2039
  function printLeftOut(leftOut, figures) {
@@ -2168,8 +2203,8 @@ Options:
2168
2203
  --force content unpublish: take the page down even while delivering Meta ads link to it
2169
2204
  --plan <json> meta adset publish: the ad set to make, {"kind": "copy", ...} or {"kind": "create", ...}
2170
2205
  --yes confirm intake delete or import remove, which delete saved data for good
2171
- --from <YYYY-MM-DD> first day funnel, campaigns, money and brief cover (default: 29 days before --to)
2172
- --to <YYYY-MM-DD> last day funnel, campaigns, money and brief cover (default: today in the workspace reporting time zone)
2206
+ --from <YYYY-MM-DD> first day funnel, campaigns, money, brief and pages results cover (default: 29 days before --to)
2207
+ --to <YYYY-MM-DD> last day funnel, campaigns, money, brief and pages results cover (default: today in the workspace reporting time zone)
2173
2208
  --grain <grain> how funnel and money group days: day (default), week (starting Monday) or month
2174
2209
  --channel <channel> only this channel's campaigns (and, with brief, its ads): meta, google, affiliate, a utm_source, unknown or recorded_in_crm;
2175
2210
  for content track-link, where the link goes: facebook, email, sms or web (default)
@@ -2201,7 +2236,8 @@ Options:
2201
2236
  --step is in, when two funnels have a step with that key
2202
2237
  --step <key> with plans create, the step of --funnel this push itself is, as on the confirmed map;
2203
2238
  with pages brief, the step whose page the brief is for
2204
- --page <ref> with pages brief, the page to make a new version of: its address, page key, item id or SKU
2239
+ --page <ref> with pages brief, the page to make a new version of: its address, page key, item id or SKU;
2240
+ with pages results, the one page to show: its address, page key or item id
2205
2241
  --harness <list> which agents skill publish marks the skill for: claude, codex, or claude,codex (default)
2206
2242
  --text <words> words content search finds in a subject, headline, body or video transcript
2207
2243
  --mode word|meaning how content search matches --text: word (default, the words themselves) or meaning (close in meaning, nearest first; no --sort)
package/dist/playbooks.js CHANGED
@@ -830,7 +830,7 @@ You tag one client workspace's old Facebook ads in ${brand.name} with the market
830
830
 
831
831
  You do not work out the tags yourself. The server's tagger does: \`${cli} ads autotag\` reads each ad's copy, headlines, video transcript and picture description and answers the grammar's questions, following the hard cases in docs/marketing-grammar.md (Persona is who, Angle is the way in, a hook judged at its spot, a video that says one thing while the text says another). Your job is to run it in batches, stop at the right time, and report how much of the spend is now tagged.
832
832
 
833
- Every tag the tagger saves is proposed: usable at once, marked not yet confirmed. You never confirm, fix or reject tags; an API key cannot. A person checks the ads that carry the top 20% of spend on the Ad tags page in the ${brand.name} web app.
833
+ Every tag the tagger saves is proposed: usable at once, marked not yet confirmed. You never confirm, fix or reject tags; an API key cannot. A person checks the ads that carry the top 20% of spend on the Tag check page in the ${brand.name} web app.
834
834
 
835
835
  You work only through the \`${cli}\` CLI. Every command acts on the one workspace the saved API key belongs to.
836
836
 
@@ -877,7 +877,7 @@ Run the coverage query from step 1 again, then finish with a short report in pla
877
877
  - How many ads you tagged this run and what it cost.
878
878
  - How many ads that spent anything now have tags, and what share of the spend that is (\`pct_spend_tagged\`); what share of spend a person has confirmed (\`pct_spend_confirmed\`). Give each currency's shares on their own; never add them together.
879
879
  - How many ads are still untagged and why, if the batches said: \`nothingToRead\` ads have nothing to read (no copy, headline, transcript or picture description), plus any failures.
880
- - What to do next: open the Ad tags page in the ${brand.name} web app, press Make the list (or Make a new list) under Check these first, and confirm, fix or skip the ads that carry the top 20% of spend. Everything else stays as the tagger's best guess, marked not yet confirmed.
880
+ - What to do next: open the Tag check page in the ${brand.name} web app, press Make the list (or Make a new list) under Check these first, and confirm, fix or skip the ads that carry the top 20% of spend. Everything else stays as the tagger's best guess, marked not yet confirmed.
881
881
 
882
882
  ## Commands
883
883
 
@@ -889,7 +889,7 @@ ${cli} warehouse query --file coverage.sql how much of the spend is tagg
889
889
  `;
890
890
  export const backTagSkill = `---
891
891
  name: ${BACK_TAG_SKILL_NAME}
892
- description: Tag a ${brand.name} client workspace's old Facebook ads with the marketing grammar, biggest spenders first. Runs the server's tagger (\`${cli} ads autotag\`) in batches until no untagged ads with spend are left or the budget is reached, never confirms (a person checks the top 20% of spend on the Ad tags page), and reports how much of the spend is tagged and confirmed. Use when asked to back-tag a client's ads, tag old ads, or run the ${BACK_TAG_SKILL_NAME} playbook.
892
+ description: Tag a ${brand.name} client workspace's old Facebook ads with the marketing grammar, biggest spenders first. Runs the server's tagger (\`${cli} ads autotag\`) in batches until no untagged ads with spend are left or the budget is reached, never confirms (a person checks the top 20% of spend on the Tag check page), and reports how much of the spend is tagged and confirmed. Use when asked to back-tag a client's ads, tag old ads, or run the ${BACK_TAG_SKILL_NAME} playbook.
893
893
  ---
894
894
 
895
895
  ${backTagPlaybook}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrangled/cli",
3
- "version": "0.3.31",
3
+ "version": "0.3.32",
4
4
  "description": "Command-line access to your marketing data warehouse, for people and coding agents",
5
5
  "type": "module",
6
6
  "bin": {