@thinkingai/ae-cli 6.1.11 → 6.1.12

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.
Files changed (27) hide show
  1. package/dist/{capability-TPORIKRQ.js → capability-DRLGDVS4.js} +5 -2
  2. package/dist/{capability-VQNC5CF7.js → capability-P6GK3AQH.js} +5 -2
  3. package/dist/index.js +3 -3
  4. package/dist/{te-engage-FMYAYCNV.js → te-engage-L72HWRGO.js} +396 -26
  5. package/dist/{te-engage-F7V55KVW.js → te-engage-QWM4GFS7.js} +396 -26
  6. package/dist/{te-experiment-UPDMHCAJ.js → te-experiment-2T2HEZML.js} +109 -3
  7. package/dist/{te-experiment-JWXOYJ3W.js → te-experiment-PVEY7AEZ.js} +109 -3
  8. package/package.json +2 -2
  9. package/skills/ae-engage/SKILL.md +12 -2
  10. package/skills/ae-engage/references/activity-data-detail.md +61 -0
  11. package/skills/ae-engage/references/build-task-save-guide.md +25 -1
  12. package/skills/ae-engage/references/common-metric.md +45 -0
  13. package/skills/ae-engage/references/preset-event.md +2 -0
  14. package/skills/ae-engage/references/save-flow.md +14 -0
  15. package/skills/ae-engage/references/save-task.md +35 -0
  16. package/skills/ae-engage/references/scene-strategy-audience.md +12 -7
  17. package/skills/ae-experiment/SKILL.md +17 -4
  18. package/skills/ae-experiment/references/delete_metric.md +2 -0
  19. package/skills/ae-experiment/references/query_experiment_metric_trend.md +5 -5
  20. package/skills/ae-experiment/references/query_experiment_report_summary.md +4 -3
  21. package/skills/ae-experiment/references/query_experiment_sample_size_report.md +6 -5
  22. package/skills/ae-experiment/references/save_build_guide.md +39 -0
  23. package/skills/ae-experiment/references/save_experiment.md +7 -0
  24. package/skills/ae-experiment/references/save_metric.md +5 -1
  25. package/skills/ae-experiment/references/save_validate.md +33 -0
  26. package/skills/ae-experiment-design/references/platform-operations.md +9 -3
  27. package/skills/ae-experiment-insight/references/platform-operations.md +9 -3
@@ -209,6 +209,362 @@ var flowModifyBaseInfo = createEngageFlowCapabilityCommand({
209
209
  })
210
210
  });
211
211
 
212
+ // src/commands/te-engage/semantic-qp-validation.ts
213
+ var OPERATORS = /* @__PURE__ */ new Set([
214
+ "eq",
215
+ "neq",
216
+ "lt",
217
+ "lte",
218
+ "gt",
219
+ "gte",
220
+ "exists",
221
+ "not_exists",
222
+ "between",
223
+ "contains",
224
+ "not_contains",
225
+ "is_true",
226
+ "is_false",
227
+ "regex",
228
+ "not_regex",
229
+ "relative_current_time",
230
+ "relative_event_time",
231
+ "array_contains",
232
+ "in_cluster",
233
+ "not_in_cluster"
234
+ ]);
235
+ var AUDIENCE_AGGREGATIONS = /* @__PURE__ */ new Set([
236
+ "count",
237
+ "active_days",
238
+ "sum",
239
+ "avg",
240
+ "max",
241
+ "min",
242
+ "distinct_count"
243
+ ]);
244
+ var METRIC_AGGREGATIONS = /* @__PURE__ */ new Set([
245
+ "total_count",
246
+ "user_count",
247
+ "per_user_count",
248
+ "sum",
249
+ "avg",
250
+ "avg_per_user",
251
+ "max",
252
+ "min",
253
+ "distinct_count",
254
+ "median",
255
+ "percentile",
256
+ "variance",
257
+ "stddev"
258
+ ]);
259
+ function validateSemanticAudienceDefinition(value, path) {
260
+ const definition = object(value, path);
261
+ rejectUnknown(definition, ["type", "conditions", "include_filter", "exclude_filter"], path);
262
+ exactString(definition.type, "condition", `${path}.type`);
263
+ validateGroup(definition.conditions, `${path}.conditions`);
264
+ optional(definition.include_filter, (item) => validateGroup(item, `${path}.include_filter`));
265
+ optional(definition.exclude_filter, (item) => validateGroup(item, `${path}.exclude_filter`));
266
+ }
267
+ function validateSemanticEventDefinition(value, path) {
268
+ const event = object(value, path);
269
+ rejectUnknown(event, [
270
+ "type",
271
+ "event",
272
+ "operator",
273
+ "value",
274
+ "aggregation",
275
+ "property",
276
+ "time_range",
277
+ "filters"
278
+ ], path);
279
+ exactString(event.type, "event", `${path}.type`);
280
+ nonBlankString(event.event, `${path}.event`);
281
+ optionalEnum(event.operator, OPERATORS, `${path}.operator`);
282
+ optionalNumber(event.value, `${path}.value`);
283
+ optionalEnum(event.aggregation, AUDIENCE_AGGREGATIONS, `${path}.aggregation`);
284
+ optionalString(event.property, `${path}.property`);
285
+ optional(event.time_range, (item) => validateTimeRange(item, `${path}.time_range`));
286
+ optional(event.filters, (item) => validateFilterGroup(item, `${path}.filters`));
287
+ }
288
+ function validateSemanticMetricDefinition(value, path) {
289
+ const metric = object(value, path);
290
+ if (metric.type === "event") {
291
+ validateMetricEvent(metric, path, false);
292
+ return;
293
+ }
294
+ if (metric.type === "formula") {
295
+ rejectUnknown(metric, ["type", "expression", "dependencies", "format"], path);
296
+ nonBlankString(metric.expression, `${path}.expression`);
297
+ optionalString(metric.format, `${path}.format`);
298
+ array(metric.dependencies, `${path}.dependencies`, 1).forEach((dependency, index) => validateMetricEvent(
299
+ object(dependency, `${path}.dependencies[${index}]`),
300
+ `${path}.dependencies[${index}]`,
301
+ true
302
+ ));
303
+ return;
304
+ }
305
+ invalid(`${path}.type must be event or formula.`);
306
+ }
307
+ function validateEmbeddedSemanticDefinitions(value, path) {
308
+ walk(value, path);
309
+ }
310
+ function walk(value, path) {
311
+ if (Array.isArray(value)) {
312
+ value.forEach((item, index) => walk(item, `${path}[${index}]`));
313
+ return;
314
+ }
315
+ if (!isObject(value)) return;
316
+ for (const [key, item] of Object.entries(value)) {
317
+ const itemPath = `${path}.${key}`;
318
+ if (item === void 0 || item === null) continue;
319
+ if (["definitionRequest", "targetDefinitionRequest", "topicDefinitionRequest"].includes(key)) {
320
+ validateSemanticAudienceDefinition(item, itemPath);
321
+ } else if (key === "eventDefinition") {
322
+ validateSemanticEventDefinition(item, itemPath);
323
+ } else if (key === "triggerDefinition") {
324
+ validateTriggerDefinition(item, itemPath);
325
+ } else if (key === "config" && typeof item === "string") {
326
+ walkJsonString(item, itemPath);
327
+ } else {
328
+ walk(item, itemPath);
329
+ }
330
+ }
331
+ }
332
+ function validateTriggerDefinition(value, path) {
333
+ const definition = object(value, path);
334
+ rejectUnknown(definition, ["rules"], path);
335
+ array(definition.rules, `${path}.rules`, 1).forEach((ruleValue, ruleIndex) => {
336
+ const rulePath = `${path}.rules[${ruleIndex}]`;
337
+ const rule = object(ruleValue, rulePath);
338
+ array(rule.events, `${rulePath}.events`, 1).forEach((event, eventIndex) => {
339
+ const eventPath = `${rulePath}.events[${eventIndex}]`;
340
+ const candidate = object(event, eventPath);
341
+ if ("eventDefinition" in candidate) {
342
+ validateSemanticEventDefinition(candidate.eventDefinition, `${eventPath}.eventDefinition`);
343
+ } else {
344
+ validateSemanticEventDefinition(candidate, eventPath);
345
+ }
346
+ });
347
+ optional(rule.blackList, (items) => array(items, `${rulePath}.blackList`).forEach((event, index) => validateSemanticEventDefinition(
348
+ event,
349
+ `${rulePath}.blackList[${index}]`
350
+ )));
351
+ });
352
+ }
353
+ function validateGroup(value, path) {
354
+ const group = object(value, path);
355
+ rejectUnknown(group, ["relation", "items"], path);
356
+ enumValue(group.relation, /* @__PURE__ */ new Set(["and", "or"]), `${path}.relation`);
357
+ array(group.items, `${path}.items`, 1).forEach((item, index) => validateCondition(item, `${path}.items[${index}]`));
358
+ }
359
+ function validateCondition(value, path) {
360
+ const condition = object(value, path);
361
+ switch (condition.type) {
362
+ case "event":
363
+ validateSemanticEventDefinition(condition, path);
364
+ required(condition.time_range, `${path}.time_range`);
365
+ return;
366
+ case "user":
367
+ case "tag":
368
+ case "cluster":
369
+ validatePropertyCondition(condition, path);
370
+ return;
371
+ case "compound":
372
+ rejectUnknown(condition, ["type", "group"], path);
373
+ validateGroup(condition.group, `${path}.group`);
374
+ return;
375
+ case "behavior_sequence":
376
+ validateBehaviorSequence(condition, path);
377
+ return;
378
+ default:
379
+ invalid(`${path}.type is unsupported.`);
380
+ }
381
+ }
382
+ function validatePropertyCondition(condition, path) {
383
+ const extended = condition.type === "tag" || condition.type === "cluster";
384
+ rejectUnknown(condition, [
385
+ "type",
386
+ "field",
387
+ "operator",
388
+ "values",
389
+ "time_relative",
390
+ "time_unit",
391
+ ...extended ? ["cluster_date_policy", "specified_cluster_date"] : []
392
+ ], path);
393
+ validateField(condition.field, `${path}.field`);
394
+ if (condition.type === "user") enumValue(condition.operator, OPERATORS, `${path}.operator`);
395
+ else optionalEnum(condition.operator, OPERATORS, `${path}.operator`);
396
+ optional(condition.values, (item) => array(item, `${path}.values`));
397
+ optionalString(condition.time_relative, `${path}.time_relative`);
398
+ optionalString(condition.time_unit, `${path}.time_unit`);
399
+ optionalString(condition.cluster_date_policy, `${path}.cluster_date_policy`);
400
+ optionalString(condition.specified_cluster_date, `${path}.specified_cluster_date`);
401
+ }
402
+ function validateBehaviorSequence(condition, path) {
403
+ rejectUnknown(condition, ["type", "completed", "steps", "time_range", "window"], path);
404
+ if (typeof condition.completed !== "boolean") invalid(`${path}.completed must be a boolean.`);
405
+ array(condition.steps, `${path}.steps`, 1).forEach((stepValue, index) => {
406
+ const stepPath = `${path}.steps[${index}]`;
407
+ const step = object(stepValue, stepPath);
408
+ rejectUnknown(step, ["event", "completed", "filters", "relative_to_first", "window"], stepPath);
409
+ nonBlankString(step.event, `${stepPath}.event`);
410
+ optionalBoolean(step.completed, `${stepPath}.completed`);
411
+ optionalBoolean(step.relative_to_first, `${stepPath}.relative_to_first`);
412
+ if (index === 1 && step.relative_to_first === true) {
413
+ invalid(`${stepPath}.relative_to_first must be false or omitted for the second step.`);
414
+ }
415
+ optional(step.filters, (item) => validateFilterGroup(item, `${stepPath}.filters`));
416
+ optional(step.window, (item) => validateWindow(item, `${stepPath}.window`));
417
+ });
418
+ required(condition.time_range, `${path}.time_range`);
419
+ validateTimeRange(condition.time_range, `${path}.time_range`);
420
+ optional(condition.window, (item) => validateWindow(item, `${path}.window`));
421
+ }
422
+ function validateFilterGroup(value, path) {
423
+ const group = object(value, path);
424
+ rejectUnknown(group, ["relation", "items"], path);
425
+ optionalEnum(group.relation, /* @__PURE__ */ new Set(["and", "or"]), `${path}.relation`);
426
+ array(group.items, `${path}.items`, 1).forEach((filterValue, index) => {
427
+ const filterPath = `${path}.items[${index}]`;
428
+ const filter = object(filterValue, filterPath);
429
+ rejectUnknown(filter, ["field", "operator", "values"], filterPath);
430
+ validateField(filter.field, `${filterPath}.field`);
431
+ enumValue(filter.operator, OPERATORS, `${filterPath}.operator`);
432
+ optional(filter.values, (item) => array(item, `${filterPath}.values`));
433
+ });
434
+ }
435
+ function validateField(value, path) {
436
+ if (typeof value === "string") {
437
+ nonBlankString(value, path);
438
+ return;
439
+ }
440
+ const field = object(value, path);
441
+ rejectUnknown(field, ["name", "type"], path);
442
+ nonBlankString(field.name, `${path}.name`);
443
+ optionalEnum(field.type, /* @__PURE__ */ new Set([
444
+ "event_property",
445
+ "user_property",
446
+ "tag",
447
+ "cluster"
448
+ ]), `${path}.type`);
449
+ }
450
+ function validateTimeRange(value, path) {
451
+ const range = object(value, path);
452
+ rejectUnknown(range, ["mode", "unit", "value", "start_time", "end_time"], path);
453
+ if (range.mode === "custom") {
454
+ nonBlankString(range.start_time, `${path}.start_time`);
455
+ nonBlankString(range.end_time, `${path}.end_time`);
456
+ return;
457
+ }
458
+ enumValue(range.mode, /* @__PURE__ */ new Set(["recent", "previous"]), `${path}.mode`);
459
+ optional(range.unit, (unit) => exactString(unit, "day", `${path}.unit`));
460
+ positiveInteger(range.value, `${path}.value`);
461
+ }
462
+ function validateWindow(value, path) {
463
+ const window = object(value, path);
464
+ rejectUnknown(window, ["value", "unit"], path);
465
+ positiveInteger(window.value, `${path}.value`);
466
+ nonBlankString(window.unit, `${path}.unit`);
467
+ }
468
+ function validateMetricEvent(metric, path, dependency) {
469
+ rejectUnknown(metric, [
470
+ "type",
471
+ ...dependency ? ["key"] : [],
472
+ "event",
473
+ "aggregation",
474
+ "property",
475
+ "percentile",
476
+ "filters",
477
+ "display_name"
478
+ ], path);
479
+ exactString(metric.type, "event", `${path}.type`);
480
+ if (dependency) nonBlankString(metric.key, `${path}.key`);
481
+ nonBlankString(metric.event, `${path}.event`);
482
+ enumValue(metric.aggregation, METRIC_AGGREGATIONS, `${path}.aggregation`);
483
+ if (!["total_count", "user_count", "per_user_count"].includes(String(metric.aggregation))) {
484
+ nonBlankString(metric.property, `${path}.property`);
485
+ }
486
+ optionalString(metric.property, `${path}.property`);
487
+ if (metric.aggregation === "percentile") {
488
+ optionalNumber(metric.percentile, `${path}.percentile`);
489
+ if (typeof metric.percentile !== "number" || metric.percentile <= 0 || metric.percentile > 100) {
490
+ invalid(`${path}.percentile must be greater than 0 and at most 100.`);
491
+ }
492
+ } else if (metric.percentile !== void 0 && metric.percentile !== null) {
493
+ invalid(`${path}.percentile is only valid for percentile aggregation.`);
494
+ }
495
+ optionalString(metric.display_name, `${path}.display_name`);
496
+ optional(metric.filters, (item) => validateFilterGroup(item, `${path}.filters`));
497
+ }
498
+ function walkJsonString(value, path) {
499
+ try {
500
+ walk(JSON.parse(value), path);
501
+ } catch (error) {
502
+ if (error instanceof SyntaxError) invalid(`${path} must contain valid JSON.`);
503
+ throw error;
504
+ }
505
+ }
506
+ function object(value, path) {
507
+ if (!isObject(value)) invalid(`${path} must be a JSON object.`);
508
+ return value;
509
+ }
510
+ function isObject(value) {
511
+ return value !== null && typeof value === "object" && !Array.isArray(value);
512
+ }
513
+ function array(value, path, minLength = 0) {
514
+ if (!Array.isArray(value) || value.length < minLength) {
515
+ invalid(`${path} must be an array with at least ${minLength} item(s).`);
516
+ }
517
+ return value;
518
+ }
519
+ function rejectUnknown(value, allowed, path) {
520
+ const supported = new Set(allowed);
521
+ const unknown = Object.keys(value).find((key) => !supported.has(key));
522
+ if (unknown) invalid(`Unsupported field ${path}.${unknown}.`);
523
+ }
524
+ function optional(value, validate) {
525
+ if (value !== void 0 && value !== null) validate(value);
526
+ }
527
+ function required(value, path) {
528
+ if (value === void 0 || value === null) invalid(`${path} is required.`);
529
+ }
530
+ function nonBlankString(value, path) {
531
+ if (typeof value !== "string" || value.trim() === "") {
532
+ invalid(`${path} must be a non-blank string.`);
533
+ }
534
+ }
535
+ function optionalString(value, path) {
536
+ optional(value, (item) => nonBlankString(item, path));
537
+ }
538
+ function exactString(value, expected, path) {
539
+ if (value !== expected) invalid(`${path} must be ${expected}.`);
540
+ }
541
+ function enumValue(value, allowed, path) {
542
+ if (typeof value !== "string" || !allowed.has(value)) {
543
+ invalid(`${path} must be one of: ${[...allowed].join(", ")}.`);
544
+ }
545
+ }
546
+ function optionalEnum(value, allowed, path) {
547
+ optional(value, (item) => enumValue(item, allowed, path));
548
+ }
549
+ function optionalNumber(value, path) {
550
+ optional(value, (item) => {
551
+ if (typeof item !== "number" || !Number.isFinite(item)) invalid(`${path} must be a number.`);
552
+ });
553
+ }
554
+ function positiveInteger(value, path) {
555
+ if (!Number.isInteger(value) || value < 1) {
556
+ invalid(`${path} must be a positive integer.`);
557
+ }
558
+ }
559
+ function optionalBoolean(value, path) {
560
+ optional(value, (item) => {
561
+ if (typeof item !== "boolean") invalid(`${path} must be a boolean.`);
562
+ });
563
+ }
564
+ function invalid(message) {
565
+ throw new CliValidationError(message);
566
+ }
567
+
212
568
  // src/commands/te-engage/engage-flow/flow/save.ts
213
569
  var allowedOperations = ["build", "preview", "commit"];
214
570
  function readRequest(ctx) {
@@ -223,6 +579,7 @@ function readRequest(ctx) {
223
579
  if ("sourceFlowUuid" in req) {
224
580
  throw new Error("Flag --req.sourceFlowUuid is unsupported; get the source flow and build nodes/edges instead");
225
581
  }
582
+ validateEmbeddedSemanticDefinitions(req, "--req");
226
583
  return req;
227
584
  }
228
585
  var flowSave = createEngageFlowCapabilityCommand({
@@ -1042,6 +1399,12 @@ var presetEventUpdate = createEngageSettingCapabilityCommand({
1042
1399
  { name: "recharge-event-definition", type: "json", required: false, desc: "Semantic recharge event definition." }
1043
1400
  ],
1044
1401
  risk: "write",
1402
+ validate: (ctx) => {
1403
+ for (const name of ["add-event-definition", "active-event-definition", "recharge-event-definition"]) {
1404
+ const definition = ctx.json(name);
1405
+ if (definition !== void 0) validateSemanticEventDefinition(definition, `--${name}`);
1406
+ }
1407
+ },
1045
1408
  buildInput: (ctx) => ({
1046
1409
  project_id: ctx.num("project-id"),
1047
1410
  add_event_definition: ctx.json("add-event-definition") || void 0,
@@ -1085,23 +1448,7 @@ var commonMetricGet = createEngageSettingCapabilityCommand({
1085
1448
  // src/commands/te-engage/engage-setting/common-metric/metric-qp-validation.ts
1086
1449
  var VALID_WINDOW_TIME_UNITS = /* @__PURE__ */ new Set(["minute", "hour", "day"]);
1087
1450
  function validateMetricDefinitionFlag(definition) {
1088
- if (definition === null || typeof definition !== "object" || Array.isArray(definition)) {
1089
- printError(
1090
- "validation",
1091
- "--metric-definition must be a JSON object.",
1092
- "Pass a semantic event or formula metric definition."
1093
- );
1094
- process.exit(1);
1095
- }
1096
- const type = definition.type;
1097
- if (type !== "event" && type !== "formula") {
1098
- printError(
1099
- "validation",
1100
- "--metric-definition.type must be event or formula.",
1101
- "Do not pass legacy type=0/1 metric_qp."
1102
- );
1103
- process.exit(1);
1104
- }
1451
+ validateSemanticMetricDefinition(definition, "--metric-definition");
1105
1452
  }
1106
1453
  function validateMetricWindowTimeUnitFlag(unit) {
1107
1454
  if (VALID_WINDOW_TIME_UNITS.has(unit)) {
@@ -1587,6 +1934,16 @@ var presetMetricSet = createEngageSceneCapabilityCommand({
1587
1934
  { name: "attend-event-definition", type: "json", required: false, desc: "Semantic attend event definition." }
1588
1935
  ],
1589
1936
  risk: "write",
1937
+ validate: (ctx) => {
1938
+ for (const name of [
1939
+ "impression-event-definition",
1940
+ "click-event-definition",
1941
+ "attend-event-definition"
1942
+ ]) {
1943
+ const definition = ctx.json(name);
1944
+ if (definition !== void 0) validateSemanticEventDefinition(definition, `--${name}`);
1945
+ }
1946
+ },
1590
1947
  buildInput: (ctx) => ({
1591
1948
  project_id: ctx.num("project-id"),
1592
1949
  config_id: ctx.str("config-id"),
@@ -1865,9 +2222,13 @@ var strategyCreate = createEngageSceneCapabilityCommand({
1865
2222
  }
1866
2223
  ],
1867
2224
  risk: "write",
2225
+ validate: (ctx) => {
2226
+ const payload = readRequiredJsonObject(ctx, "payload");
2227
+ validateEmbeddedSemanticDefinitions(payload, "--payload");
2228
+ },
1868
2229
  buildInput: (ctx) => ({
1869
2230
  project_id: ctx.num("project-id"),
1870
- payload: ctx.json("payload")
2231
+ payload: readRequiredJsonObject(ctx, "payload")
1871
2232
  })
1872
2233
  });
1873
2234
 
@@ -1964,9 +2325,13 @@ var strategyUpdate = createEngageSceneCapabilityCommand({
1964
2325
  }
1965
2326
  ],
1966
2327
  risk: "write",
2328
+ validate: (ctx) => {
2329
+ const payload = readRequiredJsonObject(ctx, "payload");
2330
+ validateEmbeddedSemanticDefinitions(payload, "--payload");
2331
+ },
1967
2332
  buildInput: (ctx) => ({
1968
2333
  project_id: ctx.num("project-id"),
1969
- payload: ctx.json("payload")
2334
+ payload: readRequiredJsonObject(ctx, "payload")
1970
2335
  })
1971
2336
  });
1972
2337
 
@@ -2022,7 +2387,10 @@ var strategyPredict = createEngageSceneCapabilityCommand({
2022
2387
  ],
2023
2388
  risk: "read",
2024
2389
  validate: (ctx) => {
2025
- readRequiredJsonObject(ctx, "definition-request");
2390
+ validateSemanticAudienceDefinition(
2391
+ readRequiredJsonObject(ctx, "definition-request"),
2392
+ "--definition-request"
2393
+ );
2026
2394
  },
2027
2395
  buildInput: (ctx) => ({
2028
2396
  project_id: ctx.num("project-id"),
@@ -2513,6 +2881,7 @@ var TOPIC_TASK_EXCLUSION_FIELDS = /* @__PURE__ */ new Set([
2513
2881
  ]);
2514
2882
  function validateStandaloneActivityPayload(value) {
2515
2883
  const payload = requireObject(value);
2884
+ validateEmbeddedSemanticDefinitions(payload, "payload");
2516
2885
  validateTriggerType(payload.triggerType, "triggerType");
2517
2886
  validateScheduleFields(payload);
2518
2887
  validateExperiment(payload.expConfig, "expConfig");
@@ -2536,6 +2905,7 @@ function validateStandaloneActivityPayload(value) {
2536
2905
  }
2537
2906
  function validateActivityTopicPayload(value) {
2538
2907
  const payload = requireObject(value);
2908
+ validateEmbeddedSemanticDefinitions(payload, "payload");
2539
2909
  validateTriggerType(payload.triggerType, "triggerType");
2540
2910
  validateScheduleFields(payload);
2541
2911
  validateExperiment(payload.expConfig, "expConfig");
@@ -2610,7 +2980,7 @@ function validateScheduleFields(payload) {
2610
2980
  }
2611
2981
  function validateExperiment(value, field) {
2612
2982
  if (value === void 0 || value === null) return;
2613
- if (!isObject(value)) {
2983
+ if (!isObject2(value)) {
2614
2984
  fail(
2615
2985
  `${field} must be omitted or set to {"enableExp":false}.`,
2616
2986
  "ACTIVITY_EXPERIMENT_UNSUPPORTED",
@@ -2641,7 +3011,7 @@ function validateSingleContentGroup(value, field) {
2641
3011
  }
2642
3012
  }
2643
3013
  function requireObject(value, field = "payload") {
2644
- if (!isObject(value)) {
3014
+ if (!isObject2(value)) {
2645
3015
  fail(
2646
3016
  `${field} must be a JSON object.`,
2647
3017
  "ACTIVITY_PAYLOAD_INVALID",
@@ -2653,7 +3023,7 @@ function requireObject(value, field = "payload") {
2653
3023
  }
2654
3024
  function containsAnyKey(value, keys) {
2655
3025
  if (Array.isArray(value)) return value.some((item) => containsAnyKey(item, keys));
2656
- if (!isObject(value)) return false;
3026
+ if (!isObject2(value)) return false;
2657
3027
  return Object.entries(value).some(
2658
3028
  ([key, item]) => keys.has(key) && hasValue(item) || containsAnyKey(item, keys)
2659
3029
  );
@@ -2662,10 +3032,10 @@ function hasValue(value) {
2662
3032
  if (value === void 0 || value === null) return false;
2663
3033
  if (typeof value === "string") return value.trim().length > 0;
2664
3034
  if (Array.isArray(value)) return value.length > 0;
2665
- if (isObject(value)) return Object.keys(value).length > 0;
3035
+ if (isObject2(value)) return Object.keys(value).length > 0;
2666
3036
  return true;
2667
3037
  }
2668
- function isObject(value) {
3038
+ function isObject2(value) {
2669
3039
  return typeof value === "object" && value !== null && !Array.isArray(value);
2670
3040
  }
2671
3041
  function fail(message, code, field, hint) {
@@ -3522,7 +3892,7 @@ var taskSave = createEngageTaskCapabilityCommand({
3522
3892
  ],
3523
3893
  risk: "write",
3524
3894
  validate: (ctx) => {
3525
- readRequiredJsonObject(ctx, "req");
3895
+ validateEmbeddedSemanticDefinitions(readRequiredJsonObject(ctx, "req"), "--req");
3526
3896
  },
3527
3897
  buildInput: (ctx) => ({ project_id: ctx.num("project-id"), req: readRequiredJsonObject(ctx, "req") })
3528
3898
  });
@@ -37,6 +37,9 @@ function readRequiredStringArray(ctx, name) {
37
37
  function addOptionalString(input, name, value) {
38
38
  if (value !== "") input[name] = value;
39
39
  }
40
+ function addOptionalBoolean(input, ctx, flagName, fieldName) {
41
+ if (ctx.str(flagName) !== "") input[fieldName] = ctx.bool(flagName);
42
+ }
40
43
 
41
44
  // src/commands/te-experiment/bucket/list.ts
42
45
  var bucketList = createExperimentCapabilityCommand({
@@ -481,7 +484,7 @@ var saveBuildGuide = createExperimentCapabilityCommand({
481
484
  resource: "save",
482
485
  command: "build-guide",
483
486
  capabilityId: "experiment.save.build-guide",
484
- description: "Build a read-only save guide for feature, traffic layer, experiment, or metric saves.",
487
+ description: "Build a read-only save guide for feature, traffic layer, experiment, or metric saves. WARNING: data.guide.example_args.req keys are recursively snake_cased for display; do not copy them into --req. Final save DTOs require camelCase (e.g. expName). Prefer capability inspect <final-save-id> input_schema.properties.req.",
485
488
  flags: [
486
489
  { name: "project-id", type: "number", required: true, alias: "p", desc: "Numeric project ID." },
487
490
  {
@@ -509,7 +512,7 @@ var saveValidate = createExperimentCapabilityCommand({
509
512
  resource: "save",
510
513
  command: "validate",
511
514
  capabilityId: "experiment.save.validate",
512
- description: "Dry-run validation for feature, traffic layer, experiment, or metric save requests.",
515
+ description: "Dry-run validation for feature, traffic layer, experiment, or metric save requests. WARNING: valid=true is not a final-save schema pass; snake_case req keys (e.g. exp_name) can still fail on experiment \u2026 save. Always pass camelCase --req. Do not copy example_args.req key casing from this response.",
513
516
  flags: [
514
517
  { name: "project-id", type: "number", required: true, alias: "p", desc: "Numeric project ID." },
515
518
  {
@@ -518,7 +521,12 @@ var saveValidate = createExperimentCapabilityCommand({
518
521
  required: true,
519
522
  desc: "Save operation mode: save_feature, save_traffic_layer, save_experiment, or save_metric."
520
523
  },
521
- { name: "req", type: "json", required: true, desc: "Candidate camelCase save request object." }
524
+ {
525
+ name: "req",
526
+ type: "json",
527
+ required: true,
528
+ desc: "Candidate camelCase save request object (expName/metricId/\u2026). Never use snake_case DTO keys."
529
+ }
522
530
  ],
523
531
  risk: "read",
524
532
  validate: (ctx) => {
@@ -535,6 +543,98 @@ var saveValidate = createExperimentCapabilityCommand({
535
543
  })
536
544
  });
537
545
 
546
+ // src/commands/te-experiment/report/metric-trend.ts
547
+ var reportMetricTrend = createExperimentCapabilityCommand({
548
+ resource: "report",
549
+ command: "metric-trend",
550
+ capabilityId: "experiment.report.metric-trend",
551
+ description: "Query an experiment metric trend report for a date range.",
552
+ flags: [
553
+ { name: "project-id", type: "number", required: true, alias: "p", desc: "Numeric project ID." },
554
+ { name: "exp-id", type: "string", required: true, desc: "Experiment ID." },
555
+ { name: "metric-id", type: "string", required: true, desc: "Metric ID." },
556
+ { name: "start-time", type: "string", required: true, desc: "Start date in yyyy-MM-dd format." },
557
+ { name: "end-time", type: "string", required: true, desc: "End date in yyyy-MM-dd format." },
558
+ {
559
+ name: "request-id",
560
+ type: "string",
561
+ required: false,
562
+ desc: "Optional caller-supplied cli_<32 lowercase hex> lifecycle ID. ae-cli generates and prints one before dispatch when omitted."
563
+ },
564
+ { name: "force-refresh", type: "boolean", required: false, desc: "Force refresh report data." }
565
+ ],
566
+ risk: "read",
567
+ buildInput: (ctx) => {
568
+ const input = {
569
+ project_id: ctx.num("project-id"),
570
+ exp_id: ctx.str("exp-id"),
571
+ metric_id: ctx.str("metric-id"),
572
+ start_time: ctx.str("start-time"),
573
+ end_time: ctx.str("end-time")
574
+ };
575
+ addOptionalString(input, "request_id", ctx.str("request-id"));
576
+ addOptionalBoolean(input, ctx, "force-refresh", "force_refresh");
577
+ return input;
578
+ }
579
+ });
580
+
581
+ // src/commands/te-experiment/report/sample-size.ts
582
+ var reportSampleSize = createExperimentCapabilityCommand({
583
+ resource: "report",
584
+ command: "sample-size",
585
+ capabilityId: "experiment.report.sample-size",
586
+ description: "Query an experiment sample-size report for a date range.",
587
+ flags: [
588
+ { name: "project-id", type: "number", required: true, alias: "p", desc: "Numeric project ID." },
589
+ { name: "exp-id", type: "string", required: true, desc: "Experiment ID." },
590
+ { name: "start-time", type: "string", required: true, desc: "Start date in yyyy-MM-dd format." },
591
+ { name: "end-time", type: "string", required: true, desc: "End date in yyyy-MM-dd format." },
592
+ {
593
+ name: "request-id",
594
+ type: "string",
595
+ required: false,
596
+ desc: "Optional caller-supplied cli_<32 lowercase hex> lifecycle ID. ae-cli generates and prints one before dispatch when omitted."
597
+ },
598
+ { name: "force-refresh", type: "boolean", required: false, desc: "Force refresh report data." },
599
+ { name: "by-hour", type: "boolean", required: false, desc: "Break results down by hour." }
600
+ ],
601
+ risk: "read",
602
+ buildInput: (ctx) => {
603
+ const input = {
604
+ project_id: ctx.num("project-id"),
605
+ exp_id: ctx.str("exp-id"),
606
+ start_time: ctx.str("start-time"),
607
+ end_time: ctx.str("end-time")
608
+ };
609
+ addOptionalString(input, "request_id", ctx.str("request-id"));
610
+ addOptionalBoolean(input, ctx, "force-refresh", "force_refresh");
611
+ addOptionalBoolean(input, ctx, "by-hour", "by_hour");
612
+ return input;
613
+ }
614
+ });
615
+
616
+ // src/commands/te-experiment/report/summary.ts
617
+ var reportSummary = createExperimentCapabilityCommand({
618
+ resource: "report",
619
+ command: "summary",
620
+ capabilityId: "experiment.report.summary",
621
+ description: "Query an experiment report summary.",
622
+ flags: [
623
+ { name: "project-id", type: "number", required: true, alias: "p", desc: "Numeric project ID." },
624
+ { name: "exp-id", type: "string", required: true, desc: "Experiment ID." },
625
+ { name: "force-refresh", type: "boolean", required: false, desc: "Force refresh report data." }
626
+ ],
627
+ risk: "read",
628
+ buildInput: (ctx) => {
629
+ const input = {
630
+ project_id: ctx.num("project-id"),
631
+ exp_id: ctx.str("exp-id")
632
+ };
633
+ addOptionalBoolean(input, ctx, "force-refresh", "force_refresh");
634
+ return input;
635
+ }
636
+ });
637
+
538
638
  // src/commands/te-experiment/traffic-layer/batch-delete.ts
539
639
  var trafficLayerBatchDelete = createExperimentCapabilityCommand({
540
640
  resource: "traffic-layer",
@@ -616,6 +716,9 @@ var commands = [
616
716
  trafficLayerGet,
617
717
  trafficLayerList,
618
718
  trafficLayerBatchDelete,
719
+ reportSummary,
720
+ reportSampleSize,
721
+ reportMetricTrend,
619
722
  metricSave,
620
723
  metricGet,
621
724
  metricList,
@@ -654,6 +757,9 @@ export {
654
757
  metricList,
655
758
  metricSave,
656
759
  operationLogQuery,
760
+ reportMetricTrend,
761
+ reportSampleSize,
762
+ reportSummary,
657
763
  saveBuildGuide,
658
764
  saveValidate,
659
765
  trafficLayerBatchDelete,