@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.
- package/dist/index.js +402 -27
- 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
|
|
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: '
|
|
4149
|
-
title: '
|
|
4150
|
-
visualization: '
|
|
4151
|
-
purpose: '
|
|
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>', '<
|
|
4385
|
+
eventNames: ['<start_event>', '<middle_event>', '<outcome_event>'],
|
|
4155
4386
|
},
|
|
4156
4387
|
adaptWith: [
|
|
4157
|
-
'Replace placeholders with
|
|
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
|
-
'
|
|
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: '
|
|
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>', '<
|
|
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: '
|
|
4181
|
-
title: '
|
|
4411
|
+
id: 'unit_funnel',
|
|
4412
|
+
title: 'Unit funnel',
|
|
4182
4413
|
visualization: 'funnel',
|
|
4183
|
-
purpose: 'Measure ordered
|
|
4414
|
+
purpose: 'Measure ordered progression with one reach per unit and step.',
|
|
4184
4415
|
usableAsIs: false,
|
|
4185
4416
|
placeholders: {
|
|
4186
|
-
|
|
4417
|
+
flowId: '<flow_id>',
|
|
4418
|
+
unitType: '<unit_type>',
|
|
4419
|
+
steps: ['<start_step>', '<middle_step>', '<outcome_step>'],
|
|
4187
4420
|
},
|
|
4188
4421
|
adaptWith: [
|
|
4189
|
-
'Use when
|
|
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
|
-
'
|
|
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: '
|
|
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>', '/<
|
|
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
|
}
|