@wrangled/cli 0.3.22 → 0.3.23

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
@@ -35,9 +35,10 @@ To read the whole library with your own tools, such as "every email we have" or
35
35
 
36
36
  ## Know-how library: Creative Cartel
37
37
 
38
- For what has worked for others, such as "how do members test new hooks" or "what do people say about advertorials", search the know-how library: \`${cli} knowledge search "<question>"\`. Today it holds Creative Cartel's library (\`creative-cartel\`) and the team's own Exodus copy and ad frameworks (\`exodus\`), both copied into this server every Monday afternoon. It works only when the key has library access (\`${cli} whoami\` says); without it the answer is \`knowledge_not_allowed\`. It finds snippets by their words and by their meaning, and returns them short, never whole documents: each at most 280 characters, with its title, link, kind, caveat, evidence and the day its library was copied in. Evidence \`reported member statement\` is a member's claim, not a checked result; weigh it that way and say so when you use it. Evidence \`team method\` is the team's own framework: reason with it, never paste it into client work. The library writes no answer for you: read the snippets and decide. Use your own words: saving text that repeats 12 words in a row from anything in the library is refused with \`copies_library_text\`.
38
+ For what has worked for others, such as "how do members test new hooks" or "what do people say about advertorials", search the know-how library: \`${cli} knowledge search "<question>"\`. Today it holds Creative Cartel's library (\`creative-cartel\`) the team's own Exodus copy and ad frameworks (\`exodus\`), and Meta's advertising standards (\`meta-policy\`), all copied into this server every Monday afternoon. It works only when the key has library access (\`${cli} whoami\` says); without it the answer is \`knowledge_not_allowed\`. It finds snippets by their words and by their meaning, and returns them short, never whole documents: each at most 280 characters, with its title, link, kind, caveat, evidence and the day its library was copied in. Evidence \`reported member statement\` is a member's claim, not a checked result; weigh it that way and say so when you use it. Evidence \`team method\` is the team's own framework: reason with it, never paste it into client work. The library writes no answer for you: read the snippets and decide. Use your own words: saving text that repeats 12 words in a row from anything in the library is refused with \`copies_library_text\`.
39
39
 
40
40
  - The question is 1 to 4000 characters. \`--limit\` is 1 to 12 (default 5). \`--kind\` (message, transcript, document, note) keeps one kind of source. \`--library <slug>\` keeps one library.
41
+ - \`--library meta-policy\` searches Meta's own advertising standards, copied from Meta's site every Monday. Before you write or judge an ad, check its claims there, such as income, health or before-and-after claims: \`${cli} knowledge search --library meta-policy "income claims"\`. Its evidence is \`meta_published_policy\`, Meta's published rule; Meta's reviewers still decide each ad.
41
42
  - \`knowledge_not_synced\` means the library has not been copied into this server yet, so nothing was searched; the weekly sync fills it.
42
43
 
43
44
  ## Query workflow: schema first
@@ -190,7 +191,7 @@ Tag ads, emails, texts and pages only with the words \`${cli} grammar\` lists (\
190
191
 
191
192
  ## What customers keep saying
192
193
 
193
- Every hour an AI reads the workspace's new Facebook and Instagram comments, Google reviews and Reddit posts and comments, pulls out openers (lines that would open an ad), objections (why people hesitate) and phrases (customers' own words for their problem or result), and groups the ones that say the same thing. Names, emails, phone numbers and handles are taken out first. \`${cli} voice\` lists the groups, most common first (\`--kind opener|objection|phrase\`, \`--limit <n>\` up to 200, \`--json\`), each with how many rows said it, their likes, and up to 3 real quotes. In \`--json\`, each quote's \`{kind, ref, quote}\` goes straight into a creative's \`researchSources\`, and a group itself can be cited as its \`ref\`, \`voice_clusters:<id>\`. The same rows are in the \`voice_clusters\` and \`voice_insights\` tables.
194
+ Every hour an AI reads what is new in the workspace's Facebook and Instagram comments, Google reviews, Reddit posts and comments, recorded calls and meetings (Fathom, Zoom, CallRail) and chats (GoHighLevel texts and chat, Closebot), pulls out openers (lines that would open an ad), objections (why people hesitate), pains (problems they live with), desires (results they want) and phrases (exact wording worth copying into an ad), and groups the ones that say the same thing. From calls and chats only the customer's words count, and names, emails, phone numbers and handles are taken out first. \`${cli} voice\` lists the groups, most common first (\`--kind opener|objection|pain|desire|phrase\`, \`--limit <n>\` up to 200, \`--json\`), each with how many sources said it (a conversation counts once), their likes, and up to 3 real quotes. \`${cli} voice --since <YYYY-MM-DD> --sort useful\` lists single lines said since that day instead, the most usable in an ad first: specific, first-person, recent and often said; \`--json\` adds each line's link. In \`--json\`, each quote's \`kind\` (a line's \`source\`) and \`ref\` go straight into a creative's \`researchSources\`, and a group itself can be cited as its \`ref\`, \`voice_clusters:<id>\`. The same rows are in the \`voice_clusters\` and \`voice_insights\` tables.
194
195
 
195
196
  ## The weekly story
196
197
 
package/dist/main.js CHANGED
@@ -15,6 +15,7 @@ import { SKILL_NAME_PATTERN } from "./skill-zip.js";
15
15
  import { installSkillFolder, operatorConnection, packSkill, parseSkillRef, readSkillFolder, unpackSkill, } from "./skills.js";
16
16
  const DEFAULT_WAIT_SECONDS = 300;
17
17
  const SERVER_WAIT_CAP = 50;
18
+ const VOICE_USAGE = "voice [--kind opener|objection|pain|desire|phrase] [--since <YYYY-MM-DD>] [--sort common|useful] [--limit <n>] [--json]";
18
19
  const commands = {
19
20
  login: {
20
21
  usage: `login (paste the key when asked, pipe it on stdin, or set ${envPrefix}_API_KEY)`,
@@ -1125,21 +1126,30 @@ const commands = {
1125
1126
  },
1126
1127
  },
1127
1128
  voice: {
1128
- usage: "voice [--kind opener|objection|phrase] [--limit <n>] [--json] what customers keep saying, most common first",
1129
+ usage: `${VOICE_USAGE} what customers keep saying, most common first, or with --sort useful the most usable single lines`,
1129
1130
  async run(args, options) {
1130
1131
  if (args.length > 0)
1131
- throw new CliError("Usage: voice [--kind opener|objection|phrase] [--limit <n>] [--json]", exitCodes.usage);
1132
+ throw new CliError(`Usage: ${VOICE_USAGE}`, exitCodes.usage);
1132
1133
  const query = new URLSearchParams();
1133
1134
  if (options.kind)
1134
1135
  query.set("kind", options.kind);
1136
+ if (options.since)
1137
+ query.set("since", options.since);
1138
+ if (options.sort)
1139
+ query.set("sort", options.sort);
1135
1140
  if (options.limit)
1136
1141
  query.set("limit", options.limit);
1137
- const { clusters } = await api(await resolveConnection(options.apiUrl), `${VOICE_PATH}${query.size ? `?${query}` : ""}`);
1142
+ const read = await api(await resolveConnection(options.apiUrl), `${VOICE_PATH}${query.size ? `?${query}` : ""}`);
1138
1143
  if (options.format === "json")
1139
- return printJson({ clusters });
1140
- if (clusters.length === 0)
1144
+ return printJson(read);
1145
+ if ("lines" in read) {
1146
+ if (read.lines.length === 0)
1147
+ return console.log("Nothing said in that time yet.");
1148
+ return console.log(renderTable(VOICE_LINE_COLUMNS, read.lines.map(voiceLineRow)));
1149
+ }
1150
+ if (read.clusters.length === 0)
1141
1151
  return console.log("Nothing grouped yet.");
1142
- console.log(renderTable(VOICE_COLUMNS, clusters.map(voiceRow)));
1152
+ console.log(renderTable(VOICE_COLUMNS, read.clusters.map(voiceRow)));
1143
1153
  },
1144
1154
  },
1145
1155
  "ads tags": {
@@ -1798,6 +1808,7 @@ const VOICE_SUGGESTIONS_PATH = "/api/v1/voice-lists/suggestions";
1798
1808
  const FACT_COLUMNS = ["id", "kind", "subject", "key", "status", "summary"];
1799
1809
  const VOICE_PATH = "/api/v1/voice";
1800
1810
  const VOICE_COLUMNS = ["ref", "kind", "said by", "likes", "label", "top quote"];
1811
+ const VOICE_LINE_COLUMNS = ["date", "kind", "source", "quote", "ref"];
1801
1812
  function knowledgeBlock(r, i) {
1802
1813
  const lines = [`${i + 1}. ${r.title}`, ` ${r.kind}, ${r.evidence.replaceAll("_", " ")} (${r.source}${r.syncedAt ? `, copied in ${r.syncedAt.slice(0, 10)}` : ""})`];
1803
1814
  if (r.url)
@@ -1818,6 +1829,9 @@ function contentTagRow(t) {
1818
1829
  function voiceRow(c) {
1819
1830
  return { ref: c.ref, kind: c.kind, "said by": c.supportCount, likes: c.likeSum, label: excerpt(c.label, 60), "top quote": c.evidence[0] ? excerpt(c.evidence[0].quote, 60) : null };
1820
1831
  }
1832
+ function voiceLineRow(l) {
1833
+ return { date: l.date.slice(0, 10), kind: l.kind, source: l.source, quote: excerpt(l.quote, 80), ref: l.ref };
1834
+ }
1821
1835
  function factRow(f) {
1822
1836
  return { id: f.id, kind: f.kind, subject: `${f.subjectType}:${f.subjectRef}`, key: f.key, status: f.status, summary: excerpt(f.summary, 80) };
1823
1837
  }
@@ -1934,7 +1948,7 @@ Options:
1934
1948
  --order desc|asc content search's sort direction (default: desc, best or newest first)
1935
1949
  --limit <n> how many results content search, creatives list, campaigns, voice, ads tags, content tags and content to-check
1936
1950
  return, or how many ads ads autotag tags and how many items content autotag tags
1937
- --library <slug> with knowledge search, only this library: creative-cartel or exodus
1951
+ --library <slug> with knowledge search, only this library: creative-cartel, exodus or meta-policy
1938
1952
  --ad <id> with ads tags, only this Meta ad's tags; with ads autotag, only this ad
1939
1953
  --kind email|text|page with content tags and content autotag, only that kind (content autotag takes a comma list)
1940
1954
  --max-seconds <n> stop a runner's run after this many seconds, if that comes before its deadline
@@ -1987,6 +2001,7 @@ function parseOptions(argv) {
1987
2001
  force: { type: "boolean", default: false },
1988
2002
  yes: { type: "boolean", default: false },
1989
2003
  from: { type: "string" },
2004
+ since: { type: "string" },
1990
2005
  to: { type: "string" },
1991
2006
  grain: { type: "string" },
1992
2007
  channel: { type: "string" },
@@ -2082,6 +2097,7 @@ function parseOptions(argv) {
2082
2097
  force: values.force,
2083
2098
  yes: values.yes,
2084
2099
  from: values.from,
2100
+ since: values.since,
2085
2101
  to: values.to,
2086
2102
  grain: values.grain,
2087
2103
  channel: values.channel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrangled/cli",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "description": "Command-line access to your marketing data warehouse, for people and coding agents",
5
5
  "type": "module",
6
6
  "bin": {