@tenderprompt/cli 0.1.23 → 0.1.24

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 (2) hide show
  1. package/dist/index.js +402 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,6 +31,9 @@ function readCliPackageVersion() {
31
31
  return UNKNOWN_CLI_VERSION;
32
32
  }
33
33
  const CLI_PACKAGE_VERSION = readCliPackageVersion();
34
+ const ANALYTICS_AUTHORING_PLAYBOOK_ID = 'analytics-event-authoring';
35
+ const ANALYTICS_AUTHORING_COMMAND = 'tender app analytics authoring --json';
36
+ const ANALYTICS_AUTHORING_PLAYBOOK_COMMAND = `tender playbooks get ${ANALYTICS_AUTHORING_PLAYBOOK_ID} --json`;
34
37
  class TenderCliUsageError extends Error {
35
38
  constructor(message) {
36
39
  super(message);
@@ -121,7 +124,9 @@ Commands:
121
124
  Approve manifest-declared outbound auth grants
122
125
  tender app analytics summary <app-id>
123
126
  Read artifact analytics totals and trend points
124
- tender app analytics query <app-id> --spec <file|->
127
+ tender app analytics authoring
128
+ Print the current analytics event authoring contract
129
+ tender app analytics query <app-id> --spec <file|->
125
130
  Run a server-validated chart spec
126
131
  tender app analytics suggestions <app-id>
127
132
  Generate useful starter chart suggestions
@@ -171,6 +176,7 @@ Examples:
171
176
  tender app outbound-auth list artifact_123 --json
172
177
  tender app outbound-auth approve artifact_123 --all --confirm approve --json
173
178
  tender app analytics summary artifact_123 --range 30d --json
179
+ tender app analytics authoring --json
174
180
  tender app analytics query artifact_123 --spec chart.json --json
175
181
  tender app analytics suggestions artifact_123 --range 30d --json
176
182
  tender app db capabilities artifact_123 --json
@@ -360,6 +366,8 @@ Commands:
360
366
  Approve manifest-declared outbound auth grants
361
367
  tender app analytics summary <app-id>
362
368
  Read artifact analytics totals and trend points
369
+ tender app analytics authoring
370
+ Print the current analytics event authoring contract
363
371
  tender app analytics query <app-id> --spec <file|->
364
372
  Run a server-validated chart spec
365
373
  tender app analytics suggestions <app-id>
@@ -423,13 +431,14 @@ Examples:
423
431
  tender app outbound-auth list artifact_123 --json
424
432
  tender app outbound-auth approve artifact_123 --all --confirm approve --json
425
433
  tender app analytics summary artifact_123 --range 30d --json
434
+ tender app analytics authoring --json
426
435
  tender app analytics query artifact_123 --spec chart.json --json
427
436
  tender app analytics suggestions artifact_123 --range 30d --json
428
437
  tender app analytics dashboards artifact_123 --json
429
438
  tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
430
- tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
439
+ tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json
431
440
  tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
432
- tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
441
+ tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
433
442
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
434
443
  tender app analytics properties list artifact_123 --json
435
444
  tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
@@ -515,16 +524,19 @@ Examples:
515
524
  tender version --json`;
516
525
  }
517
526
  function analyticsHelp() {
518
- return `Usage: tender app analytics <command> <app-id> [options]
527
+ return `Usage: tender app analytics <command> [<app-id>] [options]
519
528
 
520
529
  Start here for agents:
530
+ tender app analytics authoring --json
521
531
  tender app analytics capabilities <app-id> --include-catalog --range 30d --json
522
532
 
523
533
  Commands:
534
+ tender app analytics authoring Print the current event authoring contract
524
535
  tender app analytics summary <app-id> Read totals and trend points
525
536
  tender app analytics query <app-id> Run one chart spec
526
537
  tender app analytics capabilities <app-id> Describe agent-safe analytics capabilities
527
538
  tender app analytics suggestions <app-id> Generate starter chart suggestions
539
+ tender app analytics exact-funnel <app-id> Run exact unit funnel analysis
528
540
  tender app analytics dashboards <app-id> List or create dashboards
529
541
  tender app analytics export <kind> <app-id> Export catalog metadata or query rows
530
542
  tender app analytics charts templates Print reusable chart spec templates
@@ -536,22 +548,33 @@ Commands:
536
548
  tender app analytics properties activate <app-id> Make a property queryable
537
549
 
538
550
  Examples:
551
+ tender app analytics authoring --json
539
552
  tender app analytics summary artifact_123 --range 30d --json
540
553
  tender app analytics capabilities artifact_123 --include-catalog --json
541
554
  tender app analytics query artifact_123 --spec chart.json --json
542
555
  tender app analytics query artifact_123 --spec - --json < chart.json
556
+ tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json
543
557
  tender app analytics export catalog artifact_123 --range 30d --json
544
558
  tender app analytics export query artifact_123 --spec chart.json --format csv
545
559
  tender app analytics suggestions artifact_123 --range 2026-04-01..2026-04-30 --json
546
560
  tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
547
561
  tender app analytics charts templates --json
548
- tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json
562
+ tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json
549
563
  tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
550
- tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
564
+ tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
551
565
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
552
566
  tender app analytics properties list artifact_123 --json
553
567
  tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json`;
554
568
  }
569
+ function analyticsAuthoringHelp() {
570
+ return `Usage: tender app analytics authoring [--json]
571
+
572
+ Prints the current generated-app analytics event authoring contract. This command
573
+ does not require an app id, checkout, API token, or installed skill.
574
+
575
+ Examples:
576
+ tender app analytics authoring --json`;
577
+ }
555
578
  function analyticsSummaryHelp() {
556
579
  return `Usage: tender app analytics summary <app-id> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
557
580
 
@@ -605,6 +628,16 @@ function analyticsSuggestionsHelp() {
605
628
  Examples:
606
629
  tender app analytics suggestions artifact_123 --range 30d --json`;
607
630
  }
631
+ function analyticsExactFunnelHelp() {
632
+ return `Usage: tender app analytics exact-funnel <app-id> --flow <flow-id> --steps <step-a,step-b> [--range <7d|30d|90d|YYYY-MM-DD..YYYY-MM-DD>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
633
+
634
+ Runs an explicit raw-event unit funnel audit. This path dedupes by unit id and
635
+ flow step from artifact-scoped raw analytics rows; it is not used for normal
636
+ dashboard page loads.
637
+
638
+ Examples:
639
+ tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json`;
640
+ }
608
641
  function analyticsDashboardsHelp() {
609
642
  return `Usage: tender app analytics dashboards <app-id> [--create <name>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
610
643
 
@@ -630,8 +663,8 @@ Property charts automatically make required custom event properties queryable be
630
663
  saving when the spec has an event_name = filter.
631
664
 
632
665
  Examples:
633
- tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --dry-run --json
634
- tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec funnel.json --json`;
666
+ tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
667
+ tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json`;
635
668
  }
636
669
  function analyticsChartsListHelp() {
637
670
  return `Usage: tender app analytics charts list <app-id> --dashboard <dashboard-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
@@ -649,8 +682,8 @@ saving when the spec has an event_name = filter.
649
682
 
650
683
  Examples:
651
684
  tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --dry-run --json
652
- tender app analytics charts update artifact_123 --chart analytics_chart_123 --spec funnel.json --dry-run --json
653
- tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec - --json < funnel.json`;
685
+ tender app analytics charts update artifact_123 --chart analytics_chart_123 --spec unit-funnel.json --dry-run --json
686
+ tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec - --json < unit-funnel.json`;
654
687
  }
655
688
  function analyticsChartsDeleteHelp() {
656
689
  return `Usage: tender app analytics charts delete <app-id> --chart <chart-id> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
@@ -1135,6 +1168,13 @@ function tenderCliCapabilities() {
1135
1168
  persistence: 'session_only',
1136
1169
  writesToCheckout: false,
1137
1170
  },
1171
+ analyticsAuthoring: {
1172
+ command: ANALYTICS_AUTHORING_COMMAND,
1173
+ playbookId: ANALYTICS_AUTHORING_PLAYBOOK_ID,
1174
+ playbookCommand: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
1175
+ doctorCommand: 'tender app doctor --dir <dir> --json',
1176
+ purpose: 'Keep generated app analytics events compatible with Tender dashboards and unit funnels.',
1177
+ },
1138
1178
  description: 'Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.',
1139
1179
  discovery: {
1140
1180
  primary: 'npm exec --yes @tenderprompt/cli@latest -- capabilities --json',
@@ -1244,6 +1284,8 @@ function tenderCliCapabilities() {
1244
1284
  name: 'analytics_agent_loop',
1245
1285
  when: 'When understanding app analytics or creating saved charts.',
1246
1286
  commands: [
1287
+ ANALYTICS_AUTHORING_COMMAND,
1288
+ ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
1247
1289
  'tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json',
1248
1290
  'tender app analytics charts templates --json',
1249
1291
  'tender app analytics query <artifact-id> --spec chart.json --json',
@@ -1257,6 +1299,7 @@ function tenderCliCapabilities() {
1257
1299
  'tender app analytics charts delete <artifact-id> --chart <chart-id> --dry-run --json',
1258
1300
  ],
1259
1301
  notes: [
1302
+ 'Run authoring before adding or repairing analytics instrumentation; use capabilities for artifact-scoped read/query details.',
1260
1303
  'Combine CLI capabilities with local source inspection of analytics calls.',
1261
1304
  'Validate chart specs with query before creating or updating saved charts.',
1262
1305
  'Property chart create/update commands automatically make missing custom event properties queryable when the spec has an event_name = filter.',
@@ -1383,6 +1426,18 @@ function tenderCliCapabilities() {
1383
1426
  requiresAuth: false,
1384
1427
  writes: false,
1385
1428
  },
1429
+ {
1430
+ command: ANALYTICS_AUTHORING_COMMAND,
1431
+ purpose: 'Print the generated-app analytics event payload contract for unit funnels and dashboard-ready events.',
1432
+ requiresAuth: false,
1433
+ writes: false,
1434
+ },
1435
+ {
1436
+ command: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
1437
+ purpose: 'Fetch the remote analytics authoring playbook for examples and repair patterns.',
1438
+ requiresAuth: true,
1439
+ writes: false,
1440
+ },
1386
1441
  {
1387
1442
  command: 'tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json',
1388
1443
  purpose: 'Discover artifact-scoped analytics commands, chart specs, saved dashboards, and bounded observed catalog.',
@@ -3937,6 +3992,20 @@ function parseAnalyticsCapabilitiesArgs(args) {
3937
3992
  ...parsed,
3938
3993
  };
3939
3994
  }
3995
+ function parseAnalyticsAuthoringArgs(args) {
3996
+ if (args.includes('--help') || args.includes('-h')) {
3997
+ return { command: 'help', topic: 'app analytics authoring' };
3998
+ }
3999
+ let json = false;
4000
+ for (const arg of args) {
4001
+ if (arg === '--json') {
4002
+ json = true;
4003
+ continue;
4004
+ }
4005
+ throw new TenderCliUsageError(`Unknown analytics authoring option: ${arg}`);
4006
+ }
4007
+ return { command: 'app analytics authoring', json };
4008
+ }
3940
4009
  function parseAnalyticsSuggestionsArgs(args) {
3941
4010
  if (args.includes('--help') || args.includes('-h')) {
3942
4011
  return { command: 'help', topic: 'app analytics suggestions' };
@@ -3958,6 +4027,53 @@ function parseAnalyticsSuggestionsArgs(args) {
3958
4027
  }
3959
4028
  return { command: 'app analytics suggestions', artifactId, range, ...parsed };
3960
4029
  }
4030
+ function parseAnalyticsSteps(value) {
4031
+ const raw = parseRequiredFlagValue(value, '--steps');
4032
+ const steps = raw
4033
+ .split(',')
4034
+ .map((step) => step.trim())
4035
+ .filter((step) => step.length > 0);
4036
+ if (steps.length < 2 || steps.length > 25 || new Set(steps).size !== steps.length) {
4037
+ throw new TenderCliUsageError('--steps must contain 2-25 unique comma-separated flow steps.');
4038
+ }
4039
+ return steps;
4040
+ }
4041
+ function parseAnalyticsExactFunnelArgs(args) {
4042
+ if (args.includes('--help') || args.includes('-h')) {
4043
+ return { command: 'help', topic: 'app analytics exact-funnel' };
4044
+ }
4045
+ const artifactId = args[0];
4046
+ if (!artifactId || artifactId.startsWith('-')) {
4047
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics exact-funnel <app-id> --flow checkout_flow --steps started,completed --range 30d --json');
4048
+ }
4049
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4050
+ let range = '7d';
4051
+ let flowId = null;
4052
+ let steps = null;
4053
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4054
+ const arg = parsed.remaining[index];
4055
+ if (arg === '--range') {
4056
+ range = parseAnalyticsRange(parsed.remaining[index + 1]);
4057
+ index += 1;
4058
+ continue;
4059
+ }
4060
+ if (arg === '--flow') {
4061
+ flowId = parseRequiredFlagValue(parsed.remaining[index + 1], '--flow');
4062
+ index += 1;
4063
+ continue;
4064
+ }
4065
+ if (arg === '--steps') {
4066
+ steps = parseAnalyticsSteps(parsed.remaining[index + 1]);
4067
+ index += 1;
4068
+ continue;
4069
+ }
4070
+ throw new TenderCliUsageError(`Unknown analytics exact-funnel option: ${arg}`);
4071
+ }
4072
+ if (!flowId || !steps) {
4073
+ throw new TenderCliUsageError('--flow and --steps are required. Example: tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json');
4074
+ }
4075
+ return { command: 'app analytics exact-funnel', artifactId, flowId, steps, range, ...parsed };
4076
+ }
3961
4077
  function parseAnalyticsDashboardsArgs(args) {
3962
4078
  if (args.includes('--help') || args.includes('-h')) {
3963
4079
  return { command: 'help', topic: 'app analytics dashboards' };
@@ -4079,6 +4195,121 @@ function parseAnalyticsExportQueryArgs(args) {
4079
4195
  ...parsed,
4080
4196
  };
4081
4197
  }
4198
+ function analyticsAuthoringContract() {
4199
+ return {
4200
+ ok: true,
4201
+ surface: 'analytics_authoring',
4202
+ command: 'app analytics authoring',
4203
+ version: 1,
4204
+ playbook: {
4205
+ id: ANALYTICS_AUTHORING_PLAYBOOK_ID,
4206
+ command: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
4207
+ useWhen: [
4208
+ 'Creating a generated app with custom analytics.',
4209
+ 'Repairing dashboards or funnels that cannot dedupe a shopper journey.',
4210
+ 'Retrofitting an existing app before saving unit funnel charts.',
4211
+ 'Publishing Shopify Customer Events while keeping Tender dashboards complete.',
4212
+ ],
4213
+ },
4214
+ doctor: {
4215
+ command: 'tender app doctor --dir <dir> --json',
4216
+ diagnosticCodes: [
4217
+ 'analytics_reserved_unit_step_event',
4218
+ 'analytics_reserved_event_type_field',
4219
+ 'analytics_metadata_nested_in_properties',
4220
+ 'analytics_missing_unit_flow_metadata',
4221
+ 'analytics_tender_tracking_skipped_after_shopify_publish',
4222
+ 'analytics_unstable_unit_id',
4223
+ ],
4224
+ },
4225
+ analyticsAuthoring: {
4226
+ payloadShape: {
4227
+ event: 'custom_event_name',
4228
+ unit: { type: 'quiz_attempt', id: 'stable-attempt-id' },
4229
+ flow: {
4230
+ id: 'recommendation_quiz',
4231
+ step: 'quiz_completed',
4232
+ order: 3,
4233
+ role: 'outcome',
4234
+ },
4235
+ properties: {
4236
+ experiment_id: 'quiz_copy_v1',
4237
+ experiment_variant: 'guided',
4238
+ score: 84,
4239
+ },
4240
+ },
4241
+ rules: [
4242
+ 'Keep event names business-specific and do not use platform-reserved names such as unit_step.',
4243
+ 'Put unit and flow metadata at the top level of the analytics payload, not inside properties.',
4244
+ 'Use one stable unit.id for the user journey being measured; do not regenerate it for each click.',
4245
+ 'Use flow.role values start, milestone, activity, outcome, or error.',
4246
+ 'Do not emit event_type from generated app code; the platform derives it.',
4247
+ 'When also publishing Shopify Customer Events, still send Tender analytics for Tender dashboards.',
4248
+ 'Keep properties low-cardinality and dashboard-safe; avoid PII, secrets, free text, ids, and URLs unless explicitly approved.',
4249
+ ],
4250
+ propertyStates: ['tracked', 'observed', 'queryable'],
4251
+ queryablePropertyCommand: 'tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type <dimension|metric> --dry-run --json',
4252
+ },
4253
+ examples: [
4254
+ {
4255
+ scenario: 'game attempt',
4256
+ payload: {
4257
+ event: 'level_completed',
4258
+ unit: { type: 'game_attempt', id: 'attempt-01HXYZ' },
4259
+ flow: { id: 'pasta_game', step: 'level_completed', order: 3, role: 'milestone' },
4260
+ properties: { level: 2, score: 1270, experiment_variant: 'timer_v2' },
4261
+ },
4262
+ },
4263
+ {
4264
+ scenario: 'recommendation quiz',
4265
+ payload: {
4266
+ event: 'quiz_completed',
4267
+ unit: { type: 'quiz_attempt', id: 'quiz-session-42' },
4268
+ flow: { id: 'recommendation_quiz', step: 'quiz_completed', order: 4, role: 'outcome' },
4269
+ properties: { result: 'starter_bundle', score: 84 },
4270
+ },
4271
+ },
4272
+ {
4273
+ scenario: 'bundle builder',
4274
+ payload: {
4275
+ event: 'bundle_checkout_clicked',
4276
+ unit: { type: 'bundle_build', id: 'bundle-build-42' },
4277
+ flow: { id: 'bundle_builder', step: 'checkout_clicked', order: 5, role: 'outcome' },
4278
+ properties: { selected_items: 6, cadence: 'monthly', variant: 'guided' },
4279
+ },
4280
+ },
4281
+ {
4282
+ scenario: 'Shopify Customer Events duplication',
4283
+ notes: [
4284
+ 'Publish consent-aware Shopify Customer Events when the embed runs in Shopify.',
4285
+ 'Do not return before sending the Tender analytics event needed by Tender dashboards.',
4286
+ ],
4287
+ payload: {
4288
+ event: 'product_match_submitted',
4289
+ unit: { type: 'quiz_attempt', id: 'quiz-session-42' },
4290
+ flow: { id: 'recommendation_quiz', step: 'submitted', order: 3, role: 'milestone' },
4291
+ properties: { shopify_customer_event: 'product_match_submitted' },
4292
+ },
4293
+ },
4294
+ ],
4295
+ charting: {
4296
+ unitFunnel: 'After traffic exists, use tender app analytics capabilities <app-id> --include-catalog --json and create version 2 unit_step_count funnel specs from observedFlows and observedUnitTypes.',
4297
+ exactAudit: 'Use tender app analytics exact-funnel <app-id> --flow <flow-id> --steps <step-a,step-b> --json for repair and low-volume audits.',
4298
+ },
4299
+ };
4300
+ }
4301
+ function runAnalyticsAuthoring(command, io) {
4302
+ const payload = analyticsAuthoringContract();
4303
+ printAnalyticsPayload(command, io, payload, [
4304
+ 'Analytics authoring contract',
4305
+ '',
4306
+ `command: ${ANALYTICS_AUTHORING_COMMAND}`,
4307
+ `playbook: ${ANALYTICS_AUTHORING_PLAYBOOK_COMMAND}`,
4308
+ 'payload: event plus top-level unit, flow, and properties',
4309
+ 'doctor: tender app doctor --dir <dir> --json',
4310
+ ]);
4311
+ return 0;
4312
+ }
4082
4313
  function analyticsChartTemplates() {
4083
4314
  return [
4084
4315
  {
@@ -4145,30 +4376,30 @@ function analyticsChartTemplates() {
4145
4376
  },
4146
4377
  },
4147
4378
  {
4148
- id: 'event_funnel_by_name',
4149
- title: 'Event funnel',
4150
- visualization: 'funnel',
4151
- purpose: 'Measure ordered conversion through observed custom event names.',
4379
+ id: 'milestone_event_totals',
4380
+ title: 'Milestone event totals',
4381
+ visualization: 'table',
4382
+ purpose: 'Compare ordered milestone event totals without treating them as conversion rates.',
4152
4383
  usableAsIs: false,
4153
4384
  placeholders: {
4154
- eventNames: ['<start_event>', '<middle_event>', '<conversion_event>'],
4385
+ eventNames: ['<start_event>', '<middle_event>', '<outcome_event>'],
4155
4386
  },
4156
4387
  adaptWith: [
4157
- 'Replace placeholders with ordered event names from catalog classification or observed events.',
4388
+ 'Replace placeholders with observed event names from catalog classification or source order.',
4158
4389
  'Use only event names observed for the selected app.',
4159
- 'Validate with analytics query before saving because funnel order changes the meaning.',
4390
+ 'Use unit_funnel instead when the app emits unit and flow metadata.',
4160
4391
  ],
4161
4392
  spec: {
4162
4393
  version: 1,
4163
4394
  dataset: 'events',
4164
- visualization: 'funnel',
4395
+ visualization: 'table',
4165
4396
  metric: { name: 'custom_event_count' },
4166
4397
  groupBy: [{ field: 'event_name' }],
4167
4398
  filters: [
4168
4399
  {
4169
4400
  field: 'event_name',
4170
4401
  op: 'in',
4171
- value: ['<start_event>', '<middle_event>', '<conversion_event>'],
4402
+ value: ['<start_event>', '<middle_event>', '<outcome_event>'],
4172
4403
  },
4173
4404
  ],
4174
4405
  range: { preset: '30d' },
@@ -4177,30 +4408,65 @@ function analyticsChartTemplates() {
4177
4408
  },
4178
4409
  },
4179
4410
  {
4180
- id: 'request_path_funnel',
4181
- title: 'Request path funnel',
4411
+ id: 'unit_funnel',
4412
+ title: 'Unit funnel',
4182
4413
  visualization: 'funnel',
4183
- purpose: 'Measure ordered conversion through observed request paths.',
4414
+ purpose: 'Measure ordered progression with one reach per unit and step.',
4184
4415
  usableAsIs: false,
4185
4416
  placeholders: {
4186
- paths: ['/<start_path>', '/<middle_path>', '/<conversion_path>'],
4417
+ flowId: '<flow_id>',
4418
+ unitType: '<unit_type>',
4419
+ steps: ['<start_step>', '<middle_step>', '<outcome_step>'],
4187
4420
  },
4188
4421
  adaptWith: [
4189
- 'Use when catalog classification includes a path-based funnel or the source code shows ordered route milestones.',
4422
+ 'Use only when the app emits unit and flow metadata and capabilities show unit-step rows.',
4423
+ 'Replace placeholders with ordered flow steps, usually matching event names.',
4424
+ 'Use activity charts for repeated actions instead of adding raw activity to this funnel.',
4425
+ ],
4426
+ spec: {
4427
+ version: 2,
4428
+ dataset: 'events',
4429
+ visualization: 'funnel',
4430
+ metric: { name: 'unit_step_count' },
4431
+ funnel: {
4432
+ flowId: '<flow_id>',
4433
+ unit: { source: 'unit_id', type: '<unit_type>' },
4434
+ steps: [
4435
+ { event: '<start_step>', label: 'Started' },
4436
+ { event: '<middle_step>', label: 'Reached milestone' },
4437
+ { event: '<outcome_step>', label: 'Completed' },
4438
+ ],
4439
+ },
4440
+ filters: [],
4441
+ range: { preset: '30d' },
4442
+ limit: 3,
4443
+ },
4444
+ },
4445
+ {
4446
+ id: 'route_request_totals',
4447
+ title: 'Route request totals',
4448
+ visualization: 'table',
4449
+ purpose: 'Compare observed request path totals without treating them as conversion rates.',
4450
+ usableAsIs: false,
4451
+ placeholders: {
4452
+ paths: ['/<start_path>', '/<middle_path>', '/<outcome_path>'],
4453
+ },
4454
+ adaptWith: [
4455
+ 'Use when the source code shows ordered route milestones but custom events are unavailable.',
4190
4456
  'Replace placeholders with exact observed paths from the selected app.',
4191
- 'Do not include repeated activity routes unless source context says they are milestones.',
4457
+ 'Use custom events with unit and flow metadata for conversion funnels.',
4192
4458
  ],
4193
4459
  spec: {
4194
4460
  version: 1,
4195
4461
  dataset: 'events',
4196
- visualization: 'funnel',
4462
+ visualization: 'table',
4197
4463
  metric: { name: 'request_count' },
4198
4464
  groupBy: [{ field: 'path' }],
4199
4465
  filters: [
4200
4466
  {
4201
4467
  field: 'path',
4202
4468
  op: 'in',
4203
- value: ['/<start_path>', '/<middle_path>', '/<conversion_path>'],
4469
+ value: ['/<start_path>', '/<middle_path>', '/<outcome_path>'],
4204
4470
  },
4205
4471
  ],
4206
4472
  range: { preset: '30d' },
@@ -4208,6 +4474,33 @@ function analyticsChartTemplates() {
4208
4474
  limit: 3,
4209
4475
  },
4210
4476
  },
4477
+ {
4478
+ id: 'activity_rate',
4479
+ title: 'Activity rate',
4480
+ visualization: 'number',
4481
+ purpose: 'Show an average repeated-activity total recorded on an outcome event.',
4482
+ usableAsIs: false,
4483
+ placeholders: {
4484
+ eventName: '<outcome_event>',
4485
+ metric: 'avg:<queryable_metric_property>',
4486
+ },
4487
+ adaptWith: [
4488
+ 'Use when the outcome event records a numeric total such as throws, answers, edits, duration_ms, or attempts.',
4489
+ 'Activate the metric property for the selected outcome event before dashboard queries.',
4490
+ 'Use raw activity volume charts when the app only emits repeated activity events and no per-unit total.',
4491
+ ],
4492
+ spec: {
4493
+ version: 1,
4494
+ dataset: 'events',
4495
+ visualization: 'number',
4496
+ metric: { name: 'avg:<queryable_metric_property>' },
4497
+ groupBy: [],
4498
+ filters: [{ field: 'event_name', op: '=', value: '<outcome_event>' }],
4499
+ range: { preset: '30d' },
4500
+ sort: [],
4501
+ limit: 1,
4502
+ },
4503
+ },
4211
4504
  {
4212
4505
  id: 'property_breakdown',
4213
4506
  title: 'Property breakdown',
@@ -4235,6 +4528,42 @@ function analyticsChartTemplates() {
4235
4528
  limit: 10,
4236
4529
  },
4237
4530
  },
4531
+ {
4532
+ id: 'experiment_readout',
4533
+ title: 'Experiment readout',
4534
+ visualization: 'table',
4535
+ purpose: 'Compare selected milestones by experiment variant in one table.',
4536
+ usableAsIs: false,
4537
+ placeholders: {
4538
+ eventNames: ['<exposure_event>', '<started_event>', '<outcome_event>'],
4539
+ property: 'experiment_variant',
4540
+ },
4541
+ adaptWith: [
4542
+ 'Use when each selected event has an active low-cardinality experiment_variant property mapping.',
4543
+ 'Keep this as a table; do not render raw event totals as conversion percentages.',
4544
+ 'Use exact analysis for low-volume winner decisions.',
4545
+ ],
4546
+ spec: {
4547
+ version: 1,
4548
+ dataset: 'events',
4549
+ visualization: 'table',
4550
+ metric: { name: 'custom_event_count' },
4551
+ groupBy: [
4552
+ { field: 'event_name' },
4553
+ { field: 'property:experiment_variant' },
4554
+ ],
4555
+ filters: [
4556
+ {
4557
+ field: 'event_name',
4558
+ op: 'in',
4559
+ value: ['<exposure_event>', '<started_event>', '<outcome_event>'],
4560
+ },
4561
+ ],
4562
+ range: { preset: '30d' },
4563
+ sort: [{ field: 'event_name', direction: 'asc' }],
4564
+ limit: 50,
4565
+ },
4566
+ },
4238
4567
  {
4239
4568
  id: 'average_metric_property',
4240
4569
  title: 'Average metric property',
@@ -5235,9 +5564,15 @@ function parseTenderArgs(args) {
5235
5564
  if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'capabilities') {
5236
5565
  return parseAnalyticsCapabilitiesArgs(resourceArgs.slice(3));
5237
5566
  }
5567
+ if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'authoring') {
5568
+ return parseAnalyticsAuthoringArgs(resourceArgs.slice(3));
5569
+ }
5238
5570
  if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'suggestions') {
5239
5571
  return parseAnalyticsSuggestionsArgs(resourceArgs.slice(3));
5240
5572
  }
5573
+ if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'exact-funnel') {
5574
+ return parseAnalyticsExactFunnelArgs(resourceArgs.slice(3));
5575
+ }
5241
5576
  if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'dashboards') {
5242
5577
  return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
5243
5578
  }
@@ -5790,6 +6125,9 @@ function analyticsCorrectedExample(artifactId, analyticsPath) {
5790
6125
  if (analyticsPath === 'suggestions') {
5791
6126
  return `tender app analytics suggestions ${artifactId} --range 30d --json`;
5792
6127
  }
6128
+ if (analyticsPath === 'exact-funnel') {
6129
+ return `tender app analytics exact-funnel ${artifactId} --flow <flow-id> --steps started,completed --range 30d --json`;
6130
+ }
5793
6131
  if (analyticsPath === 'properties') {
5794
6132
  return `tender app analytics properties list ${artifactId} --json`;
5795
6133
  }
@@ -9268,6 +9606,31 @@ async function runAnalyticsSuggestions(command, io, runtime) {
9268
9606
  printAnalyticsPayload(command, io, payload, [`app_id: ${command.artifactId}`, `suggestions: ${suggestions.length}`]);
9269
9607
  return 0;
9270
9608
  }
9609
+ async function runAnalyticsExactFunnel(command, io, runtime) {
9610
+ const credentials = await resolveAnalyticsCredentials(command, runtime);
9611
+ const payload = await requestAnalyticsApi({
9612
+ artifactId: command.artifactId,
9613
+ analyticsPath: 'exact-funnel',
9614
+ method: 'POST',
9615
+ baseUrl: credentials.baseUrl,
9616
+ token: credentials.token,
9617
+ fetcher: runtime.fetcher ?? fetch,
9618
+ range: command.range,
9619
+ body: {
9620
+ flowId: command.flowId,
9621
+ steps: command.steps,
9622
+ },
9623
+ });
9624
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
9625
+ printAnalyticsPayload(command, io, payload, [
9626
+ `app_id: ${command.artifactId}`,
9627
+ `source: ${String(payload.source ?? 'unknown')}`,
9628
+ `flow_id: ${command.flowId}`,
9629
+ `steps: ${rows.length}`,
9630
+ `exact: ${String(payload.exact ?? false)}`,
9631
+ ]);
9632
+ return 0;
9633
+ }
9271
9634
  async function runAnalyticsDashboards(command, io, runtime) {
9272
9635
  if (command.name && command.dryRun) {
9273
9636
  const payload = {
@@ -9814,9 +10177,15 @@ export async function runTenderCli(args, io = {
9814
10177
  else if (command.topic === 'app analytics capabilities') {
9815
10178
  io.stdout(analyticsCapabilitiesHelp());
9816
10179
  }
10180
+ else if (command.topic === 'app analytics authoring') {
10181
+ io.stdout(analyticsAuthoringHelp());
10182
+ }
9817
10183
  else if (command.topic === 'app analytics suggestions') {
9818
10184
  io.stdout(analyticsSuggestionsHelp());
9819
10185
  }
10186
+ else if (command.topic === 'app analytics exact-funnel') {
10187
+ io.stdout(analyticsExactFunnelHelp());
10188
+ }
9820
10189
  else if (command.topic === 'app analytics dashboards') {
9821
10190
  io.stdout(analyticsDashboardsHelp());
9822
10191
  }
@@ -10002,9 +10371,15 @@ export async function runTenderCli(args, io = {
10002
10371
  if (command.command === 'app analytics capabilities') {
10003
10372
  return await runAnalyticsCapabilities(command, io, runtime);
10004
10373
  }
10374
+ if (command.command === 'app analytics authoring') {
10375
+ return runAnalyticsAuthoring(command, io);
10376
+ }
10005
10377
  if (command.command === 'app analytics suggestions') {
10006
10378
  return await runAnalyticsSuggestions(command, io, runtime);
10007
10379
  }
10380
+ if (command.command === 'app analytics exact-funnel') {
10381
+ return await runAnalyticsExactFunnel(command, io, runtime);
10382
+ }
10008
10383
  if (command.command === 'app analytics dashboards') {
10009
10384
  return await runAnalyticsDashboards(command, io, runtime);
10010
10385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"