@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 +3 -2
- package/dist/main.js +39 -3
- package/dist/playbooks.js +3 -3
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
2172
|
-
--to <YYYY-MM-DD> last day funnel, campaigns, money and
|
|
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
|
|
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
|
|
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
|
|
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}`;
|