@wrangled/cli 0.3.37 → 0.3.38

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
@@ -40,7 +40,7 @@ ${brand.name} does not build pages; it hands whoever builds one what is already
40
40
 
41
41
  ## Which Facebook ads and pages won: winners and flops
42
42
 
43
- \`${cli} winners\` says which Facebook ads won and which flopped, for one Monday-to-Sunday week and for the 30 days ending that Sunday, judged on real sales: 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. An ad with no sales that has spent less than twice the account's cost per sale is too new to judge on sales, so it is judged on cost per sign-up and listed apart. 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 the week (default: the latest complete week), \`--top <n>\` how many ads, pages or pairs each list shows (default 5, at most 20), and \`--format json\` gives the report as data.
43
+ \`${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 real sales: 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. An ad with no sales that has spent less than twice the account's cost per sale is too new to judge on sales, so it is judged on cost per sign-up and listed apart. 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 sign-ups, and the report says so. \`--top <n>\` how many ads, pages or pairs each list shows (default 5, at most 20), and \`--format json\` gives the report as data.
44
44
 
45
45
  ## What winners and flops taught: lessons
46
46
 
@@ -50,11 +50,11 @@ Write one as JSON, \`{"text": "...", "dimension": "hook", "evidence": [{"ad": "<
50
50
 
51
51
  ## Sending winners to Exodus
52
52
 
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.
53
+ \`${cli} winners send-to-exodus\` sends the 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 freshest customer words of the dates. Winners whose page, words or format are unknown are left out and listed with why. \`--week <YYYY-MM-DD>\`, \`--days <n>\` or \`--from\` and \`--to\` pick the dates, as for \`${cli} winners\` (default: the latest complete week). \`--dry-run\` asks Exodus what it would create or update and writes nothing. Sending the same dates 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.
54
54
 
55
55
  ## The weekly loop: a sample for an orchestrator agent
56
56
 
57
- The \`weekly-loop\` playbook (\`${cli} skill install weekly-loop\` installs it as the \`${WEEKLY_LOOP_SKILL_NAME}\` skill; \`${cli} docs weekly-loop\` prints it) is a sample week for a local agent that runs a client's Facebook ads with its owner. It names the command and the MCP tool for each step: the winners and flops report, lessons, fresh customer words, the brief, the offers and funnels map, making ads (Exodus, paid tools or the agent's own skills), and the results of what was published, read back by Adzara's labels. Judge winners by ${brand.name}'s real-sales report (\`${cli} winners\`); Adzara's results are Meta's count. Approvals and decisions happen in the agent's own session with the owner; ${brand.name} records none. Ads are published and changed through Adzara, never through ${brand.name}.
57
+ The \`weekly-loop\` playbook (\`${cli} skill install weekly-loop\` installs it as the \`${WEEKLY_LOOP_SKILL_NAME}\` skill; \`${cli} docs weekly-loop\` prints it) is a sample to borrow from, not a schedule, for a local agent that runs a client's Facebook ads with its owner: its steps can run daily, several times a week or weekly, and the winners report covers whatever dates are asked for. It names the command and the MCP tool for each step: the winners and flops report, lessons, fresh customer words, the brief, the offers and funnels map, making ads (Exodus, paid tools or the agent's own skills), and the results of what was published, read back by Adzara's labels. Judge winners by ${brand.name}'s real-sales report (\`${cli} winners\`); Adzara's results are Meta's count. Approvals and decisions happen in the agent's own session with the owner; ${brand.name} records none. Ads are published and changed through Adzara, never through ${brand.name}.
58
58
 
59
59
  ## Trying one change: funnel tests
60
60
 
@@ -298,8 +298,8 @@ ${cli} guide what this workspace's data holds, how fresh
298
298
  ${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
299
299
  ${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
300
300
  ${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
301
- ${cli} winners [--week <date>] [--top <n>] which Facebook ads and landing pages won and flopped this week and over 30 days, judged on first-touch sales
302
- ${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
301
+ ${cli} winners [--week <date> | --days <n> | --from <date> --to <date>] [--top <n>] which Facebook ads and landing pages won and flopped in a week or any dates and over 30 days, judged on first-touch sales
302
+ ${cli} winners send-to-exodus [--week <date> | --days <n> | --from <date> --to <date>] [--dry-run] send the winners, with their reasons, lessons and customer words, to Exodus; safe to send again
303
303
  ${cli} warehouse schema [--summary] [--table <name> ...] [--search <words>] list tables and columns; --summary for one row per table
304
304
  ${cli} warehouse query "<sql>" run one read-only query (or --file <path>, or stdin); --out <file.csv | file.ndjson | file.json> saves the rows; add --all for every row
305
305
  ${cli} funnel [--from <date>] [--to <date>] [--grain day|week|month] spend, leads, calls and sales per period, with totals
package/dist/main.js CHANGED
@@ -670,12 +670,12 @@ const commands = {
670
670
  },
671
671
  },
672
672
  winners: {
673
- usage: "winners [--week <YYYY-MM-DD>] [--top <n>] (which Facebook ads and landing pages won and flopped, judged on real first-touch sales)",
673
+ usage: "winners [--week <YYYY-MM-DD> | --days <n> | --from <YYYY-MM-DD> --to <YYYY-MM-DD>] [--top <n>] (which Facebook ads and landing pages won and flopped, judged on real first-touch sales)",
674
674
  async run(args, options) {
675
675
  if (args.length > 0)
676
- throw new CliError("Usage: winners [--week <date>] [--top <n>]", exitCodes.usage);
676
+ throw new CliError("Usage: winners [--week <date> | --days <n> | --from <date> --to <date>] [--top <n>]", exitCodes.usage);
677
677
  const query = new URLSearchParams();
678
- for (const [name, value] of Object.entries({ week: options.week, top: options.top }))
678
+ for (const [name, value] of Object.entries({ ...winnersPeriod(options), top: options.top }))
679
679
  if (value !== undefined)
680
680
  query.set(name, value);
681
681
  const report = await api(await resolveConnection(options.apiUrl), `/api/v1/winners${query.size ? `?${query}` : ""}`);
@@ -685,14 +685,12 @@ const commands = {
685
685
  },
686
686
  },
687
687
  "winners send-to-exodus": {
688
- usage: "winners send-to-exodus [--week <YYYY-MM-DD>] [--dry-run] send the week's winning ads, with their reasons, lessons and customer words, to Exodus; safe to send again",
688
+ usage: "winners send-to-exodus [--week <YYYY-MM-DD> | --days <n> | --from <YYYY-MM-DD> --to <YYYY-MM-DD>] [--dry-run] send the winning ads, with their reasons, lessons and customer words, to Exodus; safe to send again",
689
689
  async run(args, options) {
690
690
  if (args.length > 0)
691
- throw new CliError("Usage: winners send-to-exodus [--week <date>] [--dry-run]", exitCodes.usage);
692
- const sent = await api(await resolveConnection(options.apiUrl), "/api/v1/winners/exodus", {
693
- ...(options.week === undefined ? {} : { week: options.week }),
694
- dryRun: options.dryRun,
695
- });
691
+ throw new CliError("Usage: winners send-to-exodus [--week <date> | --days <n> | --from <date> --to <date>] [--dry-run]", exitCodes.usage);
692
+ const period = Object.entries(winnersPeriod(options)).filter(([, value]) => value !== undefined);
693
+ const sent = await api(await resolveConnection(options.apiUrl), "/api/v1/winners/exodus", { ...Object.fromEntries(period), dryRun: options.dryRun });
696
694
  if (options.format === "json")
697
695
  printJson(sent);
698
696
  else
@@ -2059,7 +2057,7 @@ function winnersPageLines(page, currency) {
2059
2057
  return `- ${ad}${name}: ${counted(Math.round(n.visits), "visit")}, spent ${money(n.spend, currency)}, ${count ? `${count}, ` : ""}${cost}${bought}\n ${page.pickedBecause}\n`;
2060
2058
  }
2061
2059
  function winnersWindowText(title, window) {
2062
- const head = `\n## ${title}, ${window.from} to ${window.to}\n`;
2060
+ const head = `\n## ${title}\n`;
2063
2061
  if (window.sections.length === 0)
2064
2062
  return `${head}\nNo Facebook ad spent in these dates.\n`;
2065
2063
  return (head +
@@ -2095,8 +2093,19 @@ function winnersWindowText(title, window) {
2095
2093
  })
2096
2094
  .join(""));
2097
2095
  }
2096
+ /** The window flags as the API takes them; the server refuses two windows at once or half a range. */
2097
+ function winnersPeriod(options) {
2098
+ return { week: options.week, days: options.days, from: options.from, to: options.to };
2099
+ }
2100
+ /** "This week, 2026-09-28 to 2026-10-04", "Last 3 days, 2026-10-04 to 2026-10-06" or "2026-10-01 to 2026-10-03". */
2101
+ function winnersDatesTitle(window) {
2102
+ const dates = `${window.from} to ${window.to}`;
2103
+ if (window.kind === "week")
2104
+ return `This week, ${dates}`;
2105
+ return window.kind === "days" ? `Last ${counted(window.days, "day")}, ${dates}` : dates;
2106
+ }
2098
2107
  function winnersSendText(sent) {
2099
- const lines = [`Week ${sent.week.from} to ${sent.week.to}${sent.dryRun ? ", dry run: nothing was written to Exodus" : ""}.`];
2108
+ const lines = [`${sent.window.kind === "week" ? "Week " : ""}${sent.window.from} to ${sent.window.to}${sent.dryRun ? ", dry run: nothing was written to Exodus" : ""}.`];
2100
2109
  if (sent.pages.length === 0)
2101
2110
  lines.push("No winners to send.");
2102
2111
  for (const page of sent.pages) {
@@ -2123,12 +2132,12 @@ function winnersSendText(sent) {
2123
2132
  lines.push("Left out:", ...sent.skipped.map((s) => ` ${s.adId}: ${s.reason}`));
2124
2133
  return `${lines.join("\n")}\n`;
2125
2134
  }
2126
- /** The winners report as plain Markdown: what a sale means, then this week and the last 30 days, each currency apart. */
2135
+ /** The winners report as plain Markdown: what a sale means, then the dates asked for and the last 30 days, each currency apart. */
2127
2136
  function winnersText(report) {
2128
2137
  return (`# Winners and flops for ${report.workspace}\n\n` +
2129
2138
  `A sale means ${report.saleMeans}. Each sale goes to the Facebook ad that first brought the buyer in; payments typed into GoHighLevel by hand never count toward an ad.\n` +
2130
- winnersWindowText("This week", report.week) +
2131
- winnersWindowText("Last 30 days", report.last30Days));
2139
+ winnersWindowText(winnersDatesTitle(report.window), report.week) +
2140
+ winnersWindowText(`Last 30 days, ${report.last30Days.from} to ${report.last30Days.to}`, report.last30Days));
2132
2141
  }
2133
2142
  function reportQuery(options, extra) {
2134
2143
  const query = new URLSearchParams();
@@ -2384,6 +2393,8 @@ Options:
2384
2393
  --campaign <id> the campaign content track-link puts in utm_campaign; with brief, the one campaign it covers
2385
2394
  --top <n> how many best ads, worst ads, pages and campaigns brief shows, or ads in each winners list (default: 5, at most 20)
2386
2395
  --week <YYYY-MM-DD> any day of the week winners covers, Monday to Sunday (default: the latest complete week)
2396
+ --days <n> winners: the last n complete days, ending yesterday, instead of a week (at most 90); or give winners
2397
+ --from and --to for any dates
2387
2398
  --body, --html <text | @file> the words, or the full HTML, content create, capture and edit save
2388
2399
  --file <path> an image, video or audio file, up to 50 MB, content create, capture and edit save; repeat for several
2389
2400
  --transcript <text | @file> what is said in the first video or audio file content capture saves, so none is paid for
@@ -2489,6 +2500,7 @@ function parseOptions(argv) {
2489
2500
  campaign: { type: "string" },
2490
2501
  top: { type: "string" },
2491
2502
  week: { type: "string" },
2503
+ days: { type: "string" },
2492
2504
  name: { type: "string" },
2493
2505
  subject: { type: "string" },
2494
2506
  preview: { type: "string" },
@@ -2597,6 +2609,7 @@ function parseOptions(argv) {
2597
2609
  campaign: values.campaign,
2598
2610
  top: values.top,
2599
2611
  week: values.week,
2612
+ days: values.days,
2600
2613
  name: values.name,
2601
2614
  subject: values.subject,
2602
2615
  preview: values.preview,
package/dist/playbooks.js CHANGED
@@ -1558,15 +1558,15 @@ const testResultsSql = "SELECT test_id, currency, sum(spend) AS spend, sum(sales
1558
1558
  const lineageSql = "SELECT facebook_ad_id, hypothesis_id, test_id, creative_id, asset_version FROM adzara_ad_lineage ORDER BY synced_at DESC LIMIT 100";
1559
1559
  /** The commands the weekly loop shows, in order; its text is made from them, so a test runs exactly these. */
1560
1560
  export const weeklyLoopCommands = {
1561
- winners: { purpose: "the week's Facebook winners and flops, and the 30 days ending that Sunday, judged on real first-touch sales", argv: ["winners", "--week", "<week-monday>", "--format", "json"], mcp: "get_winners_report" },
1561
+ winners: { purpose: "the Facebook winners and flops of the dates asked for, here a week, and of the 30 days ending on their last day, judged on real first-touch sales", argv: ["winners", "--week", "<week-monday>", "--format", "json"], mcp: "get_winners_report" },
1562
1562
  lessons: { purpose: "what earlier weeks taught, the most often seen first", argv: ["lessons", "--format", "json"], mcp: "get_lessons" },
1563
1563
  writeLessons: { purpose: "install the lessons playbook, then follow it to add, reinforce or retire this week's lessons", argv: ["skill", "install", "lessons"], mcp: "record_lesson" },
1564
1564
  voice: { purpose: "fresh customer words: the most usable single lines said since that Monday", argv: ["voice", "--since", "<week-monday>", "--sort", "useful", "--json"], mcp: "get_customer_voice" },
1565
1565
  brief: { purpose: "the campaign brief: offer, voice, lessons, best and worst ads, pages and audience, as Markdown", argv: ["brief"], mcp: "get_campaign_brief" },
1566
1566
  funnelMap: { purpose: "the offers and funnels map: what is sold, at what price, and the steps a buyer takes", argv: ["funnels", "show"], mcp: "get_funnel_map" },
1567
1567
  cro: { purpose: "install the cro playbook, then follow it to turn the funnel's costliest leaks into test ideas for the owner and read the running tests", argv: ["skill", "install", "cro"], mcp: "draft_funnel_test" },
1568
- exodusPreview: { purpose: "ask Exodus what it would make from the week's winners, writing nothing", argv: ["winners", "send-to-exodus", "--week", "<week-monday>", "--dry-run"], mcp: "send_winners_to_exodus" },
1569
- exodusSend: { purpose: "send the week's winners to Exodus so it makes new ads from them; sending again is safe", argv: ["winners", "send-to-exodus", "--week", "<week-monday>"], mcp: "send_winners_to_exodus" },
1568
+ exodusPreview: { purpose: "ask Exodus what it would make from the winners of the same dates, writing nothing", argv: ["winners", "send-to-exodus", "--week", "<week-monday>", "--dry-run"], mcp: "send_winners_to_exodus" },
1569
+ exodusSend: { purpose: "send those winners to Exodus so it makes new ads from them; sending again is safe", argv: ["winners", "send-to-exodus", "--week", "<week-monday>"], mcp: "send_winners_to_exodus" },
1570
1570
  paidTools: { purpose: "the paid making tools this workspace can run; each run costs money", argv: ["tools", "list"], mcp: "list_tools" },
1571
1571
  testResults: { purpose: "spend, first-touch sales and cost per sale for each Adzara test over the last 30 days", argv: ["warehouse", "query", testResultsSql], mcp: "query_warehouse" },
1572
1572
  lineage: { purpose: "which Meta ad carries which Adzara labels: idea, test, creative and version", argv: ["warehouse", "query", lineageSql], mcp: "query_warehouse" },
@@ -1586,7 +1586,7 @@ ${commandLine(c)}
1586
1586
  Without a shell: MCP \`${c.mcp}\`.`;
1587
1587
  export const weeklyLoopPlaybook = `# Weekly loop playbook (sample)
1588
1588
 
1589
- A sample for a local orchestrator agent that runs a client's Facebook ads week with its owner. Once a week it reads what sold, learns from it, gathers fresh material, gets new ads made, has them published through Adzara, and later reads their results back. ${brand.name} supplies the data and keeps no part of the loop's decisions.
1589
+ A sample to borrow from, not a schedule, for a local orchestrator agent that runs a client's Facebook ads with its owner. It reads what sold, learns from it, gathers fresh material, gets new ads made, has them published through Adzara, and later reads their results back. It is written as a week, but its steps can run daily, several times a week or weekly, each on its own rhythm: the winners report covers whatever dates you ask for. ${brand.name} supplies the data and keeps no part of the loop's decisions.
1590
1590
 
1591
1591
  Every \`${cli}\` command acts on the one workspace the saved API key belongs to. Each step also names the MCP tool that does the same, for an agent connected over MCP without a shell.
1592
1592
 
@@ -1601,7 +1601,7 @@ Every \`${cli}\` command acts on the one workspace the saved API key belongs to.
1601
1601
 
1602
1602
  ${step(W.winners)}
1603
1603
 
1604
- ${W.winners.purpose[0].toUpperCase()}${W.winners.purpose.slice(1)}. Each ad has its \`numbers\`, \`pickedBecause\`, \`tags\` and Adzara \`test\`; each section's \`tests\` adds up each Adzara test. Leave out \`--week\` for the latest complete week.
1604
+ ${W.winners.purpose[0].toUpperCase()}${W.winners.purpose.slice(1)}. Each ad has its \`numbers\`, \`pickedBecause\`, \`tags\` and Adzara \`test\`; each section's \`tests\` adds up each Adzara test. Leave out \`--week\` for the latest complete week. To check more often, swap \`--week\` for \`--days <n>\`, the last n complete days ending yesterday, or \`--from <YYYY-MM-DD> --to <YYYY-MM-DD>\` for any dates (the MCP tool takes \`week\`, \`days\` or \`from\` and \`to\`). A short window holds few sales, so fewer ads can be judged on sales and more are judged on sign-ups; the report says so in \`notes\`.
1605
1605
 
1606
1606
  ## 2. Lessons
1607
1607
 
@@ -1641,7 +1641,7 @@ Follow the installed skill. It drafts at most 3 funnel tests, each with one chan
1641
1641
 
1642
1642
  Exodus is one way to make ads among several. Pick what fits the week.
1643
1643
 
1644
- **Exodus.** Preview what it would make from the week's winners, then send. The MCP tool takes \`week\` and \`dryRun\`. A real send needs the workspace's Exodus brand and a Scrape Creators key on the Exodus account.
1644
+ **Exodus.** Preview what it would make from the winners, then send. It takes the same dates as the winners report: \`--week\`, \`--days\` or \`--from\` and \`--to\`; over MCP, \`week\`, \`days\` or \`from\` and \`to\`, and \`dryRun\`. A real send needs the workspace's Exodus brand and a Scrape Creators key on the Exodus account.
1645
1645
 
1646
1646
  ${step(W.exodusPreview)}
1647
1647
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrangled/cli",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "description": "Command-line access to your marketing data warehouse, for people and coding agents",
5
5
  "type": "module",
6
6
  "bin": {