@wrangled/cli 0.3.21 → 0.3.22
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 +12 -3
- package/dist/main.js +91 -7
- package/dist/skill-zip.js +8 -2
- package/package.json +1 -1
package/dist/guide.js
CHANGED
|
@@ -35,7 +35,7 @@ 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\`), 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. 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\`) 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\`.
|
|
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
41
|
- \`knowledge_not_synced\` means the library has not been copied into this server yet, so nothing was searched; the weekly sync fills it.
|
|
@@ -87,7 +87,7 @@ A webhook address lets a tool such as a form or webinar platform push events int
|
|
|
87
87
|
1. \`${cli} tools list\` shows each tool id (\`provider:tool\`, such as \`kie:nano-banana\`), whether it runs sync or async, and what it makes. \`--format json\` adds each tool's description, cost, and input JSON Schema.
|
|
88
88
|
2. \`${cli} tools run kie:nano-banana --input '{"prompt": "...", "aspectRatio": "1:1"}' --idempotency-key <key>\` starts a run and waits for it (up to \`--wait\` seconds, default 300). Tools cost money. Always pass an idempotency key, and reuse it when you retry, so a retry returns the same run instead of paying twice.
|
|
89
89
|
3. A run that is still going prints its id. Check it with \`${cli} tools get <run-id>\`. Image results list an \`asset\` link. Save the file with \`${cli} creatives download <asset-link>\`, or fetch the link with your API key as a bearer token; it redirects to a copy that works for 10 minutes.
|
|
90
|
-
4. Record a succeeded run in the ledger with its lineage: \`${cli} creatives create --run <run-id> --lineage '{"persona": "...", "painPoint": "...", "desiredOutcome": "...", "angle": "...", "sourceAd": {"metaAdId": "..."}}'\`. Each output gets a SKU such as \`DLC-000123\`. The SKU goes in the Meta ad name.
|
|
90
|
+
4. Record a succeeded run in the ledger with its lineage: \`${cli} creatives create --run <run-id> --lineage '{"persona": "...", "painPoint": "...", "desiredOutcome": "...", "angle": "...", "sourceAd": {"metaAdId": "..."}}'\`. Each output gets a SKU such as \`DLC-000123\`. The SKU goes in the Meta ad name. Lineage is free-text notes; give the ad's marketing tags with \`--tags '<json>'\` or \`--tags @tags.json\`, the same fields as ad tags (see Ad tags), such as \`{"offer_key": "course", "landing_funnel_key": "webinar_to_course", "landing_step_key": "registration_page", "persona": "near_retirement", "angle": "story", "format": "image"}\`. Every output of the run gets them. Values come from \`${cli} grammar\`; persona and concept from this client's lists, and offer, funnel and step from its confirmed map (\`${cli} funnels show\`), where the step must be in that funnel and \`reply\` or \`no_link\` take no step. A value off them is refused with the ones to use. Recording the same run again returns its creatives as they are, tags included; \`${cli} creatives tag <sku> --file tags.json\` is how to change a creative's tags. Your tags read proposed until an Owner or Admin confirms them on the creative's page in Content; after that only a person changes them.
|
|
91
91
|
5. \`${cli} creatives list\` and \`${cli} creatives show <sku>\` read the ledger. \`${cli} creatives download <sku>\` saves a creative's image or video as \`<sku>.<ext>\` in the current folder; \`--out <path>\` names the file or a folder, and \`--open\` opens it. It also takes an asset path or link, such as a \`meta_ad_creatives\` \`image_path\`. \`--format json\` prints \`{ path, bytes, contentType }\`. Copy has no file; its text is in \`${cli} creatives show\`. For performance, query the \`creatives\` table or the \`creative_performance_daily\` view with \`${cli} warehouse query\`.
|
|
92
92
|
|
|
93
93
|
## Landing pages
|
|
@@ -178,6 +178,12 @@ To have the server tag ads for you, run \`${cli} ads autotag\` (\`--limit <n>\`,
|
|
|
178
178
|
|
|
179
179
|
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\`.
|
|
180
180
|
|
|
181
|
+
## Content tags
|
|
182
|
+
|
|
183
|
+
Emails, text messages and landing pages in the content library get the same grammar's tags, by content item id. \`${cli} content tags [<item-id>]\` lists them (\`--kind email|text|page\`, \`--status proposed|confirmed|rejected|replaced\`, \`--limit <n>\`, \`--json\`); by default the live ones. Each row names the version of the item's words it describes (\`contentHash\`). Only the parts that fit the kind are filled: an email's or text's job (invite, reminder, last chance and so on), no job for a page, and no secondary hook for a text, whose format (sms or mms) comes from whether it carries a picture.
|
|
184
|
+
|
|
185
|
+
To have the server tag them, run \`${cli} content autotag\` (\`--limit <n>\`, 1 to 100, default 25; \`--kind email,text\` to narrow; item ids as arguments for those items; \`--json\`). It takes the items whose current words have no tags yet, the ones that brought in the most sales first, then the most clicks, then the newest, so a repeat run spends nothing on what it already tagged. Items that read the same, such as one email sent to several lists, are asked about once and share the tags. An email whose body was never stored is asked only whether it is marketing, its hook and its job. When an item's words change, its next run replaces its tags, even confirmed ones, since a person confirmed the old words. \`${cli} content to-check\` lists the emails and texts whose tags wait for a person, most sales first, then most clicks; the same rows are in the \`content_tags\` table.
|
|
186
|
+
|
|
181
187
|
## Ad words
|
|
182
188
|
|
|
183
189
|
Tag ads, emails, texts and pages only with the words \`${cli} grammar\` lists (\`--format json\` for the same lists as data): each part's question and allowed values. Persona (who it talks to) and Concept (the one idea it sells) are this client's own lists; the other parts are shared by every client. A persona or concept marked not yet confirmed may be used. A retired one is never used for new tags; an old tag with it counts as the value it was merged into. To suggest new personas and concepts, run \`${cli} grammar propose --file values.json\` with \`{"values": [{"part": "persona", "key": "age_50s_retirement", "label": "In their 50s, worried about retirement", "definition": "People in their 50s who fear their savings will not last.", "examples": ["<SKU>"]}]}\`. You only suggest: a value already on the list is left as it is, and a person confirms, renames, merges or retires values in the app. The \`grammar\` playbook (\`${cli} skill install grammar\` installs it as the \`${GRAMMAR_SKILL_NAME}\` skill; \`${cli} docs grammar\` prints it) walks an agent from the client's ads to a first draft.
|
|
@@ -259,7 +265,8 @@ ${cli} tools get <run-id> show a tool run, waiting for it to finish (-
|
|
|
259
265
|
${cli} creatives list list creatives (--status, --kind, --limit)
|
|
260
266
|
${cli} creatives show <sku> show one creative with its spec and lineage
|
|
261
267
|
${cli} creatives download <sku | asset-path | asset-link> [--out <path>] [--open] save a creative's image or video
|
|
262
|
-
${cli} creatives create --run <run-id> [--lineage '<json>' | @file.json] record a run's output with a new SKU
|
|
268
|
+
${cli} creatives create --run <run-id> [--lineage '<json>' | @file.json] [--tags '<json>' | @file.json] record a run's output with a new SKU
|
|
269
|
+
${cli} creatives tag <sku> --file <tags.json> replace a creative's marketing tags, proposed until a person confirms them
|
|
263
270
|
${cli} content create --kind <kind> [--body ...] [--file <path>] [--note <why>] [--plan <plan-id>] save made content with a new SKU
|
|
264
271
|
${cli} content search [--text <words>] [--mode word|meaning] [--kind <kind>] [--platform <p>] [--origin <o>] [--from <date>] [--to <date>] [--sort <column>] [--order asc|desc] [--limit <n>] find content with its numbers
|
|
265
272
|
${cli} content pull <dir> [--kind <kind>] [--platform <p>] [--origin <o>] [--media] [--full] copy the library to a folder of Markdown files; rerun for changes
|
|
@@ -290,6 +297,8 @@ ${cli} facts [--kind <kind>] [--subject <type>[:<ref>]] [--status <status>] [--j
|
|
|
290
297
|
${cli} facts propose --file <fact.json> suggest a fact for a person to confirm
|
|
291
298
|
${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
|
|
292
299
|
${cli} ads autotag [--limit <n>] [--ad <id>] have the server propose tags for the biggest spenders not yet tagged
|
|
300
|
+
${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
|
|
301
|
+
${cli} content autotag [<item-id> ...] [--kind email|text|page] [--limit <n>] have the server propose tags for the emails, texts and pages worth most not yet tagged
|
|
293
302
|
${cli} grammar [--format json] every list ads are tagged from, this client's personas and concepts included
|
|
294
303
|
${cli} grammar propose --file <values.json> suggest personas and concepts for a person to confirm
|
|
295
304
|
${cli} voice [--kind opener|objection|phrase] [--limit <n>] [--json] what customers keep saying, grouped and counted, with real quotes
|
package/dist/main.js
CHANGED
|
@@ -500,7 +500,7 @@ const commands = {
|
|
|
500
500
|
tool: creative.spec.tool,
|
|
501
501
|
prompt: creative.spec.input?.prompt,
|
|
502
502
|
};
|
|
503
|
-
const shown = ["sku", "kind", "status", "tool", "prompt", "lineage", "assetUrl", "copyText", "metaAdId", "toolRunId", "createdAt"];
|
|
503
|
+
const shown = ["sku", "kind", "status", "tool", "prompt", "lineage", "tags", "tagsStatus", "tagsConfirmedBy", "assetUrl", "copyText", "metaAdId", "toolRunId", "createdAt"];
|
|
504
504
|
console.log(renderTable(["field", "value"], shown.filter((f) => fields[f] != null).map((f) => ({ field: f, value: fields[f] }))));
|
|
505
505
|
},
|
|
506
506
|
},
|
|
@@ -522,18 +522,33 @@ const commands = {
|
|
|
522
522
|
},
|
|
523
523
|
},
|
|
524
524
|
"creatives create": {
|
|
525
|
-
usage: "creatives create --run <tool-run-id> [--lineage '<json>' | @file.json]",
|
|
525
|
+
usage: "creatives create --run <tool-run-id> [--lineage '<json>' | @file.json] [--tags '<json>' | @file.json]",
|
|
526
526
|
async run(args, options) {
|
|
527
|
-
if (args.length > 0 || !options.run)
|
|
528
|
-
throw new CliError("Usage: creatives create --run <tool-run-id> [--lineage '<json>' | @file.json]", exitCodes.usage);
|
|
527
|
+
if (args.length > 0 || !options.run) {
|
|
528
|
+
throw new CliError("Usage: creatives create --run <tool-run-id> [--lineage '<json>' | @file.json] [--tags '<json>' | @file.json]", exitCodes.usage);
|
|
529
|
+
}
|
|
529
530
|
const lineage = options.lineage === undefined ? undefined : await parseJson(options.lineage, "--lineage");
|
|
531
|
+
const tags = options.tags === undefined ? undefined : await parseJson(options.tags, "--tags");
|
|
530
532
|
const { creatives } = await api(await resolveConnection(options.apiUrl), "/api/v1/creatives", {
|
|
531
533
|
runId: options.run,
|
|
532
534
|
...(lineage === undefined ? {} : { lineage }),
|
|
535
|
+
...(tags === undefined ? {} : { tags }),
|
|
533
536
|
});
|
|
534
537
|
if (options.format === "json")
|
|
535
538
|
return printJson({ creatives });
|
|
536
|
-
console.log(renderTable(["sku", "kind", "status", "assetUrl"], creatives));
|
|
539
|
+
console.log(renderTable(["sku", "kind", "status", "tagsStatus", "assetUrl"], creatives));
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
"creatives tag": {
|
|
543
|
+
usage: "creatives tag <sku> --file <tags.json> | < tags.json replace a creative's marketing tags; a person confirms them in the app",
|
|
544
|
+
async run(args, options) {
|
|
545
|
+
if (args.length !== 1)
|
|
546
|
+
throw new CliError("Usage: creatives tag <sku> --file <tags.json> | < tags.json", exitCodes.usage);
|
|
547
|
+
const tags = await readToolInput(options);
|
|
548
|
+
const { creative } = await api(await resolveConnection(options.apiUrl), `/api/v1/creatives/${encodeURIComponent(args[0])}/tags`, { tags }, "PATCH");
|
|
549
|
+
if (options.format === "json")
|
|
550
|
+
return printJson({ creative });
|
|
551
|
+
console.log(`Tagged ${creative.sku}. The tags read ${creative.tagsStatus} until a person confirms them in the app.`);
|
|
537
552
|
},
|
|
538
553
|
},
|
|
539
554
|
"content create": {
|
|
@@ -1186,6 +1201,63 @@ const commands = {
|
|
|
1186
1201
|
console.log("Stopped early: the run's tool spending cap was reached.");
|
|
1187
1202
|
},
|
|
1188
1203
|
},
|
|
1204
|
+
"content tags": {
|
|
1205
|
+
usage: "content tags [<item-id>] [--kind email|text|page] [--status <status>] [--limit <n>] [--json] the marketing tags of emails, texts and landing pages",
|
|
1206
|
+
async run(args, options) {
|
|
1207
|
+
if (args.length > 1)
|
|
1208
|
+
throw new CliError("Usage: content tags [<item-id>] [--kind email|text|page] [--status <status>] [--limit <n>] [--json]", exitCodes.usage);
|
|
1209
|
+
const filters = { item: args[0], kind: options.kind, status: options.status, limit: options.limit };
|
|
1210
|
+
const query = new URLSearchParams(Object.entries(filters).filter((entry) => entry[1] !== undefined));
|
|
1211
|
+
const { tags } = await api(await resolveConnection(options.apiUrl), `${CONTENT_TAGS_PATH}${query.size ? `?${query}` : ""}`);
|
|
1212
|
+
if (options.format === "json")
|
|
1213
|
+
return printJson({ tags });
|
|
1214
|
+
if (tags.length === 0)
|
|
1215
|
+
return console.log("No content tags.");
|
|
1216
|
+
console.log(renderTable(CONTENT_TAG_COLUMNS, tags.map(contentTagRow)));
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
"content to-check": {
|
|
1220
|
+
usage: "content to-check [--limit <n>] [--json] the emails and texts whose tags wait for a person, most sales first, then most clicks",
|
|
1221
|
+
async run(args, options) {
|
|
1222
|
+
if (args.length > 0)
|
|
1223
|
+
throw new CliError("Usage: content to-check [--limit <n>] [--json]", exitCodes.usage);
|
|
1224
|
+
const query = new URLSearchParams({ to_check: "1", ...(options.limit ? { limit: options.limit } : {}) });
|
|
1225
|
+
const { tags } = await api(await resolveConnection(options.apiUrl), `${CONTENT_TAGS_PATH}?${query}`);
|
|
1226
|
+
if (options.format === "json")
|
|
1227
|
+
return printJson({ tags });
|
|
1228
|
+
if (tags.length === 0)
|
|
1229
|
+
return console.log("No email or text tags wait for a person.");
|
|
1230
|
+
console.log(renderTable(["item", "kind", "revenue", "clicks", ...CONTENT_TAG_COLUMNS.slice(4)], tags.map((t) => ({ ...contentTagRow(t), revenue: t.revenue, clicks: t.clicks }))));
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
"content autotag": {
|
|
1234
|
+
usage: "content autotag [<item-id> ...] [--kind email|text|page] [--limit <n>] [--json] propose marketing tags for the emails, texts and landing pages worth most that have none",
|
|
1235
|
+
async run(args, options) {
|
|
1236
|
+
const body = {
|
|
1237
|
+
...(args.length > 0 ? { item_ids: args } : {}),
|
|
1238
|
+
...(options.kind ? { kinds: options.kind.split(",") } : {}),
|
|
1239
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
1240
|
+
};
|
|
1241
|
+
const result = await api(await resolveConnection(options.apiUrl), CONTENT_AUTOTAG_PATH, body);
|
|
1242
|
+
if (options.format === "json")
|
|
1243
|
+
return printJson(result);
|
|
1244
|
+
const plural = (n) => `${n} ${n === 1 ? "item" : "items"}`;
|
|
1245
|
+
console.log(`Proposed tags for ${plural(result.proposed.length)}, asking about ${result.groups} ${result.groups === 1 ? "set of words" : "sets of words"} for $${result.costUsd.toFixed(4)}. A person confirms or rejects them in the app.`);
|
|
1246
|
+
if (result.proposed.length > 0)
|
|
1247
|
+
console.log(renderTable(["item", "id", "confidence"], result.proposed.map((p) => ({ item: p.itemId, id: p.id, confidence: p.confidence }))));
|
|
1248
|
+
if (result.kept.length > 0)
|
|
1249
|
+
console.log(`Kept as they are, since a person confirmed their tags: ${result.kept.join(", ")}.`);
|
|
1250
|
+
for (const reason of ["not_marketing", "gate_unclear", "no_content", "not_found", "changed"]) {
|
|
1251
|
+
const ids = result.skipped.filter((s) => s.reason === reason).map((s) => s.itemId);
|
|
1252
|
+
if (ids.length > 0)
|
|
1253
|
+
console.log(`${CONTENT_SKIP_REASONS[reason]}: ${ids.join(", ")}.`);
|
|
1254
|
+
}
|
|
1255
|
+
for (const failure of result.failed)
|
|
1256
|
+
console.log(`Failed for ${failure.itemIds.join(", ")}: ${failure.error}`);
|
|
1257
|
+
if (result.stopped === "tool_cap")
|
|
1258
|
+
console.log("Stopped early: the run's tool spending cap was reached.");
|
|
1259
|
+
},
|
|
1260
|
+
},
|
|
1189
1261
|
"facts show": {
|
|
1190
1262
|
usage: "facts show <id> one fact with its value and evidence",
|
|
1191
1263
|
async run(args, options) {
|
|
@@ -1716,6 +1788,10 @@ const AD_TAGS_PATH = "/api/v1/ad-tags";
|
|
|
1716
1788
|
const AD_AUTOTAG_PATH = "/api/v1/ad-tags/autotag";
|
|
1717
1789
|
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" };
|
|
1718
1790
|
const AD_TAG_COLUMNS = ["id", "ad", "status", "confidence", "offer", "persona", "angle", "hook", "format", "author"];
|
|
1791
|
+
const CONTENT_TAGS_PATH = "/api/v1/content-tags";
|
|
1792
|
+
const CONTENT_SKIP_REASONS = { ...SKIP_REASONS, not_found: "No such email, text or landing page", changed: "Its words changed while it was being tagged, so nothing was saved; the next run tags them" };
|
|
1793
|
+
const CONTENT_AUTOTAG_PATH = "/api/v1/content-tags/autotag";
|
|
1794
|
+
const CONTENT_TAG_COLUMNS = ["id", "item", "kind", "status", "confidence", "hook", "job", "offer", "angle", "format"];
|
|
1719
1795
|
const GRAMMAR_PATH = "/api/v1/grammar";
|
|
1720
1796
|
const VOICE_LISTS_PATH = "/api/v1/voice-lists";
|
|
1721
1797
|
const VOICE_SUGGESTIONS_PATH = "/api/v1/voice-lists/suggestions";
|
|
@@ -1735,6 +1811,10 @@ function adTagRow(t) {
|
|
|
1735
1811
|
const { offer_key, persona, angle, hook_kind, hook_spot, format, style } = t.tags;
|
|
1736
1812
|
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 };
|
|
1737
1813
|
}
|
|
1814
|
+
function contentTagRow(t) {
|
|
1815
|
+
const { hook_kind, hook_spot, job, offer_key, angle, format } = t.tags;
|
|
1816
|
+
return { id: t.id, item: t.itemId, kind: t.kind, status: t.status, confidence: t.confidence, hook: [hook_kind, hook_spot].filter(Boolean).join(" in ") || null, job, offer: offer_key, angle, format };
|
|
1817
|
+
}
|
|
1738
1818
|
function voiceRow(c) {
|
|
1739
1819
|
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 };
|
|
1740
1820
|
}
|
|
@@ -1852,9 +1932,11 @@ Options:
|
|
|
1852
1932
|
--from, --to <n> with content diff, the two versions to compare (default: the one before the current one, and the current one)
|
|
1853
1933
|
--sort <column> how content search sorts: recent (default) or a content_scorecard number, such as click_rate or roas
|
|
1854
1934
|
--order desc|asc content search's sort direction (default: desc, best or newest first)
|
|
1855
|
-
--limit <n> how many results content search, creatives list, campaigns, voice
|
|
1856
|
-
|
|
1935
|
+
--limit <n> how many results content search, creatives list, campaigns, voice, ads tags, content tags and content to-check
|
|
1936
|
+
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
|
|
1857
1938
|
--ad <id> with ads tags, only this Meta ad's tags; with ads autotag, only this ad
|
|
1939
|
+
--kind email|text|page with content tags and content autotag, only that kind (content autotag takes a comma list)
|
|
1858
1940
|
--max-seconds <n> stop a runner's run after this many seconds, if that comes before its deadline
|
|
1859
1941
|
--workspace <slug> for worker promote and worker drop: the workspace whose hand they change
|
|
1860
1942
|
-h, --help show this help
|
|
@@ -1888,6 +1970,7 @@ function parseOptions(argv) {
|
|
|
1888
1970
|
limit: { type: "string" },
|
|
1889
1971
|
run: { type: "string" },
|
|
1890
1972
|
lineage: { type: "string" },
|
|
1973
|
+
tags: { type: "string" },
|
|
1891
1974
|
sources: { type: "string" },
|
|
1892
1975
|
codex: { type: "boolean", default: false },
|
|
1893
1976
|
agent: { type: "string" },
|
|
@@ -1981,6 +2064,7 @@ function parseOptions(argv) {
|
|
|
1981
2064
|
limit: values.limit,
|
|
1982
2065
|
run: values.run,
|
|
1983
2066
|
lineage: values.lineage,
|
|
2067
|
+
tags: values.tags,
|
|
1984
2068
|
sources: values.sources,
|
|
1985
2069
|
codex: values.codex,
|
|
1986
2070
|
agent: values.agent ?? (process.env[AGENT_ENV] || "meta_ads"),
|
package/dist/skill-zip.js
CHANGED
|
@@ -41,10 +41,16 @@ export function unsafeFileName(name) {
|
|
|
41
41
|
return `empty or . segment in ${name}`;
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The time stamped on every zipped file: the earliest a zip can hold. The library recognises a skill version by its
|
|
46
|
+
* zip's sha256, so the same files must zip to the same bytes on every run. fflate reads the date in local time, and
|
|
47
|
+
* this one is built in local time, so it encodes the same in every time zone.
|
|
48
|
+
*/
|
|
49
|
+
const ZIP_MTIME = new Date(1980, 0, 1);
|
|
50
|
+
/** Zips the files; each name is a `/`-separated path inside the skill folder. The same files always give the same bytes. */
|
|
45
51
|
export function zipSkillFiles(files) {
|
|
46
52
|
const unix = (mode) => ({ os: 3, attrs: ((0o100000 | mode) << 16) >>> 0 });
|
|
47
|
-
return zipSync(Object.fromEntries(files.map((f) => [f.name, [f.data, unix(f.executable ? 0o755 : 0o644)]])), { level: 9 });
|
|
53
|
+
return zipSync(Object.fromEntries(files.map((f) => [f.name, [f.data, unix(f.executable ? 0o755 : 0o644)]])), { level: 9, mtime: ZIP_MTIME });
|
|
48
54
|
}
|
|
49
55
|
/** Reads and checks a skill's zip without trusting any size or name it declares. Throws SkillZipError naming the problem. */
|
|
50
56
|
export function readSkillZip(zip, skillName) {
|