@wrangled/cli 0.3.24 → 0.3.25
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/api.js +5 -0
- package/dist/guide.js +4 -1
- package/dist/main.js +64 -10
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -16,6 +16,11 @@ export class UnreadableCredentialsError extends CliError {
|
|
|
16
16
|
super(`The saved credentials at ${path} are unreadable. Run \`${brand.slug} login\` again.`, exitCodes.auth);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The grammar fields library entries are tagged with, and the CLI flag for each; the server's list is entryTagFields
|
|
21
|
+
* in src/lib/knowledge/tags.ts. format's flag is --ad-format, because --format picks the output.
|
|
22
|
+
*/
|
|
23
|
+
export const entryTagFlags = { angle: "angle", hook_kind: "hook-kind", format: "ad-format", awareness: "awareness" };
|
|
19
24
|
/** Copies of the server's IMPORT_LIMITS and import name rule; src/lib/cli-consistency.test.ts keeps them in step. */
|
|
20
25
|
export const IMPORT_LIMITS = { bytes: 50 * 1024 * 1024, rows: 1_000_000, columns: 500 };
|
|
21
26
|
export const IMPORT_NAME_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
package/dist/guide.js
CHANGED
|
@@ -44,6 +44,8 @@ For what has worked for others, such as "how do members test new hooks" or "what
|
|
|
44
44
|
- 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.
|
|
45
45
|
- \`--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.
|
|
46
46
|
- \`knowledge_not_synced\` means the library has not been copied into this server yet, so nothing was searched; the weekly sync fills it.
|
|
47
|
+
- Library entries carry the marketing grammar's tags: \`--angle\`, \`--hook-kind\`, \`--ad-format\` and \`--awareness\` (the ad tag fields angle, hook_kind, format and awareness, with the same values). Give any of them to keep to entries tagged that way; the question is then optional, and without one the entries come surest tags first. With a tag filter the answer also lists this workspace's own Meta ads whose live tags hold the same values, with their lifetime spend and results, biggest spend first (at most 20): what the library says next to how it went for this client.
|
|
48
|
+
- Each result shows its entry id and tags. Tag an entry you read with \`${cli} knowledge tag <entry-id> --hook-kind big_win --confidence medium\` (several flags at once is fine), take a wrong tag off with \`--remove\`, or tag a batch with \`${cli} knowledge tag --file tags.json\`, where the file is \`{"tags": [{"entry_id", "field", "value", "confidence"}]}\` (up to 500; \`"remove": true\` in place of confidence takes one off). Values off the grammar's list are refused.
|
|
47
49
|
|
|
48
50
|
## Query workflow: schema first
|
|
49
51
|
|
|
@@ -244,7 +246,8 @@ A scheduled hand's run acts as its skill's agent, the way the Facebook ads agent
|
|
|
244
246
|
${cli} login validate a key (prompted, piped, or ${envPrefix}_API_KEY) and save it
|
|
245
247
|
${cli} logout delete saved credentials
|
|
246
248
|
${cli} whoami show the current workspace and library access
|
|
247
|
-
${cli} knowledge search "<question>" [--limit <n>] [--kind <kind>] short, cited snippets from the
|
|
249
|
+
${cli} knowledge search "<question>" [--limit <n>] [--kind <kind>] [--hook-kind <v> ...] short, cited snippets from the know-how libraries (keys with library access)
|
|
250
|
+
${cli} knowledge tag <entry-id> --hook-kind <v> --confidence <c> tag a library entry with the marketing grammar (or --file tags.json)
|
|
248
251
|
${cli} guide what this workspace's data holds, how fresh it is, and what is missing: read it first
|
|
249
252
|
${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
|
|
250
253
|
${cli} winners [--week <date>] [--top <n>] which Facebook ads won and flopped this week and over 30 days, judged on first-touch sales
|
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mkdir, readFile, stat } from "node:fs/promises";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
|
-
import { api, ApiError, apiDownload, apiText, apiUpload, CliError, exitCodes, getSigned, IMPORT_LIMITS, IMPORT_NAME_PATTERN, putSigned, readCredentials, removeCredentials, resolveApiUrl, resolveConnection, UnreadableCredentialsError, writeCredentials, streamSigned, } from "./api.js";
|
|
7
|
+
import { api, ApiError, apiDownload, apiText, apiUpload, CliError, exitCodes, getSigned, IMPORT_LIMITS, IMPORT_NAME_PATTERN, putSigned, readCredentials, removeCredentials, resolveApiUrl, resolveConnection, UnreadableCredentialsError, writeCredentials, entryTagFlags, streamSigned, } from "./api.js";
|
|
8
8
|
import { brand, envPrefix } from "./brand.js";
|
|
9
9
|
import { firstSymlink, inRunFolder, writeFileAtomic } from "./files.js";
|
|
10
10
|
import { guide, skill } from "./guide.js";
|
|
@@ -57,23 +57,55 @@ const commands = {
|
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
"knowledge search": {
|
|
60
|
-
usage: 'knowledge search "<question>" [--limit <n>] [--kind message|transcript|document|note] [--library <slug>]',
|
|
60
|
+
usage: 'knowledge search ["<question>"] [--limit <n>] [--kind message|transcript|document|note] [--library <slug>] [--angle <v>] [--hook-kind <v>] [--ad-format <v>] [--awareness <v>]',
|
|
61
61
|
async run(args, options) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const filters = Object.entries(options.entryTags);
|
|
63
|
+
if (args.length > 1 || (args.length === 0 && filters.length === 0)) {
|
|
64
|
+
throw new CliError('Usage: knowledge search "<question>" [--limit <n>] [--kind <kind>] [--library <slug>] [--angle <v>] [--hook-kind <v>] [--ad-format <v>] [--awareness <v>]; put the question in quotes, or give a tag filter', exitCodes.usage);
|
|
65
|
+
}
|
|
66
|
+
const query = new URLSearchParams(args[0] === undefined ? {} : { q: args[0] });
|
|
65
67
|
if (options.limit !== undefined)
|
|
66
68
|
query.set("limit", options.limit);
|
|
67
69
|
if (options.kind !== undefined)
|
|
68
70
|
query.set("kind", options.kind);
|
|
69
71
|
if (options.library !== undefined)
|
|
70
72
|
query.set("library", options.library);
|
|
71
|
-
const
|
|
73
|
+
for (const [field, value] of filters)
|
|
74
|
+
query.set(field, value);
|
|
75
|
+
const answer = await api(await resolveConnection(options.apiUrl), `/api/v1/knowledge/search?${query}`);
|
|
72
76
|
if (options.format === "json")
|
|
73
|
-
return printJson(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
return printJson(answer);
|
|
78
|
+
console.log(answer.results.length === 0 ? "Nothing in the library matched." : answer.results.map(knowledgeBlock).join("\n\n"));
|
|
79
|
+
if (answer.yourAds === undefined)
|
|
80
|
+
return;
|
|
81
|
+
const tagged = filters.map(([field, value]) => `${field} ${value}`).join(", ");
|
|
82
|
+
if (answer.yourAds.length === 0)
|
|
83
|
+
return console.log(`\nNone of your ads is tagged ${tagged}.`);
|
|
84
|
+
console.log(`\nYour ads tagged ${tagged}, biggest spend first:`);
|
|
85
|
+
console.log(renderTable(["ad", "name", "tags", "spend", "results"], answer.yourAds.map(yourAdRow)));
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
"knowledge tag": {
|
|
89
|
+
usage: "knowledge tag <entry-id> [--angle <v>] [--hook-kind <v>] [--ad-format <v>] [--awareness <v>] --confidence high|medium|low [--remove] | --file <tags.json> tag library entries with the marketing grammar",
|
|
90
|
+
async run(args, options) {
|
|
91
|
+
const usage = "Usage: knowledge tag <entry-id> [--angle <v>] [--hook-kind <v>] [--ad-format <v>] [--awareness <v>] --confidence high|medium|low [--remove], or knowledge tag --file <tags.json>";
|
|
92
|
+
const filters = Object.entries(options.entryTags);
|
|
93
|
+
if (args.length > 1 || (args.length === 1 && filters.length === 0))
|
|
94
|
+
throw new CliError(usage, exitCodes.usage);
|
|
95
|
+
const body = args.length === 1
|
|
96
|
+
? {
|
|
97
|
+
tags: filters.map(([field, value]) => ({
|
|
98
|
+
entry_id: args[0],
|
|
99
|
+
field,
|
|
100
|
+
value,
|
|
101
|
+
...(options.remove ? { remove: true } : { confidence: options.confidence }),
|
|
102
|
+
})),
|
|
103
|
+
}
|
|
104
|
+
: await readToolInput(options);
|
|
105
|
+
const result = await api(await resolveConnection(options.apiUrl), KNOWLEDGE_TAGS_PATH, body);
|
|
106
|
+
if (options.format === "json")
|
|
107
|
+
return printJson(result);
|
|
108
|
+
console.log(`Tagged ${result.tagged}, took off ${result.removed}.`);
|
|
77
109
|
},
|
|
78
110
|
},
|
|
79
111
|
"warehouse schema": {
|
|
@@ -1867,6 +1899,7 @@ function printReport(title, report, labels, numbers) {
|
|
|
1867
1899
|
const agentPath = (options) => `/api/v1/agents/${encodeURIComponent(options.agent)}`;
|
|
1868
1900
|
const FACTS_PATH = "/api/v1/facts";
|
|
1869
1901
|
const AD_TAGS_PATH = "/api/v1/ad-tags";
|
|
1902
|
+
const KNOWLEDGE_TAGS_PATH = "/api/v1/knowledge/tags";
|
|
1870
1903
|
const AD_AUTOTAG_PATH = "/api/v1/ad-tags/autotag";
|
|
1871
1904
|
const SKIP_REASONS = { not_marketing: "Not marketing, so only that answer was saved", gate_unclear: "Could not tell whether it is marketing, so only that was saved", no_content: "Nothing to read", not_found: "No such ad" };
|
|
1872
1905
|
const AD_TAG_COLUMNS = ["id", "ad", "status", "confidence", "offer", "persona", "angle", "hook", "format", "author"];
|
|
@@ -1888,8 +1921,16 @@ function knowledgeBlock(r, i) {
|
|
|
1888
1921
|
lines.push(` ${r.snippet}`);
|
|
1889
1922
|
if (r.caveat)
|
|
1890
1923
|
lines.push(` Caveat: ${r.caveat}`);
|
|
1924
|
+
if (r.entryId) {
|
|
1925
|
+
const tags = Object.entries(r.tags ?? {}).flatMap(([field, values]) => values.map((value) => `${field} ${value}`));
|
|
1926
|
+
lines.push(` Entry ${r.entryId}${tags.length > 0 ? `, tagged ${tags.join(", ")}` : ""}`);
|
|
1927
|
+
}
|
|
1891
1928
|
return lines.join("\n");
|
|
1892
1929
|
}
|
|
1930
|
+
function yourAdRow(a) {
|
|
1931
|
+
const results = a.results === null ? null : `${a.results} ${a.resultType ?? ""}`.trim();
|
|
1932
|
+
return { ad: a.adId, name: a.adName, tags: `${a.tagStatus}, ${a.tagConfidence}`, spend: a.spend === null ? null : `${a.spend.toFixed(2)} ${a.currency ?? ""}`.trim(), results };
|
|
1933
|
+
}
|
|
1893
1934
|
function adTagRow(t) {
|
|
1894
1935
|
const { offer_key, persona, angle, hook_kind, hook_spot, format, style } = t.tags;
|
|
1895
1936
|
return { id: t.id, ad: t.adId, status: t.status, confidence: t.confidence, offer: offer_key, persona, angle, hook: [hook_kind, hook_spot].filter(Boolean).join(" in ") || null, format: [format, style].filter(Boolean).join(", ") || null, author: t.author };
|
|
@@ -2022,6 +2063,10 @@ Options:
|
|
|
2022
2063
|
--limit <n> how many results content search, creatives list, campaigns, voice, ads tags, content tags and content to-check
|
|
2023
2064
|
return, or how many ads ads autotag tags and how many items content autotag tags
|
|
2024
2065
|
--library <slug> with knowledge search, only this library: creative-cartel, exodus or meta-policy
|
|
2066
|
+
--angle, --hook-kind, --ad-format, --awareness <value> with knowledge search, only library entries tagged that way, and
|
|
2067
|
+
your own ads tagged the same way with their results; with knowledge tag, the tags to put on an entry
|
|
2068
|
+
--confidence high|medium|low with knowledge tag, how sure you are of the tags
|
|
2069
|
+
--remove with knowledge tag, take the tags off instead
|
|
2025
2070
|
--ad <id> with ads tags, only this Meta ad's tags; with ads autotag, only this ad
|
|
2026
2071
|
--kind email|text|page with content tags and content autotag, only that kind (content autotag takes a comma list)
|
|
2027
2072
|
--max-seconds <n> stop a runner's run after this many seconds, if that comes before its deadline
|
|
@@ -2122,6 +2167,12 @@ function parseOptions(argv) {
|
|
|
2122
2167
|
step: { type: "string" },
|
|
2123
2168
|
workspace: { type: "string" },
|
|
2124
2169
|
"max-seconds": { type: "string" },
|
|
2170
|
+
angle: { type: "string" },
|
|
2171
|
+
"hook-kind": { type: "string" },
|
|
2172
|
+
"ad-format": { type: "string" },
|
|
2173
|
+
awareness: { type: "string" },
|
|
2174
|
+
confidence: { type: "string" },
|
|
2175
|
+
remove: { type: "boolean", default: false },
|
|
2125
2176
|
help: { type: "boolean", short: "h", default: false },
|
|
2126
2177
|
},
|
|
2127
2178
|
});
|
|
@@ -2219,6 +2270,9 @@ function parseOptions(argv) {
|
|
|
2219
2270
|
step: values.step,
|
|
2220
2271
|
workspace: values.workspace,
|
|
2221
2272
|
maxSeconds,
|
|
2273
|
+
entryTags: Object.fromEntries(Object.entries(entryTagFlags).flatMap(([field, flag]) => (values[flag] === undefined ? [] : [[field, values[flag]]]))),
|
|
2274
|
+
confidence: values.confidence,
|
|
2275
|
+
remove: values.remove,
|
|
2222
2276
|
},
|
|
2223
2277
|
};
|
|
2224
2278
|
}
|