@wrangled/cli 0.3.31 → 0.3.33
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 +24 -2
- package/dist/main.js +131 -4
- package/dist/playbooks.js +3 -3
- package/package.json +1 -1
package/dist/guide.js
CHANGED
|
@@ -3,6 +3,20 @@ import { brand, envPrefix } from "./brand.js";
|
|
|
3
3
|
import { BACK_TAG_SKILL_NAME, GRAMMAR_SKILL_NAME, LEARN_NAMES_SKILL_NAME, LESSONS_SKILL_NAME, MAP_OFFERS_SKILL_NAME, PLAYBOOK_CHOICES, STORY_SKILL_NAME } from "./playbooks.js";
|
|
4
4
|
import { PULL_STATE_FILE } from "./pull.js";
|
|
5
5
|
const cli = brand.slug;
|
|
6
|
+
/** The funnel test the guide shows; the app's parser must accept it (src/lib/cli-consistency.test.ts). */
|
|
7
|
+
export const funnelTestExample = {
|
|
8
|
+
hypothesis: "A video at the top of the sign-up page will get more people to sign up, because buyers on calls say they did not know what the webinar was about.",
|
|
9
|
+
funnel_key: "webinar",
|
|
10
|
+
step_key: "signup",
|
|
11
|
+
variable: "top of the page: letter to video",
|
|
12
|
+
control: { label: "letter at the top", url: "https://example.com/signup" },
|
|
13
|
+
variants: [{ label: "video at the top", url: "https://example.com/signup-video" }],
|
|
14
|
+
metric: "step_conversion",
|
|
15
|
+
expected_change: "sign-ups per visitor from 22% to 26%",
|
|
16
|
+
min_people_per_arm: 400,
|
|
17
|
+
split_method: "adzara_ads",
|
|
18
|
+
evidence: [{ kind: "customer_words", ref: "sales call 2026-09-30", note: "I didn't know what the webinar was about." }],
|
|
19
|
+
};
|
|
6
20
|
export const guide = `# ${brand.name} CLI
|
|
7
21
|
|
|
8
22
|
\`${cli}\` gives read-only SQL access to one client's marketing data warehouse (ad spend, orders, and other synced sources), runs paid generation and research tools for that client, and keeps the creative ledger. Each API key belongs to exactly one workspace, and every command works only in that workspace.
|
|
@@ -38,6 +52,12 @@ Write one as JSON, \`{"text": "...", "dimension": "hook", "evidence": [{"ad": "<
|
|
|
38
52
|
|
|
39
53
|
\`${cli} winners send-to-exodus\` sends the week's winners to Exodus so it makes new ads from them: one package per Facebook page, each winner with its reason, its numbers, and a note of the lessons it proves, persona lessons and the week's freshest customer words. Winners whose page, words or format are unknown are left out and listed with why. \`--week <YYYY-MM-DD>\` picks the week (default: the latest complete week). \`--dry-run\` asks Exodus what it would create or update and writes nothing. Sending the same week again is safe: an unchanged package is not sent twice, and Exodus updates each ad in place. A real send needs the workspace's Exodus brand and a Scrape Creators key on the Exodus account.
|
|
40
54
|
|
|
55
|
+
## Trying one change: funnel tests
|
|
56
|
+
|
|
57
|
+
A funnel test writes down one change to a page or funnel step, tried against what runs now: the idea, the funnel and step (\`funnel_key\` and \`step_key\` from the funnels map), the one thing that changes, the control and 1 to 3 changed versions, the number that judges it (\`metric\`: step_conversion, sales_per_visitor, revenue_per_visitor or cost_per_sale) with the change expected and the people each side needs, how traffic is split (\`split_method\`: adzara_ads, before_after, or other with a \`split_note\`), and the evidence it should work (each \`{kind, ref, note}\`, kind one of leak, lesson, customer_words, page, ad or other). A test changes one thing: a \`variable\` naming two is refused, and so is a test with no metric.
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
41
61
|
## Content: search before SQL
|
|
42
62
|
|
|
43
63
|
To find content, such as "the 10 emails with the best click rate", "our 5 best videos by return on ad spend" or "every landing page that mentions the guarantee", use \`${cli} content search\` before writing SQL on the \`content_\` tables.
|
|
@@ -193,11 +213,11 @@ The confirmed definition with key \`ad_name_key\` is the client's name key: the
|
|
|
193
213
|
|
|
194
214
|
## Ad tags
|
|
195
215
|
|
|
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
|
|
216
|
+
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
217
|
|
|
198
218
|
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
219
|
|
|
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
|
|
220
|
+
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
221
|
|
|
202
222
|
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
223
|
|
|
@@ -267,6 +287,7 @@ ${cli} knowledge tag <entry-id> --hook-kind <v> --confidence <c> tag a library
|
|
|
267
287
|
${cli} guide what this workspace's data holds, how fresh it is, and what is missing: read it first
|
|
268
288
|
${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
289
|
${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
|
|
290
|
+
${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
291
|
${cli} winners [--week <date>] [--top <n>] which Facebook ads won and flopped this week and over 30 days, judged on first-touch sales
|
|
271
292
|
${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
293
|
${cli} warehouse schema [--summary] [--table <name> ...] [--search <words>] list tables and columns; --summary for one row per table
|
|
@@ -324,6 +345,7 @@ ${cli} funnels draft --file <map.json> [--note <why>] [--evidence '<json>' | @fi
|
|
|
324
345
|
${cli} facts [--kind <kind>] [--subject <type>[:<ref>]] [--status <status>] [--json] | facts show <id> settled facts about the data, to treat as true
|
|
325
346
|
${cli} facts propose --file <fact.json> suggest a fact for a person to confirm
|
|
326
347
|
${cli} lessons [--status active|retired] | lessons add --file <lesson.json> | lessons retire <id> --note "<why>" lessons from winners and flops, writing one, retiring one
|
|
348
|
+
${cli} tests [--status <status>] [--funnel <key>] [--limit <n>] | tests show <id> | tests draft --file <test.json> | tests start <id> | tests abandon <id> [--note "<why>"] funnel tests, one change each
|
|
327
349
|
${cli} ads tags [--ad <id>] [--status <status>] [--limit <n>] | ads tag --file <tags.json> the marketing tags of ads not made here, and suggesting them
|
|
328
350
|
${cli} ads autotag [--limit <n>] [--ad <id>] have the server propose tags for the biggest spenders not yet tagged
|
|
329
351
|
${cli} content tags [<item-id>] [--kind email|text|page] | content to-check the marketing tags of emails, texts and landing pages, and those waiting for a person
|
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) {
|
|
@@ -1420,6 +1431,70 @@ const commands = {
|
|
|
1420
1431
|
: `Lesson ${lesson.id} already counted this week (${weeks}); its proof was updated.`);
|
|
1421
1432
|
},
|
|
1422
1433
|
},
|
|
1434
|
+
tests: {
|
|
1435
|
+
usage: "tests [list] [--status <status>] [--funnel <funnel_key>] [--limit <n>] the page and funnel tests written down, newest first (50 unless --limit, at most 200)",
|
|
1436
|
+
async run(args, options) {
|
|
1437
|
+
if (!(args.length === 0 || (args.length === 1 && args[0] === "list")))
|
|
1438
|
+
throw new CliError("Usage: tests [list] [--status <status>] [--funnel <funnel_key>] [--limit <n>]", exitCodes.usage);
|
|
1439
|
+
const query = new URLSearchParams();
|
|
1440
|
+
if (options.status)
|
|
1441
|
+
query.set("status", options.status);
|
|
1442
|
+
if (options.funnel)
|
|
1443
|
+
query.set("funnel", options.funnel);
|
|
1444
|
+
if (options.limit)
|
|
1445
|
+
query.set("limit", options.limit);
|
|
1446
|
+
const { tests } = await api(await resolveConnection(options.apiUrl), `${TESTS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1447
|
+
if (options.format === "json")
|
|
1448
|
+
return printJson({ tests });
|
|
1449
|
+
if (tests.length === 0)
|
|
1450
|
+
return console.log("No funnel tests.");
|
|
1451
|
+
console.log(renderTable(TEST_COLUMNS, tests.map(testRow)));
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
"tests show": {
|
|
1455
|
+
usage: "tests show <id|test_id> one funnel test in full",
|
|
1456
|
+
async run(args, options) {
|
|
1457
|
+
if (args.length !== 1)
|
|
1458
|
+
throw new CliError("Usage: tests show <id|test_id>", exitCodes.usage);
|
|
1459
|
+
const { test } = await api(await resolveConnection(options.apiUrl), testPath(args[0]));
|
|
1460
|
+
if (options.format === "json")
|
|
1461
|
+
return printJson({ test });
|
|
1462
|
+
printTest(test);
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
"tests draft": {
|
|
1466
|
+
usage: "tests draft --file <test.json> | < test.json write down a funnel test: one change against a control, a metric and the evidence",
|
|
1467
|
+
async run(args, options) {
|
|
1468
|
+
if (args.length > 0)
|
|
1469
|
+
throw new CliError("Usage: tests draft --file <test.json> | < test.json", exitCodes.usage);
|
|
1470
|
+
const { test } = await api(await resolveConnection(options.apiUrl), TESTS_PATH, await readToolInput(options));
|
|
1471
|
+
if (options.format === "json")
|
|
1472
|
+
return printJson({ test });
|
|
1473
|
+
console.log(`Drafted test ${test.test_id} (${test.id}).`);
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
"tests start": {
|
|
1477
|
+
usage: "tests start <id|test_id> mark a draft test as running from today; nothing goes live",
|
|
1478
|
+
async run(args, options) {
|
|
1479
|
+
if (args.length !== 1)
|
|
1480
|
+
throw new CliError("Usage: tests start <id|test_id>", exitCodes.usage);
|
|
1481
|
+
const { test } = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/start`, {});
|
|
1482
|
+
if (options.format === "json")
|
|
1483
|
+
return printJson({ test });
|
|
1484
|
+
console.log(`Test ${test.test_id} is running from ${test.started_on}.`);
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
"tests abandon": {
|
|
1488
|
+
usage: 'tests abandon <id|test_id> [--note "<why>"] drop a test that has not ended',
|
|
1489
|
+
async run(args, options) {
|
|
1490
|
+
if (args.length !== 1)
|
|
1491
|
+
throw new CliError('Usage: tests abandon <id|test_id> [--note "<why>"]', exitCodes.usage);
|
|
1492
|
+
const { test } = await api(await resolveConnection(options.apiUrl), `${testPath(args[0])}/abandon`, options.note ? { note: options.note } : {});
|
|
1493
|
+
if (options.format === "json")
|
|
1494
|
+
return printJson({ test });
|
|
1495
|
+
console.log(`Abandoned test ${test.test_id}.`);
|
|
1496
|
+
},
|
|
1497
|
+
},
|
|
1423
1498
|
"lessons retire": {
|
|
1424
1499
|
usage: 'lessons retire <id> --note "<why it is no longer true>" retire a lesson the numbers no longer back; it is kept with why',
|
|
1425
1500
|
async run(args, options) {
|
|
@@ -1999,6 +2074,30 @@ function reportQuery(options, extra) {
|
|
|
1999
2074
|
query.set(name, value);
|
|
2000
2075
|
return query.size ? `?${query}` : "";
|
|
2001
2076
|
}
|
|
2077
|
+
const PAGE_NUMBERS = ["spend", "page views", "leads", "sales", "by ad link", "revenue", "cost/sale", "sales/view"];
|
|
2078
|
+
function pageNumbers(r) {
|
|
2079
|
+
return {
|
|
2080
|
+
page: r.pageKey === null ? "(no page named)" : r.pageKey.replace(/^web:page:/, ""),
|
|
2081
|
+
currency: r.currency,
|
|
2082
|
+
spend: r.spend,
|
|
2083
|
+
"page views": r.landingPageViews,
|
|
2084
|
+
leads: r.leads,
|
|
2085
|
+
sales: r.sales,
|
|
2086
|
+
"by ad link": r.salesByAdLink,
|
|
2087
|
+
revenue: r.revenue,
|
|
2088
|
+
"cost/sale": r.costPerSale,
|
|
2089
|
+
"sales/view": r.salesPerLandingPageView,
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
function printPageResults(results) {
|
|
2093
|
+
if (results.pages.length === 0)
|
|
2094
|
+
return console.log(`No page results from ${results.from} to ${results.to}.`);
|
|
2095
|
+
console.log(`Landing pages, most first-touch sales first, ${results.from} to ${results.to}:`);
|
|
2096
|
+
console.log(renderTable(["page", "headline", "currency", ...PAGE_NUMBERS], results.pages.map((r) => ({ ...pageNumbers(r), headline: r.headline === null ? "" : excerpt(r.headline, 40) }))));
|
|
2097
|
+
console.log("\nAd and page pairs:");
|
|
2098
|
+
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) }))));
|
|
2099
|
+
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.');
|
|
2100
|
+
}
|
|
2002
2101
|
/** The rows, then the totals, which are one row per currency. */
|
|
2003
2102
|
/** What confirmed leave-out rules took out of `figures`, one line per rule and currency. */
|
|
2004
2103
|
function printLeftOut(leftOut, figures) {
|
|
@@ -2030,6 +2129,32 @@ const GRAMMAR_PATH = "/api/v1/grammar";
|
|
|
2030
2129
|
const VOICE_LISTS_PATH = "/api/v1/voice-lists";
|
|
2031
2130
|
const VOICE_SUGGESTIONS_PATH = "/api/v1/voice-lists/suggestions";
|
|
2032
2131
|
const LESSONS_PATH = "/api/v1/lessons";
|
|
2132
|
+
const TESTS_PATH = "/api/v1/funnel-tests";
|
|
2133
|
+
const TEST_COLUMNS = ["test_id", "status", "funnel", "step", "change", "metric"];
|
|
2134
|
+
const testPath = (ref) => `${TESTS_PATH}/${encodeURIComponent(ref)}`;
|
|
2135
|
+
function testRow(t) {
|
|
2136
|
+
return { test_id: t.test_id, status: t.status, funnel: t.funnel_key, step: t.step_key, change: excerpt(t.variable, 50), metric: t.metric };
|
|
2137
|
+
}
|
|
2138
|
+
function printTest(t) {
|
|
2139
|
+
const arm = (a) => [a.label, a.page && `page ${a.page}`, a.url, a.item_id && `item ${a.item_id}`, a.asset_version && `version ${a.asset_version}`].filter(Boolean).join(" · ");
|
|
2140
|
+
const lines = [
|
|
2141
|
+
`${t.test_id} (${t.status}) idea ${t.hypothesis_id} id ${t.id}`,
|
|
2142
|
+
`Idea: ${t.hypothesis}`,
|
|
2143
|
+
`Where: ${t.funnel_key} / ${t.step_key}`,
|
|
2144
|
+
`The one change: ${t.variable}`,
|
|
2145
|
+
`Stays the same: ${arm(t.control)}`,
|
|
2146
|
+
...t.variants.map((v, i) => `${t.variants.length === 1 ? "Changed" : `Changed ${i + 1}`}: ${arm(v)}`),
|
|
2147
|
+
`Judged by: ${t.metric}, ${t.expected_change}, ${t.min_people_per_arm} people each side`,
|
|
2148
|
+
`Split: ${t.split_method}${t.split_note ? `, ${t.split_note}` : ""}`,
|
|
2149
|
+
"Evidence:",
|
|
2150
|
+
...t.evidence.map((e) => ` ${e.kind}: ${e.ref}${e.note ? ` (${e.note})` : ""}`),
|
|
2151
|
+
];
|
|
2152
|
+
if (t.started_on)
|
|
2153
|
+
lines.push(`Started: ${t.started_on}${t.ended_on ? `, ended ${t.ended_on}` : ""}`);
|
|
2154
|
+
if (t.decision_note)
|
|
2155
|
+
lines.push(`Note: ${t.decision_note}`);
|
|
2156
|
+
console.log(lines.join("\n"));
|
|
2157
|
+
}
|
|
2033
2158
|
const LESSON_COLUMNS = ["id", "seen", "last week", "dimension", "lesson"];
|
|
2034
2159
|
function lessonRow(l) {
|
|
2035
2160
|
const row = { id: l.id, seen: l.timesSeen, "last week": l.lastSeenWeek, dimension: l.dimension ?? "", lesson: excerpt(l.text, 100) };
|
|
@@ -2168,8 +2293,8 @@ Options:
|
|
|
2168
2293
|
--force content unpublish: take the page down even while delivering Meta ads link to it
|
|
2169
2294
|
--plan <json> meta adset publish: the ad set to make, {"kind": "copy", ...} or {"kind": "create", ...}
|
|
2170
2295
|
--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
|
|
2296
|
+
--from <YYYY-MM-DD> first day funnel, campaigns, money, brief and pages results cover (default: 29 days before --to)
|
|
2297
|
+
--to <YYYY-MM-DD> last day funnel, campaigns, money, brief and pages results cover (default: today in the workspace reporting time zone)
|
|
2173
2298
|
--grain <grain> how funnel and money group days: day (default), week (starting Monday) or month
|
|
2174
2299
|
--channel <channel> only this channel's campaigns (and, with brief, its ads): meta, google, affiliate, a utm_source, unknown or recorded_in_crm;
|
|
2175
2300
|
for content track-link, where the link goes: facebook, email, sms or web (default)
|
|
@@ -2188,7 +2313,8 @@ Options:
|
|
|
2188
2313
|
--object-type <type> what that platform's sync calls it (campaign_message or flow_message on klaviyo); needed where
|
|
2189
2314
|
the sync matches several kinds, so its next read joins the item
|
|
2190
2315
|
--notes <text> what changed in the version skill publish adds
|
|
2191
|
-
--note <why> why: what content create and edit changed,
|
|
2316
|
+
--note <why> why: what content create and edit changed, why lessons retire says a lesson is no longer true,
|
|
2317
|
+
or why tests abandon drops one
|
|
2192
2318
|
--version <n> the map version funnels show prints (default: the confirmed one)
|
|
2193
2319
|
--based-on <n> the map version funnels draft was written from; refused when someone saved since
|
|
2194
2320
|
(leave it out only for the first map, when there is neither a map nor a draft)
|
|
@@ -2201,7 +2327,8 @@ Options:
|
|
|
2201
2327
|
--step is in, when two funnels have a step with that key
|
|
2202
2328
|
--step <key> with plans create, the step of --funnel this push itself is, as on the confirmed map;
|
|
2203
2329
|
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
|
|
2330
|
+
--page <ref> with pages brief, the page to make a new version of: its address, page key, item id or SKU;
|
|
2331
|
+
with pages results, the one page to show: its address, page key or item id
|
|
2205
2332
|
--harness <list> which agents skill publish marks the skill for: claude, codex, or claude,codex (default)
|
|
2206
2333
|
--text <words> words content search finds in a subject, headline, body or video transcript
|
|
2207
2334
|
--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}`;
|