@koda-sl/baker-cli 0.526.0 → 0.528.0
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/README.md +2 -0
- package/dist/{account-APMJVG2F.js → account-CQJFUII3.js} +3 -3
- package/dist/{account-APMJVG2F.js.map → account-CQJFUII3.js.map} +1 -1
- package/dist/{account-assets-TAUEBCY5.js → account-assets-IDVG4DGK.js} +12 -12
- package/dist/account-assets-IDVG4DGK.js.map +1 -0
- package/dist/{account-level-2MYDHOID.js → account-level-GK7M2MMQ.js} +14 -14
- package/dist/account-level-GK7M2MMQ.js.map +1 -0
- package/dist/{accounts-VIAQC3PA.js → accounts-KRW3JNCW.js} +2 -2
- package/dist/{activities-MJMAV3SI.js → activities-2RAJXP3J.js} +3 -3
- package/dist/{activities-MJMAV3SI.js.map → activities-2RAJXP3J.js.map} +1 -1
- package/dist/{ads-O4RL4JXD.js → ads-6ESCXPHK.js} +3 -3
- package/dist/{ads-NNKUR4HG.js → ads-JVBLNTBF.js} +20 -15
- package/dist/ads-JVBLNTBF.js.map +1 -0
- package/dist/{adsets-VO5C7PDM.js → adsets-FN5ZWSYF.js} +11 -11
- package/dist/adsets-FN5ZWSYF.js.map +1 -0
- package/dist/{asset-writes-PQNENVYY.js → asset-writes-SSI3CO2A.js} +4 -4
- package/dist/{audience-writes-QDDADCYE.js → audience-writes-G64754LR.js} +6 -6
- package/dist/audience-writes-G64754LR.js.map +1 -0
- package/dist/{audiences-TQ5ZEWIR.js → audiences-JAVNJ5NC.js} +8 -8
- package/dist/audiences-JAVNJ5NC.js.map +1 -0
- package/dist/{businesses-CW4RB36R.js → businesses-DXRDQIFP.js} +2 -2
- package/dist/{campaigns-IQYYYD2S.js → campaigns-GQWFSKGC.js} +11 -11
- package/dist/campaigns-GQWFSKGC.js.map +1 -0
- package/dist/{catalogs-PLBY32BE.js → catalogs-TBRONC2D.js} +15 -15
- package/dist/catalogs-TBRONC2D.js.map +1 -0
- package/dist/{chunk-OEJNZSJG.js → chunk-4XXA4WCS.js} +4 -4
- package/dist/chunk-4XXA4WCS.js.map +1 -0
- package/dist/{chunk-HPAGYPAF.js → chunk-BN4TDL5L.js} +65 -23
- package/dist/chunk-BN4TDL5L.js.map +1 -0
- package/dist/{chunk-5LN7GWRE.js → chunk-GEFZVG4P.js} +10 -10
- package/dist/chunk-GEFZVG4P.js.map +1 -0
- package/dist/{chunk-XB2FODWU.js → chunk-TKDOVFO6.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{copy-writes-GZ744FHU.js → copy-writes-EUFEVWDW.js} +5 -5
- package/dist/{copy-writes-GZ744FHU.js.map → copy-writes-EUFEVWDW.js.map} +1 -1
- package/dist/{creatives-GTRQAZSB.js → creatives-6VJDUHEY.js} +6 -6
- package/dist/creatives-6VJDUHEY.js.map +1 -0
- package/dist/{custom-conversions-VQ3LBWYV.js → custom-conversions-NDY5RHSM.js} +6 -6
- package/dist/{custom-conversions-VQ3LBWYV.js.map → custom-conversions-NDY5RHSM.js.map} +1 -1
- package/dist/{draft-ME4TNPO7.js → draft-G432FJ7N.js} +2 -2
- package/dist/{insights-INIFHYR7.js → insights-IVW6W5CP.js} +2 -2
- package/dist/{instant-experiences-NMJUWNP3.js → instant-experiences-6F6GXZ67.js} +5 -5
- package/dist/{instant-experiences-NMJUWNP3.js.map → instant-experiences-6F6GXZ67.js.map} +1 -1
- package/dist/{leads-QBWR7FKT.js → leads-CJKRKB2Q.js} +6 -6
- package/dist/{leads-QBWR7FKT.js.map → leads-CJKRKB2Q.js.map} +1 -1
- package/dist/{media-23IW442U.js → media-HDHZULQ7.js} +3 -3
- package/dist/{meta-H3KBPU33.js → meta-MOSJVYIE.js} +38 -38
- package/dist/{page-photos-SHIGXDYE.js → page-photos-4JTIKXAW.js} +5 -5
- package/dist/page-photos-4JTIKXAW.js.map +1 -0
- package/dist/{pages-XJF3FBOJ.js → pages-BAUS7LQM.js} +2 -2
- package/dist/{performance-L3Q57H4H.js → performance-6XFKDJGY.js} +11 -2
- package/dist/performance-6XFKDJGY.js.map +1 -0
- package/dist/{pixels-AZCIWWXK.js → pixels-QOSEYXCP.js} +3 -3
- package/dist/pixels-QOSEYXCP.js.map +1 -0
- package/dist/{preview-BIEVHIDJ.js → preview-X6EJGPAY.js} +2 -2
- package/dist/{rule-writes-QLZNJ7CT.js → rule-writes-A4BFYHPA.js} +7 -7
- package/dist/rule-writes-A4BFYHPA.js.map +1 -0
- package/dist/{rules-FJHYG4SJ.js → rules-HLREXY2M.js} +11 -11
- package/dist/rules-HLREXY2M.js.map +1 -0
- package/dist/{shared-HY2VP7PT.js → shared-I3SB77VH.js} +2 -2
- package/dist/{studies-Z2ZIWLTO.js → studies-IGOCWM7F.js} +7 -7
- package/dist/{studies-Z2ZIWLTO.js.map → studies-IGOCWM7F.js.map} +1 -1
- package/dist/{study-writes-V7BXLTQG.js → study-writes-Z5P2W7JH.js} +6 -6
- package/dist/study-writes-Z5P2W7JH.js.map +1 -0
- package/dist/{targeting-YBIWD27C.js → targeting-7HXKBXWY.js} +12 -12
- package/dist/targeting-7HXKBXWY.js.map +1 -0
- package/dist/{write-commands-R5SN6CVQ.js → write-commands-CY734U34.js} +22 -22
- package/dist/write-commands-CY734U34.js.map +1 -0
- package/package.json +1 -1
- package/dist/account-assets-TAUEBCY5.js.map +0 -1
- package/dist/account-level-2MYDHOID.js.map +0 -1
- package/dist/ads-NNKUR4HG.js.map +0 -1
- package/dist/adsets-VO5C7PDM.js.map +0 -1
- package/dist/audience-writes-QDDADCYE.js.map +0 -1
- package/dist/audiences-TQ5ZEWIR.js.map +0 -1
- package/dist/campaigns-IQYYYD2S.js.map +0 -1
- package/dist/catalogs-PLBY32BE.js.map +0 -1
- package/dist/chunk-5LN7GWRE.js.map +0 -1
- package/dist/chunk-HPAGYPAF.js.map +0 -1
- package/dist/chunk-OEJNZSJG.js.map +0 -1
- package/dist/creatives-GTRQAZSB.js.map +0 -1
- package/dist/page-photos-SHIGXDYE.js.map +0 -1
- package/dist/performance-L3Q57H4H.js.map +0 -1
- package/dist/pixels-AZCIWWXK.js.map +0 -1
- package/dist/rule-writes-QLZNJ7CT.js.map +0 -1
- package/dist/rules-FJHYG4SJ.js.map +0 -1
- package/dist/study-writes-V7BXLTQG.js.map +0 -1
- package/dist/targeting-YBIWD27C.js.map +0 -1
- package/dist/write-commands-R5SN6CVQ.js.map +0 -1
- /package/dist/{accounts-VIAQC3PA.js.map → accounts-KRW3JNCW.js.map} +0 -0
- /package/dist/{ads-O4RL4JXD.js.map → ads-6ESCXPHK.js.map} +0 -0
- /package/dist/{asset-writes-PQNENVYY.js.map → asset-writes-SSI3CO2A.js.map} +0 -0
- /package/dist/{businesses-CW4RB36R.js.map → businesses-DXRDQIFP.js.map} +0 -0
- /package/dist/{chunk-XB2FODWU.js.map → chunk-TKDOVFO6.js.map} +0 -0
- /package/dist/{draft-ME4TNPO7.js.map → draft-G432FJ7N.js.map} +0 -0
- /package/dist/{insights-INIFHYR7.js.map → insights-IVW6W5CP.js.map} +0 -0
- /package/dist/{media-23IW442U.js.map → media-HDHZULQ7.js.map} +0 -0
- /package/dist/{meta-H3KBPU33.js.map → meta-MOSJVYIE.js.map} +0 -0
- /package/dist/{pages-XJF3FBOJ.js.map → pages-BAUS7LQM.js.map} +0 -0
- /package/dist/{preview-BIEVHIDJ.js.map → preview-X6EJGPAY.js.map} +0 -0
- /package/dist/{shared-HY2VP7PT.js.map → shared-I3SB77VH.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
handleMetaError,
|
|
5
5
|
resolveAccountIdArg,
|
|
6
6
|
todayIso
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BN4TDL5L.js";
|
|
8
8
|
import "./chunk-QALAJNBG.js";
|
|
9
9
|
import {
|
|
10
10
|
writeAdsJson,
|
|
@@ -77,7 +77,7 @@ async function runStats(args) {
|
|
|
77
77
|
emit(data, args);
|
|
78
78
|
}
|
|
79
79
|
async function runList(args) {
|
|
80
|
-
const accountId = resolveAccountIdArg(args);
|
|
80
|
+
const accountId = await resolveAccountIdArg(args);
|
|
81
81
|
const params = { "account-id": accountId };
|
|
82
82
|
if (args["no-cache"]) params["skip-cache"] = "true";
|
|
83
83
|
const { pixels, restrictedFields } = await apiGet("/api/ads/meta/pixels", params);
|
|
@@ -106,4 +106,4 @@ export {
|
|
|
106
106
|
pixelWarnings,
|
|
107
107
|
pixelsCommand
|
|
108
108
|
};
|
|
109
|
-
//# sourceMappingURL=pixels-
|
|
109
|
+
//# sourceMappingURL=pixels-QOSEYXCP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/meta/pixels.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport { NO_CACHE_ARG } from \"../../../cache-flag.ts\";\nimport { apiGet } from \"../../../client.ts\";\nimport { writeAdsJson, writeAdsOutput } from \"../output.ts\";\nimport type { AdsWarning } from \"../types.ts\";\nimport { csvOrJson, daysAgoIso, handleMetaError, resolveAccountIdArg, todayIso } from \"./shared.ts\";\n\nexport const pixelsCommand = defineCommand({\n meta: {\n name: \"pixels\",\n description: `List Meta Pixels for an ad account, or fetch firing stats for one pixel.\n\nExamples:\n baker ads meta pixels --account-id act_123\n baker ads meta pixels --account-id act_123 --pixel-id 9876 --stats --days 7`,\n },\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"pixel-id\": { type: \"string\", description: \"Pixel ID for --stats\" },\n stats: { type: \"boolean\", description: \"Fetch firing stats for the pixel\" },\n aggregation: {\n type: \"string\",\n description: \"stats aggregation: event|browser_type|host|pixel_fire\",\n default: \"event\",\n },\n days: { type: \"string\", description: \"Lookback days for --stats (default 7)\" },\n \"no-cache\": NO_CACHE_ARG,\n output: { type: \"string\", description: \"Output format\", default: \"json\" },\n },\n run: async ({ args }) => {\n try {\n if (args.stats) {\n await runStats(args);\n return;\n }\n await runList(args);\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\nasync function runStats(args: Record<string, unknown>): Promise<void> {\n const pixelId = args[\"pixel-id\"] as string | undefined;\n if (!pixelId) {\n handleMetaError(new Error(\"--pixel-id is required when --stats is set\"));\n }\n const days = args.days ? Number(args.days) : 7;\n if (!Number.isInteger(days) || days <= 0 || days > 90) {\n handleMetaError(new Error(\"--days must be a positive integer ≤ 90\"));\n }\n const params: Record<string, string> = {\n \"pixel-id\": pixelId,\n aggregation: (args.aggregation as string) ?? \"event\",\n \"start-time\": daysAgoIso(days),\n \"end-time\": todayIso(),\n };\n if (args[\"no-cache\"]) params[\"skip-cache\"] = \"true\";\n const data = await apiGet<Array<Record<string, unknown>>>(\"/api/ads/meta/pixel-stats\", params);\n emit(data, args);\n}\n\nasync function runList(args: Record<string, unknown>): Promise<void> {\n const accountId = await resolveAccountIdArg(args);\n const params: Record<string, string> = { \"account-id\": accountId };\n if (args[\"no-cache\"]) params[\"skip-cache\"] = \"true\";\n const { pixels, restrictedFields } = await apiGet<{\n pixels: Array<Record<string, unknown>>;\n restrictedFields: string[];\n }>(\"/api/ads/meta/pixels\", params);\n emit(pixels, args, pixelWarnings(restrictedFields));\n}\n\n/**\n * A listing Meta trimmed is still a listing — IDs, names and firing times are\n * complete. Say which setup fields are missing and how to get them, so the gap\n * reads as a client-side access grant rather than as a pixel that isn't there.\n */\nexport function pixelWarnings(restrictedFields: string[]): AdsWarning[] {\n if (restrictedFields.length === 0) {\n return [];\n }\n return [\n {\n code: \"PIXEL_FIELDS_RESTRICTED\",\n message:\n `Meta withheld ${restrictedFields.join(\", \")} for these pixels — the connected user holds no role in the Business that owns them. ` +\n \"IDs, names, creation and last-fired times are complete; install code and setup details are not. \" +\n \"To get them, the client assigns the pixel to the connected user in Meta Business settings → Data sources → Datasets.\",\n },\n ];\n}\n\nfunction emit(data: Array<Record<string, unknown>>, args: Record<string, unknown>, warnings: AdsWarning[] = []): void {\n const fmt = csvOrJson(args);\n if (fmt !== \"json\") {\n writeAdsOutput(data, fmt);\n return;\n }\n writeAdsJson({ ok: true, data, ...(warnings.length > 0 ? { warnings } : {}) });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAOvB,IAAM,gBAAgB,cAAc;AAAA,EACzC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,EAKf;AAAA,EACA,MAAM;AAAA,IACJ,cAAc,EAAE,MAAM,UAAU,aAAa,qBAAqB;AAAA,IAClE,YAAY,EAAE,MAAM,UAAU,aAAa,uBAAuB;AAAA,IAClE,OAAO,EAAE,MAAM,WAAW,aAAa,mCAAmC;AAAA,IAC1E,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,MAAM,EAAE,MAAM,UAAU,aAAa,wCAAwC;AAAA,IAC7E,YAAY;AAAA,IACZ,QAAQ,EAAE,MAAM,UAAU,aAAa,iBAAiB,SAAS,OAAO;AAAA,EAC1E;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,UAAI,KAAK,OAAO;AACd,cAAM,SAAS,IAAI;AACnB;AAAA,MACF;AACA,YAAM,QAAQ,IAAI;AAAA,IACpB,SAAS,KAAK;AACZ,sBAAgB,GAAG;AAAA,IACrB;AAAA,EACF;AACF,CAAC;AAED,eAAe,SAAS,MAA8C;AACpE,QAAM,UAAU,KAAK,UAAU;AAC/B,MAAI,CAAC,SAAS;AACZ,oBAAgB,IAAI,MAAM,4CAA4C,CAAC;AAAA,EACzE;AACA,QAAM,OAAO,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AAC7C,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,QAAQ,KAAK,OAAO,IAAI;AACrD,oBAAgB,IAAI,MAAM,6CAAwC,CAAC;AAAA,EACrE;AACA,QAAM,SAAiC;AAAA,IACrC,YAAY;AAAA,IACZ,aAAc,KAAK,eAA0B;AAAA,IAC7C,cAAc,WAAW,IAAI;AAAA,IAC7B,YAAY,SAAS;AAAA,EACvB;AACA,MAAI,KAAK,UAAU,EAAG,QAAO,YAAY,IAAI;AAC7C,QAAM,OAAO,MAAM,OAAuC,6BAA6B,MAAM;AAC7F,OAAK,MAAM,IAAI;AACjB;AAEA,eAAe,QAAQ,MAA8C;AACnE,QAAM,YAAY,MAAM,oBAAoB,IAAI;AAChD,QAAM,SAAiC,EAAE,cAAc,UAAU;AACjE,MAAI,KAAK,UAAU,EAAG,QAAO,YAAY,IAAI;AAC7C,QAAM,EAAE,QAAQ,iBAAiB,IAAI,MAAM,OAGxC,wBAAwB,MAAM;AACjC,OAAK,QAAQ,MAAM,cAAc,gBAAgB,CAAC;AACpD;AAOO,SAAS,cAAc,kBAA0C;AACtE,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO,CAAC;AAAA,EACV;AACA,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SACE,iBAAiB,iBAAiB,KAAK,IAAI,CAAC;AAAA,IAGhD;AAAA,EACF;AACF;AAEA,SAAS,KAAK,MAAsC,MAA+B,WAAyB,CAAC,GAAS;AACpH,QAAM,MAAM,UAAU,IAAI;AAC1B,MAAI,QAAQ,QAAQ;AAClB,mBAAe,MAAM,GAAG;AACxB;AAAA,EACF;AACA,eAAa,EAAE,IAAI,MAAM,MAAM,GAAI,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC,EAAG,CAAC;AAC/E;","names":[]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import "./chunk-GDFJOCDO.js";
|
|
8
8
|
import {
|
|
9
9
|
handleMetaError
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BN4TDL5L.js";
|
|
11
11
|
import "./chunk-QALAJNBG.js";
|
|
12
12
|
import {
|
|
13
13
|
writeAdsJson,
|
|
@@ -215,4 +215,4 @@ Examples:
|
|
|
215
215
|
export {
|
|
216
216
|
previewCommand
|
|
217
217
|
};
|
|
218
|
-
//# sourceMappingURL=preview-
|
|
218
|
+
//# sourceMappingURL=preview-X6EJGPAY.js.map
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
failWriteValidation,
|
|
7
7
|
requireTarget,
|
|
8
8
|
stageOp
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4XXA4WCS.js";
|
|
10
10
|
import "./chunk-GC5P4KWY.js";
|
|
11
11
|
import "./chunk-GDFJOCDO.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-BN4TDL5L.js";
|
|
13
13
|
import {
|
|
14
14
|
repeatedValues
|
|
15
15
|
} from "./chunk-ERSX3MKH.js";
|
|
@@ -424,7 +424,7 @@ Examples:
|
|
|
424
424
|
},
|
|
425
425
|
args: { ...ruleArgs, status: { type: "string", description: "ENABLED (default) or DISABLED" } },
|
|
426
426
|
run: async ({ args, rawArgs }) => {
|
|
427
|
-
const accountId = bareAccountId(args);
|
|
427
|
+
const accountId = await bareAccountId(args);
|
|
428
428
|
await stageOp({ kind: "adRule.create", accountId, payload: rulePayload(args, rawArgs, false) });
|
|
429
429
|
}
|
|
430
430
|
});
|
|
@@ -442,7 +442,7 @@ Example: baker ads meta rules update 1132635173044914 --apply-to ad --when "cost
|
|
|
442
442
|
},
|
|
443
443
|
args: { id: { type: "positional", description: "Rule id or meta_temp_* ref", required: true }, ...ruleArgs },
|
|
444
444
|
run: async ({ args, rawArgs }) => {
|
|
445
|
-
const accountId = bareAccountId(args);
|
|
445
|
+
const accountId = await bareAccountId(args);
|
|
446
446
|
const target = requireTarget(args, "rule");
|
|
447
447
|
const payload = rulePayload(args, rawArgs, true);
|
|
448
448
|
if (payload.status === "DELETED") {
|
|
@@ -475,7 +475,7 @@ function statusCommand(name) {
|
|
|
475
475
|
meta: { name, description: `Stage ${name} for an automated rule. ${note} ${STAGED_NOTE}` },
|
|
476
476
|
args: { id: { type: "positional", description: "Rule id", required: true }, "account-id": ruleArgs["account-id"] },
|
|
477
477
|
run: async ({ args }) => {
|
|
478
|
-
const accountId = bareAccountId(args);
|
|
478
|
+
const accountId = await bareAccountId(args);
|
|
479
479
|
await stageOp({
|
|
480
480
|
kind: "adRule.update",
|
|
481
481
|
accountId,
|
|
@@ -505,7 +505,7 @@ Example: baker ads meta rules run 1132635173044914`
|
|
|
505
505
|
},
|
|
506
506
|
args: { id: { type: "positional", description: "Rule id", required: true }, "account-id": ruleArgs["account-id"] },
|
|
507
507
|
run: async ({ args }) => {
|
|
508
|
-
const accountId = bareAccountId(args);
|
|
508
|
+
const accountId = await bareAccountId(args);
|
|
509
509
|
const target = requireTarget(args, "rule");
|
|
510
510
|
if (target.startsWith("meta_temp_")) {
|
|
511
511
|
failWriteValidation("a rule this chat creates exists only at publish \u2014 run it in a later chat, once it is live");
|
|
@@ -522,4 +522,4 @@ export {
|
|
|
522
522
|
rulesRunCommand,
|
|
523
523
|
rulesUpdateCommand
|
|
524
524
|
};
|
|
525
|
-
//# sourceMappingURL=rule-writes-
|
|
525
|
+
//# sourceMappingURL=rule-writes-A4BFYHPA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/meta/rule-writes.ts","../src/commands/ads/meta/rule-flags.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { defineCommand } from \"citty\";\nimport { repeatedValues } from \"../../../lib/repeatedFlags.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { type RuleFlags, ruleFileForStaging, rulePayloadFromFlags } from \"./rule-flags.ts\";\nimport { bareAccountId, failWriteValidation, requireTarget, stageOp } from \"./write-shared.ts\";\n\n/**\n * Staged Meta automated-rule writes. Nothing here reaches Meta: each command\n * stages an op on this chat's draft, and the rule is created, changed or\n * deleted when the chat publishes — after anything it names that the same\n * draft creates.\n */\n\nconst STAGED_NOTE = \"Staged until publish — review with `baker ads meta draft`.\";\n\nconst ruleArgs = {\n \"account-id\": { type: \"string\", description: \"Numeric account ID or act_N\" },\n name: { type: \"string\", description: \"Rule name, shown in Ads Manager → Automated rules\" },\n \"apply-to\": {\n type: \"string\",\n description: \"What the rule watches and acts on: ad | adset | campaign. Alone, it means every one in the account.\",\n },\n ids: {\n type: \"string\",\n description:\n \"Only these ads / ad sets / campaigns (repeatable or comma-separated; meta_temp_* refs from this chat work). With --action rotate: the ad sets whose ads take turns\",\n },\n \"in-campaigns\": { type: \"string\", description: \"Only what sits in these campaigns (repeatable or comma-separated)\" },\n \"in-adsets\": { type: \"string\", description: \"Only what sits in these ad sets (repeatable or comma-separated)\" },\n \"name-contains\": { type: \"string\", description: \"Only objects whose name contains this text\" },\n when: {\n type: \"string\",\n description:\n 'A condition, repeatable (all must hold): \"cost_per_result>20\", \"ctr<0.8\", \"spend between 10 and 50\", \"frequency outside 1 and 3\". Money in the account currency. Metrics: cost_per_result, cpa, cpc, cpm, ctr, frequency, spend, results, impressions, reach, roas, cost_per_lead, cost_per_purchase, or any Meta rule field.',\n },\n window: {\n type: \"string\",\n description:\n \"Time range the metrics are read over (default LAST_7D — the last 7 full days): TODAY, YESTERDAY, LAST_3D, LAST_7D, LAST_14D, LAST_30D, LAST_7_DAYS (today included), LIFETIME, THIS_MONTH\",\n },\n realtime: {\n type: \"boolean\",\n description:\n \"React the moment the first --when condition becomes true (a trigger rule) instead of on a schedule. Only pause, unpause and notify; the window must include today.\",\n },\n action: {\n type: \"string\",\n description: \"pause | unpause | notify | raise-budget | lower-budget | raise-bid | lower-bid | rotate\",\n },\n by: { type: \"string\", description: \"For a budget or bid change: 20% or an amount in the account currency\" },\n cap: {\n type: \"string\",\n description: \"Highest (raise) or lowest (lower) the budget or bid may reach, account currency\",\n },\n \"max-times\": { type: \"string\", description: \"Change each object at most this many times\" },\n every: { type: \"string\", description: \"Wait at least this long before changing the same object again: 90, 12h, 7d\" },\n \"notify-user-ids\": {\n type: \"string\",\n description: \"Facebook user ids to notify (default: whoever connected Meta); repeatable or comma-separated\",\n },\n schedule: { type: \"string\", description: \"daily (default) | hourly | half-hourly | custom\" },\n at: { type: \"string\", description: \"Custom schedule: run at HH:MM (half hours), repeatable\" },\n between: { type: \"string\", description: \"Custom schedule: run every 30 minutes in this window, e.g. 09:00-17:00\" },\n days: { type: \"string\", description: \"Custom schedule: only these days, e.g. mon,wed,fri\" },\n file: {\n type: \"string\",\n description:\n \"JSON with the full rule — {name, status, evaluation_spec, execution_spec, schedule_spec} in Meta's shape. What `rules --rule-id <id>` prints can be saved and passed as it is (its id and timestamps are left out). Flags override its name and status.\",\n },\n} as const;\n\n/** `ruleArgs` as `baker schema` lists them: every flag optional, as the shorthand and --file trade off. */\nfunction schemaArgs(\n args: Record<string, { type: string; description: string }>,\n): Record<string, { type: string; description: string; required: boolean }> {\n return Object.fromEntries(Object.entries(args).map(([key, arg]) => [key, { ...arg, required: false }]));\n}\n\nfunction listValues(rawArgs: readonly string[] | undefined, flag: string, parsed: unknown): string[] {\n return repeatedValues(rawArgs, flag, parsed)\n .flatMap((value) => value.split(\",\"))\n .map((value) => value.trim())\n .filter(Boolean);\n}\n\n/** The command line as rule flags, with every repeatable flag read whole (citty keeps only the last). */\nexport function ruleFlagsFromArgs(args: Record<string, unknown>, rawArgs: readonly string[] | undefined): RuleFlags {\n const text = (key: string) => (typeof args[key] === \"string\" && args[key] !== \"\" ? String(args[key]) : undefined);\n return {\n name: text(\"name\"),\n status: text(\"status\"),\n \"apply-to\": text(\"apply-to\"),\n ids: listValues(rawArgs, \"ids\", args.ids),\n \"in-campaigns\": listValues(rawArgs, \"in-campaigns\", args[\"in-campaigns\"]),\n \"in-adsets\": listValues(rawArgs, \"in-adsets\", args[\"in-adsets\"]),\n \"name-contains\": text(\"name-contains\"),\n when: repeatedValues(rawArgs, \"when\", args.when),\n window: text(\"window\"),\n realtime: args.realtime === true,\n action: text(\"action\"),\n by: text(\"by\"),\n cap: text(\"cap\"),\n \"max-times\": text(\"max-times\"),\n every: text(\"every\"),\n \"notify-user-ids\": listValues(rawArgs, \"notify-user-ids\", args[\"notify-user-ids\"]),\n schedule: text(\"schedule\"),\n at: repeatedValues(rawArgs, \"at\", args.at),\n between: text(\"between\"),\n days: listValues(rawArgs, \"days\", args.days).join(\",\") || undefined,\n };\n}\n\nfunction payloadOrFail(flags: RuleFlags, update: boolean): Record<string, unknown> {\n try {\n return rulePayloadFromFlags(flags, { update });\n } catch (err) {\n failWriteValidation(err instanceof Error ? err.message : String(err));\n }\n}\n\nfunction loadRuleFile(path: unknown, update: boolean): Record<string, unknown> {\n if (typeof path !== \"string\" || path.length === 0) {\n return {};\n }\n try {\n return ruleFileForStaging(JSON.parse(readFileSync(path, \"utf8\")) as unknown, { update });\n } catch (err) {\n failWriteValidation(`${path}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n/** A `--file` rule as the base, the flags on top: name and status always, a whole spec when its flags are given. */\nfunction rulePayload(args: Record<string, unknown>, rawArgs: readonly string[] | undefined, update: boolean) {\n const file = loadRuleFile(args.file, update);\n const flags = ruleFlagsFromArgs(args, rawArgs);\n const fromFile = Object.keys(file).length > 0;\n const fromFlags = fromFile ? payloadOrFail(flags, true) : payloadOrFail(flags, update);\n return { ...file, ...fromFlags };\n}\n\nregisterSchema({\n command: \"ads.meta.rules.create\",\n description:\n 'Stage a new Meta automated rule — it is created at publish and then runs on Meta, unattended. Shorthand: --apply-to ad|adset|campaign [--ids …] --when \"cost_per_result>20\" [--window LAST_7D] --action pause|unpause|notify|raise-budget|lower-budget|raise-bid|lower-bid|rotate [--by 20%] [--cap 500] [--schedule daily|hourly|custom --at 09:00 --days mon,fri] [--realtime]. Or --file with the full rule in Meta\\'s shape. Fields Meta compares: Insights metrics (cost_per, spent, results, impressions, reach, frequency, cpc, cpm, ctr, cpa, link_ctr, website_purchase_roas, cost_per_lead_fb, …) and metadata (name, daily_budget, hours_since_creation, effective_status, …), optionally prefixed ad./adset./campaign. Starts switched on unless --status DISABLED.',\n args: schemaArgs({ ...ruleArgs, status: { type: \"string\", description: \"ENABLED (default) or DISABLED\" } }),\n});\n\nexport const rulesCreateCommand = defineCommand({\n meta: {\n name: \"create\",\n description: `Stage a new Meta automated rule. ${STAGED_NOTE}\nIt runs on Meta from publish on, with nobody watching — say what it will do back to the user before publishing.\nExamples:\n baker ads meta rules create --name \"Stop expensive ads\" --apply-to ad --when \"cost_per_result>20\" --action pause\n baker ads meta rules create --name \"Scale winners\" --apply-to adset --in-campaigns 6123 --when \"roas>3\" --when \"spend>50\" --window LAST_3D --action raise-budget --by 20% --cap 500 --max-times 3\n baker ads meta rules create --name \"CPM alarm\" --apply-to campaign --when \"cpm>15\" --window TODAY --action notify --realtime\n baker ads meta rules create --file rule.json`,\n },\n args: { ...ruleArgs, status: { type: \"string\", description: \"ENABLED (default) or DISABLED\" } },\n run: async ({ args, rawArgs }) => {\n const accountId = await bareAccountId(args as Record<string, unknown>);\n await stageOp({ kind: \"adRule.create\", accountId, payload: rulePayload(args, rawArgs, false) });\n },\n});\n\nregisterSchema({\n command: \"ads.meta.rules.update\",\n description:\n \"Stage a change to a live rule (or amend one staged in this chat by its meta_temp_* ref). Each part you change is replaced whole: any of --apply-to/--ids/--in-*/--name-contains/--when/--window/--realtime rebuilds what it checks, --action/--by/--cap/--max-times/--every/--notify-user-ids what it does, --schedule/--at/--between/--days when it runs. Restate every condition you keep — read it first with `rules --rule-id`.\",\n args: { id: { type: \"positional\", description: \"Rule id\", required: true }, ...schemaArgs(ruleArgs) },\n});\n\nexport const rulesUpdateCommand = defineCommand({\n meta: {\n name: \"update\",\n description: `Stage a change to a rule. ${STAGED_NOTE}\nEach part you change is replaced whole — restate every condition you keep (read the rule first: \\`rules --rule-id <id>\\`). Switch with enable/disable; remove with delete.\nExample: baker ads meta rules update 1132635173044914 --apply-to ad --when \"cost_per_result>25\" --window LAST_7D`,\n },\n args: { id: { type: \"positional\", description: \"Rule id or meta_temp_* ref\", required: true }, ...ruleArgs },\n run: async ({ args, rawArgs }) => {\n const accountId = await bareAccountId(args as Record<string, unknown>);\n const target = requireTarget(args as Record<string, unknown>, \"rule\");\n const payload = rulePayload(args, rawArgs, true);\n if (payload.status === \"DELETED\") {\n failWriteValidation(\n \"update does not delete — use `baker ads meta rules delete <id>` if the user asked to remove it\",\n );\n }\n await stageOp({ kind: \"adRule.update\", accountId, target, payload });\n },\n});\n\nconst STATUS_COMMANDS = {\n enable: { status: \"ENABLED\", note: \"It starts running on its schedule from publish on.\" },\n disable: { status: \"DISABLED\", note: \"It stops running and keeps its settings; enable brings it back.\" },\n delete: {\n status: \"DELETED\",\n note: \"Deleting is permanent — Meta removes the rule from the account. Prefer disable unless the user asked to delete it.\",\n },\n} as const;\n\nfunction statusCommand(name: keyof typeof STATUS_COMMANDS) {\n const { status, note } = STATUS_COMMANDS[name];\n registerSchema({\n command: `ads.meta.rules.${name}`,\n description: `Stage ${name} for an automated rule. ${note}`,\n args: {\n id: { type: \"positional\", description: \"Rule id\", required: true },\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n },\n });\n return defineCommand({\n meta: { name, description: `Stage ${name} for an automated rule. ${note} ${STAGED_NOTE}` },\n args: { id: { type: \"positional\", description: \"Rule id\", required: true }, \"account-id\": ruleArgs[\"account-id\"] },\n run: async ({ args }) => {\n const accountId = await bareAccountId(args as Record<string, unknown>);\n await stageOp({\n kind: \"adRule.update\",\n accountId,\n target: requireTarget(args as Record<string, unknown>, \"rule\"),\n payload: { status },\n });\n },\n });\n}\n\nexport const rulesEnableCommand = statusCommand(\"enable\");\nexport const rulesDisableCommand = statusCommand(\"disable\");\nexport const rulesDeleteCommand = statusCommand(\"delete\");\n\nregisterSchema({\n command: \"ads.meta.rules.run\",\n description:\n \"Stage running an automated rule once, at publish — it acts on whatever matches it then, whatever its schedule and even while it is off. Check what it would act on first with `rules preview <id>`.\",\n args: {\n id: { type: \"positional\", description: \"Rule id\", required: true },\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n },\n});\n\nexport const rulesRunCommand = defineCommand({\n meta: {\n name: \"run\",\n description: `Stage running a rule once, at publish. ${STAGED_NOTE}\nThe rule acts on whatever matches it when the chat is published (pauses, budget changes, notifications) — Meta runs it then, whatever its schedule, even while it is off. Start here: baker ads meta rules preview <id>, then baker ads meta rules history --rule-id <id> after publishing.\nExample: baker ads meta rules run 1132635173044914`,\n },\n args: { id: { type: \"positional\", description: \"Rule id\", required: true }, \"account-id\": ruleArgs[\"account-id\"] },\n run: async ({ args }) => {\n const accountId = await bareAccountId(args as Record<string, unknown>);\n const target = requireTarget(args as Record<string, unknown>, \"rule\");\n if (target.startsWith(\"meta_temp_\")) {\n failWriteValidation(\"a rule this chat creates exists only at publish — run it in a later chat, once it is live\");\n }\n await stageOp({ kind: \"adRule.update\", accountId, target, payload: { run_now: true } });\n },\n});\n","import type {\n AdRuleEvaluationSpec,\n AdRuleExecutionOption,\n AdRuleExecutionSpec,\n AdRuleFilter,\n AdRuleScheduleSpec,\n} from \"@baker/api/ads-meta/adRules\";\nimport { decimalMarkShown } from \"./customer-list.ts\";\n\n/**\n * The flag shorthand for Meta automated rules — `--apply-to ad --when\n * \"cost_per_result>20\" --action pause` — as the rule payload Meta's own\n * vocabulary describes. Pure: the command collects repeated flags and prints\n * refusals; everything here returns a payload or throws a message the agent can\n * act on.\n */\n\n/** Words marketers use for a metric → Meta's rule field. Anything else is passed through as a Meta field. */\nconst METRIC_ALIASES: Readonly<Record<string, string>> = {\n cost_per_result: \"cost_per\",\n cpr: \"cost_per\",\n cost_per_lead: \"cost_per_lead_fb\",\n cost_per_purchase: \"cost_per_purchase_fb\",\n cost_per_add_to_cart: \"cost_per_add_to_cart_fb\",\n cost_per_registration: \"cost_per_complete_registration_fb\",\n roas: \"website_purchase_roas\",\n purchase_roas: \"website_purchase_roas\",\n spend: \"spent\",\n amount_spent: \"spent\",\n link_clicks: \"link_click\",\n leads: \"offsite_conversion.fb_pixel_lead\",\n purchases: \"offsite_conversion.fb_pixel_purchase\",\n};\n\nconst CONDITION_HELP =\n 'write a condition as \"cost_per_result>20\", \"ctr<0.8\", \"spend between 10 and 50\" or \"frequency outside 1 and 3\" — money in the account currency, a dot or a comma for decimals (12.5 or 12,5)';\n\nfunction metric(raw: string): string {\n const key = raw.trim().toLowerCase();\n const dot = key.lastIndexOf(\".\");\n const prefix = dot > 0 && !key.startsWith(\"offsite_conversion\") ? key.slice(0, dot + 1) : \"\";\n const base = prefix ? key.slice(prefix.length) : key;\n return `${prefix}${METRIC_ALIASES[base] ?? base}`;\n}\n\n/**\n * A number as the marketer wrote it: a dot or a comma as the decimal mark\n * (\"12.5\", \"12,50\"), thousands grouped either way (\"1,234.50\", \"12,345,678\",\n * \"1.234,50\"). A lone comma before three digits (\"1,234\") could be either and is\n * refused, never guessed — a hundredfold budget change is not a typo to absorb.\n */\nfunction number(raw: string): number {\n const cleaned = raw.replace(/\\s/g, \"\");\n const mark = decimalMarkShown(cleaned) ?? (cleaned.includes(\",\") ? undefined : \".\");\n if (!mark) {\n throw new Error(\n `\"${raw}\" is ambiguous — it could be ${cleaned.replace(/,/g, \"\")} or ${cleaned.replace(\",\", \".\")}; write it with a dot for decimals and no thousands separator (1234 or 1.234)`,\n );\n }\n const plain = cleaned\n .split(mark === \",\" ? \".\" : \",\")\n .join(\"\")\n .replace(\",\", \".\");\n const value = plain === \"\" ? Number.NaN : Number(plain);\n if (!Number.isFinite(value)) throw new Error(`\"${raw}\" is not a number — ${CONDITION_HELP}`);\n return value;\n}\n\n/** One `--when` condition as a rule filter. */\nexport function ruleConditionFromFlag(raw: string): AdRuleFilter {\n const range = /^\\s*([\\w.:]+)\\s+(between|outside)\\s+(-?[\\d.,]+)\\s+and\\s+(-?[\\d.,]+)\\s*$/i.exec(raw);\n if (range?.[1] && range[2] && range[3] && range[4]) {\n return {\n field: metric(range[1]),\n operator: range[2].toLowerCase() === \"between\" ? \"IN_RANGE\" : \"NOT_IN_RANGE\",\n value: [number(range[3]), number(range[4])],\n };\n }\n const compare = /^\\s*([\\w.:]+)\\s*(>|<|=|\\babove\\b|\\bbelow\\b)\\s*(-?[\\d.,]+)\\s*$/i.exec(raw);\n if (compare?.[1] && compare[2] && compare[3]) {\n const word = compare[2].toLowerCase();\n const operator =\n word === \">\" || word === \"above\" ? \"GREATER_THAN\" : word === \"<\" || word === \"below\" ? \"LESS_THAN\" : \"EQUAL\";\n return { field: metric(compare[1]), operator, value: number(compare[3]) };\n }\n throw new Error(`could not read --when \"${raw}\" — ${CONDITION_HELP}`);\n}\n\n// --- Flags ---\n\nexport interface RuleFlags {\n name?: string;\n status?: string;\n \"apply-to\"?: string;\n ids?: string[];\n \"in-campaigns\"?: string[];\n \"in-adsets\"?: string[];\n \"name-contains\"?: string;\n when?: string[];\n window?: string;\n realtime?: boolean;\n action?: string;\n by?: string;\n cap?: string;\n \"max-times\"?: string;\n every?: string;\n \"notify-user-ids\"?: string[];\n schedule?: string;\n at?: string[];\n between?: string;\n days?: string;\n}\n\nconst LEVELS: Readonly<Record<string, string>> = {\n ad: \"AD\",\n ads: \"AD\",\n adset: \"ADSET\",\n adsets: \"ADSET\",\n \"ad-set\": \"ADSET\",\n campaign: \"CAMPAIGN\",\n campaigns: \"CAMPAIGN\",\n};\n\nconst EVALUATION_FLAGS = [\n \"apply-to\",\n \"ids\",\n \"in-campaigns\",\n \"in-adsets\",\n \"name-contains\",\n \"when\",\n \"window\",\n \"realtime\",\n] as const;\nconst EXECUTION_FLAGS = [\"action\", \"by\", \"cap\", \"max-times\", \"every\", \"notify-user-ids\"] as const;\nconst SCHEDULE_FLAGS = [\"schedule\", \"at\", \"between\", \"days\"] as const;\n\nfunction given(flags: RuleFlags, names: readonly (keyof RuleFlags)[]): boolean {\n return names.some((name) => {\n const value = flags[name];\n return Array.isArray(value) ? value.length > 0 : value !== undefined && value !== false && value !== \"\";\n });\n}\n\nfunction ruleLevel(flags: RuleFlags): string | undefined {\n const raw = flags[\"apply-to\"];\n if (!raw) return isRotate(flags) ? \"AD\" : undefined;\n const level = LEVELS[raw.toLowerCase()];\n if (!level) throw new Error(`--apply-to takes ad, adset or campaign — not \"${raw}\"`);\n return level;\n}\n\nfunction listFilter(field: string, ids: string[] | undefined): AdRuleFilter[] {\n return ids?.length ? [{ field, operator: \"IN\", value: ids }] : [];\n}\n\n/** What the rule watches: its level, the objects it names, and a name match. */\nfunction isRotate(flags: RuleFlags): boolean {\n return flags.action?.toLowerCase() === \"rotate\";\n}\n\n/**\n * A rotate rule acts on ads but names the ad sets it rotates within; Meta takes\n * those as `adset.id` beside entity_type AD and refuses a bare `id` (live).\n */\nfunction scopeFilters(flags: RuleFlags, level: string | undefined): AdRuleFilter[] {\n return [\n ...(level ? [{ field: \"entity_type\", operator: \"EQUAL\" as const, value: level }] : []),\n ...listFilter(isRotate(flags) ? \"adset.id\" : \"id\", flags.ids),\n ...listFilter(\"campaign.id\", flags[\"in-campaigns\"]),\n ...listFilter(\"adset.id\", flags[\"in-adsets\"]),\n ...(flags[\"name-contains\"] ? [{ field: \"name\", operator: \"CONTAIN\" as const, value: flags[\"name-contains\"] }] : []),\n ];\n}\n\nfunction evaluationFromFlags(flags: RuleFlags): AdRuleEvaluationSpec {\n const level = ruleLevel(flags);\n if (!level && !flags.ids?.length) {\n throw new Error(\n \"say what the rule watches: --apply-to ad|adset|campaign (every one in the account), plus --ids to name them\",\n );\n }\n const conditions = (flags.when ?? []).map(ruleConditionFromFlag);\n const [first, ...rest] = conditions;\n const trigger = flags.realtime ? first : undefined;\n if (flags.realtime && !trigger) throw new Error(\"--realtime needs a --when condition to react to\");\n const filters = scopeFilters(flags, level);\n if (conditions.length > 0 || flags.window) {\n const preset = (flags.window ?? (flags.realtime ? \"TODAY\" : \"LAST_7D\")).toUpperCase();\n filters.push({ field: \"time_preset\", operator: \"EQUAL\", value: preset });\n }\n filters.push(...(trigger ? rest : conditions));\n return {\n evaluation_type: trigger ? \"TRIGGER\" : \"SCHEDULE\",\n filters,\n ...(trigger ? { trigger: { type: \"STATS_CHANGE\" as const, ...trigger } } : {}),\n };\n}\n\nconst ACTIONS: Readonly<Record<string, { type: AdRuleExecutionSpec[\"execution_type\"]; sign?: 1 | -1 }>> = {\n pause: { type: \"PAUSE\" },\n unpause: { type: \"UNPAUSE\" },\n \"turn-on\": { type: \"UNPAUSE\" },\n notify: { type: \"NOTIFICATION\" },\n \"raise-budget\": { type: \"CHANGE_BUDGET\", sign: 1 },\n \"lower-budget\": { type: \"CHANGE_BUDGET\", sign: -1 },\n \"raise-bid\": { type: \"CHANGE_BID\", sign: 1 },\n \"lower-bid\": { type: \"CHANGE_BID\", sign: -1 },\n rotate: { type: \"ROTATE\" },\n};\n\n/** `90` minutes, `12h`, `7d` → minutes. */\nfunction minutes(raw: string): number {\n const match = /^\\s*(\\d+)\\s*(m|min|h|d)?\\s*$/i.exec(raw);\n if (!match?.[1]) throw new Error(`--every takes minutes, or hours/days like 12h or 7d — not \"${raw}\"`);\n const unit = (match[2] ?? \"m\").toLowerCase();\n return Number(match[1]) * (unit === \"d\" ? 1440 : unit === \"h\" ? 60 : 1);\n}\n\nfunction changeSpec(flags: RuleFlags, sign: 1 | -1): Record<string, unknown> {\n if (!flags.by)\n throw new Error(`--action ${flags.action} needs --by: a percentage like 20% or an amount in the account currency`);\n const percent = flags.by.trim().endsWith(\"%\");\n const amount = number(flags.by.replace(\"%\", \"\")) * sign;\n return {\n amount,\n unit: percent ? \"PERCENTAGE\" : \"ACCOUNT_CURRENCY\",\n ...(flags.cap ? { limit: number(flags.cap) } : {}),\n };\n}\n\nfunction executionFromFlags(flags: RuleFlags, level: string | undefined): AdRuleExecutionSpec {\n const action = flags.action ? ACTIONS[flags.action.toLowerCase()] : undefined;\n if (!action) {\n throw new Error(\n `--action takes ${Object.keys(ACTIONS).join(\", \")}${flags.action ? ` — not \"${flags.action}\"` : \"\"}`,\n );\n }\n // A campaign's budget is its own action on Meta (Advantage campaign budget).\n const type = action.type === \"CHANGE_BUDGET\" && level === \"CAMPAIGN\" ? \"CHANGE_CAMPAIGN_BUDGET\" : action.type;\n const options: AdRuleExecutionOption[] = [];\n if (action.sign) options.push({ field: \"change_spec\", operator: \"EQUAL\", value: changeSpec(flags, action.sign) });\n if (flags[\"max-times\"])\n options.push({ field: \"execution_count_limit\", operator: \"EQUAL\", value: number(flags[\"max-times\"]) });\n if (flags.every) options.push({ field: \"action_frequency\", operator: \"EQUAL\", value: minutes(flags.every) });\n if (flags[\"notify-user-ids\"]?.length)\n options.push({ field: \"user_ids\", operator: \"EQUAL\", value: flags[\"notify-user-ids\"] });\n return { execution_type: type, ...(options.length > 0 ? { execution_options: options } : {}) };\n}\n\nconst DAY_NUMBERS: Readonly<Record<string, number>> = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };\n\nfunction clockMinutes(raw: string): number {\n const match = /^\\s*(\\d{1,2}):(\\d{2})\\s*$/.exec(raw);\n if (!match?.[1] || !match[2]) throw new Error(`times are HH:MM on the half hour, like 09:30 — not \"${raw}\"`);\n return Number(match[1]) * 60 + Number(match[2]);\n}\n\nfunction days(raw: string | undefined): number[] | undefined {\n if (!raw) return undefined;\n return raw.split(\",\").map((day) => {\n const value = DAY_NUMBERS[day.trim().slice(0, 3).toLowerCase()];\n if (value === undefined) throw new Error(`--days takes mon,tue,wed,thu,fri,sat,sun — not \"${day}\"`);\n return value;\n });\n}\n\nconst SCHEDULE_TYPES: Readonly<Record<string, AdRuleScheduleSpec[\"schedule_type\"]>> = {\n daily: \"DAILY\",\n hourly: \"HOURLY\",\n \"half-hourly\": \"SEMI_HOURLY\",\n \"semi-hourly\": \"SEMI_HOURLY\",\n custom: \"CUSTOM\",\n};\n\nfunction scheduleFromFlags(flags: RuleFlags): AdRuleScheduleSpec {\n const custom = Boolean(flags.at?.length || flags.between || flags.days);\n const type = flags.schedule ? SCHEDULE_TYPES[flags.schedule.toLowerCase()] : custom ? \"CUSTOM\" : \"DAILY\";\n if (!type) throw new Error(`--schedule takes daily, hourly, half-hourly or custom — not \"${flags.schedule}\"`);\n // Times and days only belong to a custom schedule: refused, not dropped, so the\n // rule never runs on a timetable nobody asked for.\n if (type !== \"CUSTOM\" && custom) {\n throw new Error(\n `--at, --between and --days set a custom timetable, which --schedule ${flags.schedule} does not take — use --schedule custom (or leave --schedule out)`,\n );\n }\n if (type !== \"CUSTOM\") return { schedule_type: type };\n const dayList = days(flags.days);\n const withDays = dayList ? { days: dayList } : {};\n const items: NonNullable<AdRuleScheduleSpec[\"schedule\"]> = (flags.at ?? []).map((at) => ({\n start_minute: clockMinutes(at),\n ...withDays,\n }));\n if (flags.between) {\n const [from, to] = flags.between.split(\"-\");\n if (!from || !to) throw new Error(`--between takes a time range like 09:00-17:00 — not \"${flags.between}\"`);\n items.push({ start_minute: clockMinutes(from), end_minute: clockMinutes(to), ...withDays });\n }\n return { schedule_type: \"CUSTOM\", schedule: items.length > 0 ? items : [withDays] };\n}\n\n/**\n * The rule payload the flags describe. On an update, a part (what it checks,\n * what it does, when it runs) is built only when one of its flags is given —\n * and then whole, since Meta replaces a spec rather than merging it.\n */\n/** What a rule read carries that Meta sets itself — never sent back. */\nconst READ_ONLY_RULE_KEYS = new Set([\n \"id\",\n \"account_id\",\n \"created_time\",\n \"updated_time\",\n \"created_by\",\n \"disable_error_code\",\n \"description\",\n \"currencyCode\",\n]);\n\n/**\n * A rule file as staging takes it. It may be exactly what `rules --rule-id`\n * printed — the envelope, its `data` list of one, or the rule — so a read can\n * be edited and passed back; the fields Meta keeps for itself are left out.\n */\nexport function ruleFileForStaging(\n raw: unknown,\n { update = false }: { update?: boolean } = {},\n): Record<string, unknown> {\n const unwrapped =\n raw && typeof raw === \"object\" && !Array.isArray(raw) && \"data\" in raw ? (raw as { data: unknown }).data : raw;\n const list = Array.isArray(unwrapped) ? unwrapped : [unwrapped];\n if (list.length !== 1) {\n throw new Error(`holds ${list.length} rules — keep the one to stage`);\n }\n const rule = list[0];\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) {\n throw new Error(\"must contain a JSON object with the rule\");\n }\n // An update switches a rule only through enable/disable: restating the read's\n // status would be refused as \"already DISABLED\".\n return Object.fromEntries(\n Object.entries(rule).filter(([key]) => !READ_ONLY_RULE_KEYS.has(key) && !(update && key === \"status\")),\n );\n}\n\nexport function rulePayloadFromFlags(\n flags: RuleFlags,\n { update = false }: { update?: boolean } = {},\n): Record<string, unknown> & { evaluation_spec?: AdRuleEvaluationSpec; schedule_spec?: AdRuleScheduleSpec } {\n if (flags.realtime && given(flags, SCHEDULE_FLAGS)) {\n throw new Error(\n \"--realtime rules react the moment a metric changes and run on no schedule — drop --schedule/--at/--between/--days, or drop --realtime\",\n );\n }\n const evaluation = !update || given(flags, EVALUATION_FLAGS) ? evaluationFromFlags(flags) : undefined;\n const level = ruleLevel(flags);\n const execution = !update || given(flags, EXECUTION_FLAGS) ? executionFromFlags(flags, level) : undefined;\n const realtime = evaluation?.evaluation_type === \"TRIGGER\";\n const schedule = !realtime && (!update || given(flags, SCHEDULE_FLAGS)) ? scheduleFromFlags(flags) : undefined;\n return {\n ...(flags.name ? { name: flags.name } : {}),\n ...(flags.status ? { status: flags.status.toUpperCase() } : {}),\n ...(evaluation ? { evaluation_spec: evaluation } : {}),\n ...(execution ? { execution_spec: execution } : {}),\n ...(schedule ? { schedule_spec: schedule } : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleMetaError,
|
|
3
3
|
resolveAccountIdArg
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BN4TDL5L.js";
|
|
5
5
|
import "./chunk-QALAJNBG.js";
|
|
6
6
|
import {
|
|
7
7
|
writeJsonEnvelope
|
|
@@ -68,7 +68,7 @@ var listCommand = defineCommand({
|
|
|
68
68
|
full: { type: "boolean", description: "Include the specs and timestamps" }
|
|
69
69
|
},
|
|
70
70
|
run: async ({ args }) => {
|
|
71
|
-
const accountId = resolveAccountIdArg(args);
|
|
71
|
+
const accountId = await resolveAccountIdArg(args);
|
|
72
72
|
try {
|
|
73
73
|
const params = { "account-id": accountId };
|
|
74
74
|
if (args["rule-id"]) params["rule-id"] = String(args["rule-id"]);
|
|
@@ -126,7 +126,7 @@ var historyCommand = defineCommand({
|
|
|
126
126
|
full: { type: "boolean", description: "Include the rule's specs at each run" }
|
|
127
127
|
},
|
|
128
128
|
run: async ({ args }) => {
|
|
129
|
-
const accountId = resolveAccountIdArg(args);
|
|
129
|
+
const accountId = await resolveAccountIdArg(args);
|
|
130
130
|
try {
|
|
131
131
|
const params = { "account-id": accountId };
|
|
132
132
|
for (const key of ["rule-id", "object-id", "action", "limit"]) {
|
|
@@ -161,7 +161,7 @@ var previewCommand = defineCommand({
|
|
|
161
161
|
"account-id": { type: "string", description: "Meta ad account ID" }
|
|
162
162
|
},
|
|
163
163
|
run: async ({ args }) => {
|
|
164
|
-
const accountId = resolveAccountIdArg(args);
|
|
164
|
+
const accountId = await resolveAccountIdArg(args);
|
|
165
165
|
try {
|
|
166
166
|
const data = await apiGet("/api/ads/meta/rules/preview", {
|
|
167
167
|
"account-id": accountId,
|
|
@@ -194,12 +194,12 @@ Examples:
|
|
|
194
194
|
list: listCommand,
|
|
195
195
|
history: historyCommand,
|
|
196
196
|
preview: previewCommand,
|
|
197
|
-
create: () => import("./rule-writes-
|
|
198
|
-
update: () => import("./rule-writes-
|
|
199
|
-
enable: () => import("./rule-writes-
|
|
200
|
-
disable: () => import("./rule-writes-
|
|
201
|
-
delete: () => import("./rule-writes-
|
|
202
|
-
run: () => import("./rule-writes-
|
|
197
|
+
create: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesCreateCommand),
|
|
198
|
+
update: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesUpdateCommand),
|
|
199
|
+
enable: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesEnableCommand),
|
|
200
|
+
disable: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesDisableCommand),
|
|
201
|
+
delete: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesDeleteCommand),
|
|
202
|
+
run: () => import("./rule-writes-A4BFYHPA.js").then((m) => m.rulesRunCommand)
|
|
203
203
|
},
|
|
204
204
|
args: {
|
|
205
205
|
"account-id": { type: "string", description: "Meta ad account ID" },
|
|
@@ -214,4 +214,4 @@ export {
|
|
|
214
214
|
ruleListHints,
|
|
215
215
|
rulesCommand
|
|
216
216
|
};
|
|
217
|
-
//# sourceMappingURL=rules-
|
|
217
|
+
//# sourceMappingURL=rules-HLREXY2M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ads/meta/rules.ts"],"sourcesContent":["import type { MetaAdRuleHistoryRow, MetaAdRuleRow } from \"@baker/api/ads-meta/adRules\";\nimport { defineCommand } from \"citty\";\nimport { apiGet } from \"../../../client.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { handleMetaError, resolveAccountIdArg } from \"./shared.ts\";\n\n/**\n * Meta automated rules — reads. A rule is Meta's own automation: it runs on\n * Meta, on Meta's schedule, with no Baker involvement once created. Writes\n * (create / update / enable / disable / delete) stage like every Meta change\n * and live in `rule-writes.ts`.\n */\n\n/** What an agent needs to pick a rule and know what it does. Specs sit behind --full. */\nexport function compactRule(row: MetaAdRuleRow): Record<string, unknown> {\n return {\n id: row.id,\n name: row.name,\n status: row.status,\n does: row.description,\n ...(row.disable_error_code ? { disable_error_code: row.disable_error_code } : {}),\n updated_time: row.updated_time,\n };\n}\n\nexport function ruleListHints(rows: readonly MetaAdRuleRow[]): string[] {\n const hints: string[] = [];\n if (rows.length === 0) {\n hints.push(\n 'No automated rules on this account. Stage one with `baker ads meta rules create --name … --apply-to ad --when \"cost_per_result>20\" --action pause`.',\n );\n }\n const issues = rows.filter((row) => row.status === \"HAS_ISSUES\");\n if (issues.length > 0) {\n hints.push(\n `${issues.map((row) => row.name ?? row.id).join(\", \")} ${issues.length === 1 ? \"has\" : \"have\"} been stopped by Meta (HAS_ISSUES) — read it with --rule-id and fix the spec with \\`rules update\\`.`,\n );\n }\n if (rows.length > 0) {\n hints.push(\n \"`rules history --rule-id <id>` shows what a rule did and when; `rules preview <id>` what it would act on now.\",\n );\n }\n return hints;\n}\n\nregisterSchema({\n command: \"ads.meta.rules\",\n description:\n \"List the automated rules on a Meta ad account (Ads Manager → Automated rules): each rule's status and what it does, in words. --rule-id reads one; --full adds its evaluation_spec, execution_spec and schedule_spec in the shape `rules update --file` takes (money in major units of the account currency).\",\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n \"rule-id\": { type: \"string\", description: \"Read one rule\", required: false },\n full: { type: \"boolean\", description: \"Include the specs and timestamps\", required: false },\n },\n});\n\nconst listCommand = defineCommand({\n meta: {\n name: \"list\",\n description:\n \"List automated rules. Runs by default. Example: baker ads meta rules --account-id act_123 · one rule in full: baker ads meta rules --rule-id 1132635173044914 --full\",\n },\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"rule-id\": { type: \"string\", description: \"Read one rule\" },\n full: { type: \"boolean\", description: \"Include the specs and timestamps\" },\n },\n run: async ({ args }) => {\n const accountId = await resolveAccountIdArg(args);\n try {\n const params: Record<string, string> = { \"account-id\": accountId };\n if (args[\"rule-id\"]) params[\"rule-id\"] = String(args[\"rule-id\"]);\n const rows = await apiGet<MetaAdRuleRow[]>(\"/api/ads/meta/rules\", params);\n const full = Boolean(args.full) || Boolean(args[\"rule-id\"]);\n writeJsonEnvelope({ ok: true, data: full ? rows : rows.map(compactRule), hints: ruleListHints(rows) });\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\n/** A run in a line an agent can read: when, which rule, and what it changed. */\nexport function compactHistory(row: MetaAdRuleHistoryRow): Record<string, unknown> {\n const changes = (row.results ?? []).flatMap((result) =>\n (result.actions ?? []).map((action) => ({\n object_id: result.object_id,\n action: action.action,\n ...(action.field ? { field: action.field } : {}),\n ...(action.old_value !== undefined ? { from: action.old_value } : {}),\n ...(action.new_value !== undefined ? { to: action.new_value } : {}),\n })),\n );\n return {\n timestamp: row.timestamp,\n rule_id: row.rule_id,\n ...(row.is_manual ? { manual: true } : {}),\n changes,\n ...(row.exception_message ? { error: row.exception_message } : {}),\n };\n}\n\nregisterSchema({\n command: \"ads.meta.rules.history\",\n description:\n \"What automated rules did and when: each run with the objects it changed (PAUSED, CHANGED_BUDGET, …, old → new value). Without --rule-id, every rule on the account, newest first.\",\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n \"rule-id\": { type: \"string\", description: \"One rule's runs\", required: false },\n \"object-id\": { type: \"string\", description: \"Only runs that touched this ad, ad set or campaign\", required: false },\n action: { type: \"string\", description: \"Only this action, e.g. PAUSED, CHANGED_BUDGET\", required: false },\n \"hide-no-changes\": { type: \"boolean\", description: \"Drop runs that changed nothing\", required: false },\n limit: { type: \"string\", description: \"Max runs (default 50)\", required: false },\n full: { type: \"boolean\", description: \"Include the rule's specs at each run\", required: false },\n },\n});\n\nconst historyCommand = defineCommand({\n meta: {\n name: \"history\",\n description:\n \"What rules did and when. Example: baker ads meta rules history --rule-id 1132635173044914 --hide-no-changes · every rule: baker ads meta rules history --account-id act_123\",\n },\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"rule-id\": { type: \"string\", description: \"One rule's runs\" },\n \"object-id\": { type: \"string\", description: \"Only runs that touched this object\" },\n action: { type: \"string\", description: \"Only this action (PAUSED, CHANGED_BUDGET, …)\" },\n \"hide-no-changes\": { type: \"boolean\", description: \"Drop runs that changed nothing\" },\n limit: { type: \"string\", description: \"Max runs (default 50)\" },\n full: { type: \"boolean\", description: \"Include the rule's specs at each run\" },\n },\n run: async ({ args }) => {\n const accountId = await resolveAccountIdArg(args);\n try {\n const params: Record<string, string> = { \"account-id\": accountId };\n for (const key of [\"rule-id\", \"object-id\", \"action\", \"limit\"] as const) {\n if (args[key]) params[key] = String(args[key]);\n }\n if (args[\"hide-no-changes\"]) params[\"hide-no-changes\"] = \"true\";\n const rows = await apiGet<MetaAdRuleHistoryRow[]>(\"/api/ads/meta/rules/history\", params);\n const hints =\n rows.length === 0\n ? [\n \"No runs recorded. A daily rule first runs just after midnight in the ad account's time zone; a switched-off rule never runs.\",\n ]\n : [];\n writeJsonEnvelope({ ok: true, data: args.full ? rows : rows.map(compactHistory), hints });\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\nregisterSchema({\n command: \"ads.meta.rules.preview\",\n description:\n \"Which ads, ad sets or campaigns a live schedule rule would act on right now, by Meta's own evaluation. Changes nothing.\",\n args: {\n id: { type: \"positional\", description: \"Rule id\", required: true },\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n },\n});\n\nconst previewCommand = defineCommand({\n meta: {\n name: \"preview\",\n description:\n \"What a rule would act on right now (changes nothing). Example: baker ads meta rules preview 1132635173044914\",\n },\n args: {\n id: { type: \"positional\", description: \"Rule id\", required: true },\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n },\n run: async ({ args }) => {\n const accountId = await resolveAccountIdArg(args);\n try {\n const data = await apiGet<{ ruleId: string; matched: unknown[] }>(\"/api/ads/meta/rules/preview\", {\n \"account-id\": accountId,\n \"rule-id\": String(args.id),\n });\n const hints =\n data.matched.length === 0 ? [\"Nothing matches right now — the rule would change nothing if it ran now.\"] : [];\n writeJsonEnvelope({ ok: true, data, hints });\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\nexport const rulesCommand = defineCommand({\n meta: {\n name: \"rules\",\n description: `Meta automated rules — Meta's own automation, which runs on Meta with no Baker involvement once created.\nUse one for a standing \"if this metric crosses that line, do X\" that needs no judgement; use a Baker scheduled agent when the call needs reasoning.\n\nExamples:\n baker ads meta rules --account-id act_123 # every rule, what it does\n baker ads meta rules --rule-id 1132635173044914 # one rule with its specs\n baker ads meta rules history --rule-id 1132635173044914 --hide-no-changes # what it did\n baker ads meta rules preview 1132635173044914 # what it would act on now\n baker ads meta rules create --name \"Stop expensive ads\" --apply-to ad --when \"cost_per_result>20\" --action pause\n baker ads meta rules update 1132635173044914 --name \"Stop very expensive ads\"\n baker ads meta rules disable|enable|delete 1132635173044914\n baker ads meta rules run 1132635173044914 # run it once, at publish`,\n },\n subCommands: {\n list: listCommand,\n history: historyCommand,\n preview: previewCommand,\n create: () => import(\"./rule-writes.ts\").then((m) => m.rulesCreateCommand),\n update: () => import(\"./rule-writes.ts\").then((m) => m.rulesUpdateCommand),\n enable: () => import(\"./rule-writes.ts\").then((m) => m.rulesEnableCommand),\n disable: () => import(\"./rule-writes.ts\").then((m) => m.rulesDisableCommand),\n delete: () => import(\"./rule-writes.ts\").then((m) => m.rulesDeleteCommand),\n run: () => import(\"./rule-writes.ts\").then((m) => m.rulesRunCommand),\n },\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"rule-id\": { type: \"string\", description: \"Read one rule\" },\n full: { type: \"boolean\", description: \"Include the specs and timestamps\" },\n },\n default: \"list\",\n});\n"],"mappings":"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","names":[]}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveEffectiveStatus,
|
|
10
10
|
statusFilterHints,
|
|
11
11
|
todayIso
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BN4TDL5L.js";
|
|
13
13
|
import "./chunk-QALAJNBG.js";
|
|
14
14
|
import "./chunk-HPDGVK6R.js";
|
|
15
15
|
import "./chunk-B4UVJCJ4.js";
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
statusFilterHints,
|
|
30
30
|
todayIso
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=shared-
|
|
32
|
+
//# sourceMappingURL=shared-I3SB77VH.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
handleMetaError,
|
|
8
8
|
resolveAccountIdArg
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BN4TDL5L.js";
|
|
10
10
|
import "./chunk-QALAJNBG.js";
|
|
11
11
|
import {
|
|
12
12
|
writeJsonEnvelope
|
|
@@ -206,7 +206,7 @@ var listCommand = defineCommand({
|
|
|
206
206
|
writeJsonEnvelope({ ok: true, data: args.full ? row : detail, hints: studyHints(detail) });
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
|
-
const params = args["business-id"] ? { "business-id": String(args["business-id"]) } : { "account-id": resolveAccountIdArg(args) };
|
|
209
|
+
const params = args["business-id"] ? { "business-id": String(args["business-id"]) } : { "account-id": await resolveAccountIdArg(args) };
|
|
210
210
|
const rows = await read("/api/ads/meta/studies", params, args["no-cache"]);
|
|
211
211
|
const hints = rows.length === 0 ? [
|
|
212
212
|
"No A/B tests. To set one up: duplicate the ad set (`adsets duplicate`), change ONE thing on the copy, then `studies create --adsets <original>,<copy>`."
|
|
@@ -232,10 +232,10 @@ Examples:
|
|
|
232
232
|
},
|
|
233
233
|
subCommands: {
|
|
234
234
|
list: listCommand,
|
|
235
|
-
create: () => import("./study-writes-
|
|
236
|
-
update: () => import("./study-writes-
|
|
237
|
-
end: () => import("./study-writes-
|
|
238
|
-
delete: () => import("./study-writes-
|
|
235
|
+
create: () => import("./study-writes-Z5P2W7JH.js").then((m) => m.studiesCreateCommand),
|
|
236
|
+
update: () => import("./study-writes-Z5P2W7JH.js").then((m) => m.studiesUpdateCommand),
|
|
237
|
+
end: () => import("./study-writes-Z5P2W7JH.js").then((m) => m.studiesEndCommand),
|
|
238
|
+
delete: () => import("./study-writes-Z5P2W7JH.js").then((m) => m.studiesDeleteCommand)
|
|
239
239
|
},
|
|
240
240
|
args: listArgs,
|
|
241
241
|
default: "list"
|
|
@@ -247,4 +247,4 @@ export {
|
|
|
247
247
|
studyHints,
|
|
248
248
|
studyState
|
|
249
249
|
};
|
|
250
|
-
//# sourceMappingURL=studies-
|
|
250
|
+
//# sourceMappingURL=studies-IGOCWM7F.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../api/src/ads-meta/adStudyDescription.ts","../src/commands/ads/meta/studies.ts"],"sourcesContent":["/**\n * A Meta A/B test in words — the one reading shared by the staged summary, the\n * draft review rows and the CLI, so all three say the same thing: what each\n * version runs and how much of the audience it gets, what decides the winner,\n * for how long, and how sure Meta has to be.\n */\nimport type { AdStudyCell, AdStudyObjective } from \"./adStudies\";\n\nexport interface AdStudyWordsInput {\n type?: string;\n start_time?: number;\n end_time?: number;\n confidence_level?: number;\n cells?: readonly Pick<\n AdStudyCell,\n \"name\" | \"treatment_percentage\" | \"control_percentage\" | \"adsets\" | \"campaigns\" | \"ads\"\n >[];\n objectives?: readonly AdStudyObjective[];\n}\n\nexport interface AdStudyWords {\n sentence: string;\n versions?: string;\n measuring: string;\n /** The measures besides the primary one, by name. */\n alsoMeasured?: string;\n schedule?: string;\n confidence?: string;\n}\n\nconst MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\nexport function adStudyTime(seconds: number): string {\n const date = new Date(seconds * 1000);\n const hh = String(date.getUTCHours()).padStart(2, \"0\");\n const mm = String(date.getUTCMinutes()).padStart(2, \"0\");\n return `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]} ${date.getUTCFullYear()}, ${hh}:${mm} UTC`;\n}\n\nfunction daysLabel(seconds: number): string {\n const days = Math.round((seconds / 86_400) * 10) / 10;\n return `${days} ${days === 1 ? \"day\" : \"days\"}`;\n}\n\nfunction quoted(id: string, names: Record<string, string>): string {\n const name = names[id];\n return name ? `\"${name}\"` : id;\n}\n\nfunction cellWords(\n cell: Pick<AdStudyCell, \"name\" | \"treatment_percentage\" | \"control_percentage\" | \"adsets\" | \"campaigns\" | \"ads\">,\n names: Record<string, string>,\n): string {\n const [noun, ids] = cell.ads\n ? [\"ad\", cell.ads]\n : cell.adsets\n ? [\"ad set\", cell.adsets]\n : [\"campaign\", cell.campaigns ?? []];\n const plural = ids.length > 1 ? `${noun}s` : noun;\n const runs = ids.length > 0 ? ` (${plural} ${ids.map((id) => quoted(id, names)).join(\", \")})` : \"\";\n const share =\n cell.control_percentage === undefined\n ? `${cell.treatment_percentage}%`\n : `${cell.treatment_percentage}% see the ads, ${cell.control_percentage}% held out`;\n return `${cell.name}${runs} ${share}`;\n}\n\nconst STUDY_KIND: Record<string, string> = {\n SPLIT_TEST: \"Split test\",\n LIFT: \"Lift study\",\n SPLIT_TEST_V2: \"Creative test\",\n};\n\nfunction primaryObjective(objectives: readonly AdStudyObjective[] | undefined): AdStudyObjective | undefined {\n if (!objectives || objectives.length === 0) {\n return undefined;\n }\n return objectives.find((objective) => objective.is_primary === true) ?? objectives[0];\n}\n\nconst SOURCE_NOUN: Record<\"adspixels\" | \"applications\" | \"offsite_datasets\", string> = {\n adspixels: \"pixel\",\n applications: \"app\",\n offsite_datasets: \"dataset\",\n};\n\n/** \"Lead on pixel \"Site\"\", one per event source. */\nfunction sourceWords(objective: AdStudyObjective, names: Record<string, string>): string[] {\n const words: string[] = [];\n for (const key of [\"adspixels\", \"applications\", \"offsite_datasets\"] as const) {\n for (const source of objective[key] ?? []) {\n const events = source.event_names?.join(\" or \") ?? \"any event\";\n words.push(`${events} on ${SOURCE_NOUN[key]} ${quoted(source.id, names)}`);\n }\n }\n for (const source of objective.customconversions ?? []) {\n words.push(`custom conversion ${quoted(source.id, names)}`);\n }\n return words;\n}\n\n/** What decides the winner. Meta's split test compares cost per result of the primary measure. */\nfunction measuringWords(objectives: readonly AdStudyObjective[] | undefined, names: Record<string, string>): string {\n const primary = primaryObjective(objectives);\n if (!primary) {\n return \"cost per result on the campaigns' objective\";\n }\n const sources = sourceWords(primary, names);\n return `cost per \"${primary.name}\"${sources.length > 0 ? ` (${sources.join(\", \")})` : \"\"}`;\n}\n\n/** What the sentence says the study measures: a lift study reads the lift, a test the cost per result. */\nfunction measuringPhrase(type: string | undefined, primary: AdStudyObjective | undefined, measuring: string): string {\n if (type === \"LIFT\") return `measuring the lift in ${primary ? `\"${primary.name}\"` : \"results\"}`;\n return `measuring ${primary ? `cost per \"${primary.name}\"` : measuring}`;\n}\n\nexport function describeAdStudy(\n study: AdStudyWordsInput,\n { names = {} }: { names?: Record<string, string> } = {},\n): AdStudyWords {\n const versions =\n study.cells && study.cells.length > 0 ? study.cells.map((cell) => cellWords(cell, names)).join(\" vs \") : undefined;\n const measuring = measuringWords(study.objectives, names);\n const primary = primaryObjective(study.objectives);\n const others = (study.objectives ?? []).filter((objective) => objective !== primary);\n const alsoMeasured = others.length > 0 ? others.map((objective) => `\"${objective.name}\"`).join(\", \") : undefined;\n const hasSchedule = study.start_time !== undefined && study.end_time !== undefined;\n const length = hasSchedule ? daysLabel((study.end_time ?? 0) - (study.start_time ?? 0)) : undefined;\n const schedule = hasSchedule\n ? `${adStudyTime(study.start_time ?? 0)} → ${adStudyTime(study.end_time ?? 0)} · ${length}`\n : undefined;\n const confidence = study.confidence_level !== undefined ? `${Math.round(study.confidence_level * 100)}%` : undefined;\n const parts = [\n versions,\n measuringPhrase(study.type, primary, measuring),\n length,\n confidence ? `${confidence} confidence` : undefined,\n ].filter((part): part is string => part !== undefined);\n const kind = STUDY_KIND[study.type ?? \"SPLIT_TEST\"] ?? \"Split test\";\n return { sentence: `${kind}: ${parts.join(\", \")}`, versions, measuring, alsoMeasured, schedule, confidence };\n}\n","import { adStudyFromWire, cellObjects, unixSeconds } from \"@baker/api/ads-meta/adStudies\";\nimport { adStudyTime, describeAdStudy } from \"@baker/api/ads-meta/adStudyDescription\";\nimport { defineCommand } from \"citty\";\nimport { NO_CACHE_ARG } from \"../../../cache-flag.ts\";\nimport { apiGet } from \"../../../client.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { handleMetaError, resolveAccountIdArg } from \"./shared.ts\";\n\n/**\n * Meta A/B tests (ad studies) — reads. A test splits one audience between\n * versions (ad sets, campaigns, or a creative test's ads) so each person sees one, and Meta calls the\n * winner. Writes (create / update / end / delete) stage like every Meta change\n * and live in `study-writes.ts`.\n */\n\ntype Row = Record<string, unknown>;\n\nexport type StudyState = \"scheduled\" | \"running\" | \"ended\" | \"cancelled\";\n\nexport function studyState(row: Row, nowSeconds: number): StudyState {\n if (row.canceled_time) return \"cancelled\";\n const start = unixSeconds(row.start_time) ?? 0;\n const end = unixSeconds(row.end_time) ?? Number.POSITIVE_INFINITY;\n if (nowSeconds < start) return \"scheduled\";\n return nowSeconds < end ? \"running\" : \"ended\";\n}\n\n/** What an agent needs to pick a test and know what it compares. The raw node sits behind --full. */\nexport function compactStudy(row: Row, nowSeconds: number): Row {\n const study = adStudyFromWire(row);\n const words = describeAdStudy(study, { names: study.names });\n return {\n id: study.id,\n name: study.name,\n state: studyState(row, nowSeconds),\n compares: words.versions,\n ...(study.start_time !== undefined ? { starts: adStudyTime(study.start_time) } : {}),\n ...(study.end_time !== undefined ? { ends: adStudyTime(study.end_time) } : {}),\n ...(row.results_first_available_date ? { results_from: row.results_first_available_date } : {}),\n };\n}\n\n/** One test in full: each version with its cell id (what `studies update --split` names), each measure with Meta's results. */\nexport function studyDetail(row: Row, nowSeconds: number): Row {\n const study = adStudyFromWire(row);\n const measures = ((row.objectives as { data?: Row[] } | undefined)?.data ?? []).map((objective) => ({\n id: objective.id,\n name: objective.name,\n primary: objective.is_primary === true,\n ...(objective.results !== undefined ? { results: objective.results } : {}),\n }));\n return {\n ...compactStudy(row, nowSeconds),\n ...(study.description ? { description: study.description } : {}),\n versions: study.cells.map((cell) => ({\n cell_id: cell.id,\n name: cell.name,\n share: `${cell.treatment_percentage}%`,\n runs: cellObjects(cell).map((id) => ({ id, name: study.names[id] })),\n level: cell.ads ? \"ad\" : cell.adsets ? \"ad set\" : \"campaign\",\n })),\n measures,\n };\n}\n\nexport function studyHints(detail: Row): string[] {\n const state = detail.state as StudyState;\n if (state === \"scheduled\") {\n return [\n \"Not started: it can still be re-timed, re-split (`studies update --split <cell_id>=60 …`) or deleted. Results appear once it runs.\",\n ];\n }\n const measures = (detail.measures as Row[] | undefined) ?? [];\n const hasResults = measures.some((measure) => measure.results !== undefined);\n return [\n hasResults\n ? \"Meta's verdict is in each measure's results, with its confidence. Report a winner only when Meta names one at the test's confidence level; otherwise say the versions performed alike.\"\n : \"No results yet — Meta publishes them once the test has gathered enough. Compare per-version delivery meanwhile with `baker ads meta insights --object <ad set or campaign id>`, and do not call a winner from it.\",\n state === \"running\"\n ? \"Running: its split and start are fixed; it can be renamed, its end moved, ended now (`studies end`) or deleted (results and all).\"\n : \"Over. Read the per-version numbers with `baker ads meta insights --object <id>` for the period it ran.\",\n ];\n}\n\nregisterSchema({\n command: \"ads.meta.studies\",\n description:\n \"Meta A/B tests (ad studies) whose versions run in an ad account — what each compares, when it runs and its state (scheduled, running, ended). --study-id reads one: every version with its cell id and share, and each measure with Meta's results. --business-id lists every test on a Business. Stage new ones with `studies create`.\",\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n \"business-id\": { type: \"string\", description: \"List every test on this Business instead\", required: false },\n \"study-id\": { type: \"string\", description: \"One test: its versions and results\", required: false },\n full: { type: \"boolean\", description: \"The tests as Meta returns them\", required: false },\n \"no-cache\": { type: \"boolean\", description: \"Bypass the cache\", required: false },\n },\n});\n\nconst listArgs = {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"business-id\": { type: \"string\", description: \"List every test on this Business instead\" },\n \"study-id\": { type: \"string\", description: \"Read one test: versions, cell ids, results\" },\n full: { type: \"boolean\", description: \"The tests as Meta returns them\" },\n \"no-cache\": NO_CACHE_ARG,\n} as const;\n\nasync function read<T>(path: string, params: Record<string, string>, noCache: unknown): Promise<T> {\n if (noCache) params[\"skip-cache\"] = \"true\";\n return await apiGet<T>(path, params);\n}\n\nconst listCommand = defineCommand({\n meta: {\n name: \"list\",\n description:\n \"List A/B tests. Runs by default. Example: baker ads meta studies --account-id act_123 · one test: baker ads meta studies --study-id 6123\",\n },\n args: listArgs,\n run: async ({ args }) => {\n const now = Math.floor(Date.now() / 1000);\n try {\n if (args[\"study-id\"]) {\n const row = await read<Row>(\n \"/api/ads/meta/study-results\",\n { \"study-id\": String(args[\"study-id\"]) },\n args[\"no-cache\"],\n );\n const detail = studyDetail(row, now);\n writeJsonEnvelope({ ok: true, data: args.full ? row : detail, hints: studyHints(detail) });\n return;\n }\n const params: Record<string, string> = args[\"business-id\"]\n ? { \"business-id\": String(args[\"business-id\"]) }\n : { \"account-id\": resolveAccountIdArg(args) };\n const rows = await read<Row[]>(\"/api/ads/meta/studies\", params, args[\"no-cache\"]);\n const hints =\n rows.length === 0\n ? [\n \"No A/B tests. To set one up: duplicate the ad set (`adsets duplicate`), change ONE thing on the copy, then `studies create --adsets <original>,<copy>`.\",\n ]\n : [\"`studies --study-id <id>` reads one test's versions and results.\"];\n writeJsonEnvelope({ ok: true, data: args.full ? rows : rows.map((row) => compactStudy(row, now)), hints });\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\nexport const studiesCommand = defineCommand({\n meta: {\n name: \"studies\",\n description: `Meta A/B tests (ad studies): Meta splits one audience between versions — ad sets or campaigns — so each person sees one, and calls the winner.\nNot Baker's own page experiments (\\`baker experiment\\`), which test landing pages.\n\nExamples:\n baker ads meta studies --account-id act_123 # tests on this account and their state\n baker ads meta studies --study-id 6123 # versions, cell ids, Meta's results\n baker ads meta studies create --name \"Broad vs lookalike\" --adsets 6123,6124 --start 2026-10-20T08:00Z --days 14 --confidence 90\n baker ads meta studies update 6123 --end 2026-11-10T08:00Z\n baker ads meta studies end 6123 | delete 6123`,\n },\n subCommands: {\n list: listCommand,\n create: () => import(\"./study-writes.ts\").then((m) => m.studiesCreateCommand),\n update: () => import(\"./study-writes.ts\").then((m) => m.studiesUpdateCommand),\n end: () => import(\"./study-writes.ts\").then((m) => m.studiesEndCommand),\n delete: () => import(\"./study-writes.ts\").then((m) => m.studiesDeleteCommand),\n },\n args: listArgs,\n default: \"list\",\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../api/src/ads-meta/adStudyDescription.ts","../src/commands/ads/meta/studies.ts"],"sourcesContent":["/**\n * A Meta A/B test in words — the one reading shared by the staged summary, the\n * draft review rows and the CLI, so all three say the same thing: what each\n * version runs and how much of the audience it gets, what decides the winner,\n * for how long, and how sure Meta has to be.\n */\nimport type { AdStudyCell, AdStudyObjective } from \"./adStudies\";\n\nexport interface AdStudyWordsInput {\n type?: string;\n start_time?: number;\n end_time?: number;\n confidence_level?: number;\n cells?: readonly Pick<\n AdStudyCell,\n \"name\" | \"treatment_percentage\" | \"control_percentage\" | \"adsets\" | \"campaigns\" | \"ads\"\n >[];\n objectives?: readonly AdStudyObjective[];\n}\n\nexport interface AdStudyWords {\n sentence: string;\n versions?: string;\n measuring: string;\n /** The measures besides the primary one, by name. */\n alsoMeasured?: string;\n schedule?: string;\n confidence?: string;\n}\n\nconst MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\nexport function adStudyTime(seconds: number): string {\n const date = new Date(seconds * 1000);\n const hh = String(date.getUTCHours()).padStart(2, \"0\");\n const mm = String(date.getUTCMinutes()).padStart(2, \"0\");\n return `${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]} ${date.getUTCFullYear()}, ${hh}:${mm} UTC`;\n}\n\nfunction daysLabel(seconds: number): string {\n const days = Math.round((seconds / 86_400) * 10) / 10;\n return `${days} ${days === 1 ? \"day\" : \"days\"}`;\n}\n\nfunction quoted(id: string, names: Record<string, string>): string {\n const name = names[id];\n return name ? `\"${name}\"` : id;\n}\n\nfunction cellWords(\n cell: Pick<AdStudyCell, \"name\" | \"treatment_percentage\" | \"control_percentage\" | \"adsets\" | \"campaigns\" | \"ads\">,\n names: Record<string, string>,\n): string {\n const [noun, ids] = cell.ads\n ? [\"ad\", cell.ads]\n : cell.adsets\n ? [\"ad set\", cell.adsets]\n : [\"campaign\", cell.campaigns ?? []];\n const plural = ids.length > 1 ? `${noun}s` : noun;\n const runs = ids.length > 0 ? ` (${plural} ${ids.map((id) => quoted(id, names)).join(\", \")})` : \"\";\n const share =\n cell.control_percentage === undefined\n ? `${cell.treatment_percentage}%`\n : `${cell.treatment_percentage}% see the ads, ${cell.control_percentage}% held out`;\n return `${cell.name}${runs} ${share}`;\n}\n\nconst STUDY_KIND: Record<string, string> = {\n SPLIT_TEST: \"Split test\",\n LIFT: \"Lift study\",\n SPLIT_TEST_V2: \"Creative test\",\n};\n\nfunction primaryObjective(objectives: readonly AdStudyObjective[] | undefined): AdStudyObjective | undefined {\n if (!objectives || objectives.length === 0) {\n return undefined;\n }\n return objectives.find((objective) => objective.is_primary === true) ?? objectives[0];\n}\n\nconst SOURCE_NOUN: Record<\"adspixels\" | \"applications\" | \"offsite_datasets\", string> = {\n adspixels: \"pixel\",\n applications: \"app\",\n offsite_datasets: \"dataset\",\n};\n\n/** \"Lead on pixel \"Site\"\", one per event source. */\nfunction sourceWords(objective: AdStudyObjective, names: Record<string, string>): string[] {\n const words: string[] = [];\n for (const key of [\"adspixels\", \"applications\", \"offsite_datasets\"] as const) {\n for (const source of objective[key] ?? []) {\n const events = source.event_names?.join(\" or \") ?? \"any event\";\n words.push(`${events} on ${SOURCE_NOUN[key]} ${quoted(source.id, names)}`);\n }\n }\n for (const source of objective.customconversions ?? []) {\n words.push(`custom conversion ${quoted(source.id, names)}`);\n }\n return words;\n}\n\n/** What decides the winner. Meta's split test compares cost per result of the primary measure. */\nfunction measuringWords(objectives: readonly AdStudyObjective[] | undefined, names: Record<string, string>): string {\n const primary = primaryObjective(objectives);\n if (!primary) {\n return \"cost per result on the campaigns' objective\";\n }\n const sources = sourceWords(primary, names);\n return `cost per \"${primary.name}\"${sources.length > 0 ? ` (${sources.join(\", \")})` : \"\"}`;\n}\n\n/** What the sentence says the study measures: a lift study reads the lift, a test the cost per result. */\nfunction measuringPhrase(type: string | undefined, primary: AdStudyObjective | undefined, measuring: string): string {\n if (type === \"LIFT\") return `measuring the lift in ${primary ? `\"${primary.name}\"` : \"results\"}`;\n return `measuring ${primary ? `cost per \"${primary.name}\"` : measuring}`;\n}\n\nexport function describeAdStudy(\n study: AdStudyWordsInput,\n { names = {} }: { names?: Record<string, string> } = {},\n): AdStudyWords {\n const versions =\n study.cells && study.cells.length > 0 ? study.cells.map((cell) => cellWords(cell, names)).join(\" vs \") : undefined;\n const measuring = measuringWords(study.objectives, names);\n const primary = primaryObjective(study.objectives);\n const others = (study.objectives ?? []).filter((objective) => objective !== primary);\n const alsoMeasured = others.length > 0 ? others.map((objective) => `\"${objective.name}\"`).join(\", \") : undefined;\n const hasSchedule = study.start_time !== undefined && study.end_time !== undefined;\n const length = hasSchedule ? daysLabel((study.end_time ?? 0) - (study.start_time ?? 0)) : undefined;\n const schedule = hasSchedule\n ? `${adStudyTime(study.start_time ?? 0)} → ${adStudyTime(study.end_time ?? 0)} · ${length}`\n : undefined;\n const confidence = study.confidence_level !== undefined ? `${Math.round(study.confidence_level * 100)}%` : undefined;\n const parts = [\n versions,\n measuringPhrase(study.type, primary, measuring),\n length,\n confidence ? `${confidence} confidence` : undefined,\n ].filter((part): part is string => part !== undefined);\n const kind = STUDY_KIND[study.type ?? \"SPLIT_TEST\"] ?? \"Split test\";\n return { sentence: `${kind}: ${parts.join(\", \")}`, versions, measuring, alsoMeasured, schedule, confidence };\n}\n","import { adStudyFromWire, cellObjects, unixSeconds } from \"@baker/api/ads-meta/adStudies\";\nimport { adStudyTime, describeAdStudy } from \"@baker/api/ads-meta/adStudyDescription\";\nimport { defineCommand } from \"citty\";\nimport { NO_CACHE_ARG } from \"../../../cache-flag.ts\";\nimport { apiGet } from \"../../../client.ts\";\nimport { registerSchema } from \"../../../schemas.ts\";\nimport { writeJsonEnvelope } from \"../output.ts\";\nimport { handleMetaError, resolveAccountIdArg } from \"./shared.ts\";\n\n/**\n * Meta A/B tests (ad studies) — reads. A test splits one audience between\n * versions (ad sets, campaigns, or a creative test's ads) so each person sees one, and Meta calls the\n * winner. Writes (create / update / end / delete) stage like every Meta change\n * and live in `study-writes.ts`.\n */\n\ntype Row = Record<string, unknown>;\n\nexport type StudyState = \"scheduled\" | \"running\" | \"ended\" | \"cancelled\";\n\nexport function studyState(row: Row, nowSeconds: number): StudyState {\n if (row.canceled_time) return \"cancelled\";\n const start = unixSeconds(row.start_time) ?? 0;\n const end = unixSeconds(row.end_time) ?? Number.POSITIVE_INFINITY;\n if (nowSeconds < start) return \"scheduled\";\n return nowSeconds < end ? \"running\" : \"ended\";\n}\n\n/** What an agent needs to pick a test and know what it compares. The raw node sits behind --full. */\nexport function compactStudy(row: Row, nowSeconds: number): Row {\n const study = adStudyFromWire(row);\n const words = describeAdStudy(study, { names: study.names });\n return {\n id: study.id,\n name: study.name,\n state: studyState(row, nowSeconds),\n compares: words.versions,\n ...(study.start_time !== undefined ? { starts: adStudyTime(study.start_time) } : {}),\n ...(study.end_time !== undefined ? { ends: adStudyTime(study.end_time) } : {}),\n ...(row.results_first_available_date ? { results_from: row.results_first_available_date } : {}),\n };\n}\n\n/** One test in full: each version with its cell id (what `studies update --split` names), each measure with Meta's results. */\nexport function studyDetail(row: Row, nowSeconds: number): Row {\n const study = adStudyFromWire(row);\n const measures = ((row.objectives as { data?: Row[] } | undefined)?.data ?? []).map((objective) => ({\n id: objective.id,\n name: objective.name,\n primary: objective.is_primary === true,\n ...(objective.results !== undefined ? { results: objective.results } : {}),\n }));\n return {\n ...compactStudy(row, nowSeconds),\n ...(study.description ? { description: study.description } : {}),\n versions: study.cells.map((cell) => ({\n cell_id: cell.id,\n name: cell.name,\n share: `${cell.treatment_percentage}%`,\n runs: cellObjects(cell).map((id) => ({ id, name: study.names[id] })),\n level: cell.ads ? \"ad\" : cell.adsets ? \"ad set\" : \"campaign\",\n })),\n measures,\n };\n}\n\nexport function studyHints(detail: Row): string[] {\n const state = detail.state as StudyState;\n if (state === \"scheduled\") {\n return [\n \"Not started: it can still be re-timed, re-split (`studies update --split <cell_id>=60 …`) or deleted. Results appear once it runs.\",\n ];\n }\n const measures = (detail.measures as Row[] | undefined) ?? [];\n const hasResults = measures.some((measure) => measure.results !== undefined);\n return [\n hasResults\n ? \"Meta's verdict is in each measure's results, with its confidence. Report a winner only when Meta names one at the test's confidence level; otherwise say the versions performed alike.\"\n : \"No results yet — Meta publishes them once the test has gathered enough. Compare per-version delivery meanwhile with `baker ads meta insights --object <ad set or campaign id>`, and do not call a winner from it.\",\n state === \"running\"\n ? \"Running: its split and start are fixed; it can be renamed, its end moved, ended now (`studies end`) or deleted (results and all).\"\n : \"Over. Read the per-version numbers with `baker ads meta insights --object <id>` for the period it ran.\",\n ];\n}\n\nregisterSchema({\n command: \"ads.meta.studies\",\n description:\n \"Meta A/B tests (ad studies) whose versions run in an ad account — what each compares, when it runs and its state (scheduled, running, ended). --study-id reads one: every version with its cell id and share, and each measure with Meta's results. --business-id lists every test on a Business. Stage new ones with `studies create`.\",\n args: {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID (act_123)\", required: false },\n \"business-id\": { type: \"string\", description: \"List every test on this Business instead\", required: false },\n \"study-id\": { type: \"string\", description: \"One test: its versions and results\", required: false },\n full: { type: \"boolean\", description: \"The tests as Meta returns them\", required: false },\n \"no-cache\": { type: \"boolean\", description: \"Bypass the cache\", required: false },\n },\n});\n\nconst listArgs = {\n \"account-id\": { type: \"string\", description: \"Meta ad account ID\" },\n \"business-id\": { type: \"string\", description: \"List every test on this Business instead\" },\n \"study-id\": { type: \"string\", description: \"Read one test: versions, cell ids, results\" },\n full: { type: \"boolean\", description: \"The tests as Meta returns them\" },\n \"no-cache\": NO_CACHE_ARG,\n} as const;\n\nasync function read<T>(path: string, params: Record<string, string>, noCache: unknown): Promise<T> {\n if (noCache) params[\"skip-cache\"] = \"true\";\n return await apiGet<T>(path, params);\n}\n\nconst listCommand = defineCommand({\n meta: {\n name: \"list\",\n description:\n \"List A/B tests. Runs by default. Example: baker ads meta studies --account-id act_123 · one test: baker ads meta studies --study-id 6123\",\n },\n args: listArgs,\n run: async ({ args }) => {\n const now = Math.floor(Date.now() / 1000);\n try {\n if (args[\"study-id\"]) {\n const row = await read<Row>(\n \"/api/ads/meta/study-results\",\n { \"study-id\": String(args[\"study-id\"]) },\n args[\"no-cache\"],\n );\n const detail = studyDetail(row, now);\n writeJsonEnvelope({ ok: true, data: args.full ? row : detail, hints: studyHints(detail) });\n return;\n }\n const params: Record<string, string> = args[\"business-id\"]\n ? { \"business-id\": String(args[\"business-id\"]) }\n : { \"account-id\": await resolveAccountIdArg(args) };\n const rows = await read<Row[]>(\"/api/ads/meta/studies\", params, args[\"no-cache\"]);\n const hints =\n rows.length === 0\n ? [\n \"No A/B tests. To set one up: duplicate the ad set (`adsets duplicate`), change ONE thing on the copy, then `studies create --adsets <original>,<copy>`.\",\n ]\n : [\"`studies --study-id <id>` reads one test's versions and results.\"];\n writeJsonEnvelope({ ok: true, data: args.full ? rows : rows.map((row) => compactStudy(row, now)), hints });\n } catch (err) {\n handleMetaError(err);\n }\n },\n});\n\nexport const studiesCommand = defineCommand({\n meta: {\n name: \"studies\",\n description: `Meta A/B tests (ad studies): Meta splits one audience between versions — ad sets or campaigns — so each person sees one, and calls the winner.\nNot Baker's own page experiments (\\`baker experiment\\`), which test landing pages.\n\nExamples:\n baker ads meta studies --account-id act_123 # tests on this account and their state\n baker ads meta studies --study-id 6123 # versions, cell ids, Meta's results\n baker ads meta studies create --name \"Broad vs lookalike\" --adsets 6123,6124 --start 2026-10-20T08:00Z --days 14 --confidence 90\n baker ads meta studies update 6123 --end 2026-11-10T08:00Z\n baker ads meta studies end 6123 | delete 6123`,\n },\n subCommands: {\n list: listCommand,\n create: () => import(\"./study-writes.ts\").then((m) => m.studiesCreateCommand),\n update: () => import(\"./study-writes.ts\").then((m) => m.studiesUpdateCommand),\n end: () => import(\"./study-writes.ts\").then((m) => m.studiesEndCommand),\n delete: () => import(\"./study-writes.ts\").then((m) => m.studiesDeleteCommand),\n },\n args: listArgs,\n default: \"list\",\n});\n"],"mappings":"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","names":[]}
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
loadJsonFileArg,
|
|
5
5
|
requireTarget,
|
|
6
6
|
stageOp
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4XXA4WCS.js";
|
|
8
8
|
import "./chunk-GC5P4KWY.js";
|
|
9
9
|
import "./chunk-GDFJOCDO.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-BN4TDL5L.js";
|
|
11
11
|
import {
|
|
12
12
|
repeatedValues
|
|
13
13
|
} from "./chunk-ERSX3MKH.js";
|
|
@@ -371,7 +371,7 @@ Examples:
|
|
|
371
371
|
},
|
|
372
372
|
args: createArgs,
|
|
373
373
|
run: async ({ args, rawArgs }) => {
|
|
374
|
-
const accountId = bareAccountId(args);
|
|
374
|
+
const accountId = await bareAccountId(args);
|
|
375
375
|
const file = loadJsonFileArg(args.file);
|
|
376
376
|
const ignored = Object.keys(file).length > 0 ? studyFileIgnoredFlags(args) : [];
|
|
377
377
|
if (ignored.length > 0) {
|
|
@@ -412,7 +412,7 @@ Example: baker ads meta studies update 6123 --end 2026-11-10T08:00Z`
|
|
|
412
412
|
},
|
|
413
413
|
args: updateArgs,
|
|
414
414
|
run: async ({ args, rawArgs }) => {
|
|
415
|
-
const accountId = bareAccountId(args);
|
|
415
|
+
const accountId = await bareAccountId(args);
|
|
416
416
|
const target = requireTarget(args, "test");
|
|
417
417
|
const payload = orFail(() => studyUpdateFromFlags(studyFlagsFromArgs(args, rawArgs)));
|
|
418
418
|
if (Object.keys(payload).length === 0) {
|
|
@@ -448,7 +448,7 @@ function actionCommand(name) {
|
|
|
448
448
|
"account-id": createArgs["account-id"]
|
|
449
449
|
},
|
|
450
450
|
run: async ({ args }) => {
|
|
451
|
-
const accountId = bareAccountId(args);
|
|
451
|
+
const accountId = await bareAccountId(args);
|
|
452
452
|
await stageOp({
|
|
453
453
|
kind: "adStudy.update",
|
|
454
454
|
accountId,
|
|
@@ -467,4 +467,4 @@ export {
|
|
|
467
467
|
studiesUpdateCommand,
|
|
468
468
|
studyFlagsFromArgs
|
|
469
469
|
};
|
|
470
|
-
//# sourceMappingURL=study-writes-
|
|
470
|
+
//# sourceMappingURL=study-writes-Z5P2W7JH.js.map
|