@tenderprompt/cli 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/artifact-memory.js +4 -3
- package/dist/index.js +644 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ session:
|
|
|
43
43
|
analytics history, app database rows, secrets, or release history
|
|
44
44
|
- run server-validated chart specs and export aggregate rows
|
|
45
45
|
- generate starter analytics suggestions
|
|
46
|
+
- print a non-mutating default analytics provisioning plan for generated apps
|
|
46
47
|
- create, list, update, and delete saved analytics dashboards and charts, with
|
|
47
48
|
`--dry-run` available for write previews
|
|
48
49
|
|
|
@@ -79,6 +80,13 @@ For agents, keep the pattern simple:
|
|
|
79
80
|
6. call `preview`, `publish --dry-run`, then `publish --confirm publish` only
|
|
80
81
|
when the user asked to publish
|
|
81
82
|
|
|
83
|
+
For replacement work, run `tender app analytics authoring --json` before adding
|
|
84
|
+
or repairing instrumentation. Its agent guidance tells agents to inspect the
|
|
85
|
+
existing shopper surface before source edits, keep operator analytics out of
|
|
86
|
+
shopper UI, and treat default analytics plus saved starter dashboard charts as
|
|
87
|
+
part of generated-app delivery unless the user opts out or there is a concrete
|
|
88
|
+
blocker.
|
|
89
|
+
|
|
82
90
|
## Auth
|
|
83
91
|
|
|
84
92
|
Use the device login flow to mint a local token:
|
|
@@ -185,6 +193,7 @@ agents:
|
|
|
185
193
|
|
|
186
194
|
```bash
|
|
187
195
|
tender app analytics capabilities artifact_123 --include-catalog --range 30d --json
|
|
196
|
+
tender app analytics provision artifact_123 --from-app --json
|
|
188
197
|
```
|
|
189
198
|
|
|
190
199
|
That response tells the agent:
|
|
@@ -201,6 +210,8 @@ Common read-only commands:
|
|
|
201
210
|
```bash
|
|
202
211
|
tender app analytics summary artifact_123 --range 30d --json
|
|
203
212
|
|
|
213
|
+
tender app analytics provision artifact_123 --from-app --json
|
|
214
|
+
|
|
204
215
|
tender app analytics query artifact_123 --spec chart.json --json
|
|
205
216
|
|
|
206
217
|
tender app analytics query artifact_123 --spec - --json < chart.json
|
|
@@ -256,6 +267,10 @@ explicit:
|
|
|
256
267
|
tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
|
|
257
268
|
```
|
|
258
269
|
|
|
270
|
+
`analytics provision --from-app` is a guided plan in this release, not a hidden
|
|
271
|
+
write. It returns starter chart specs, write-preview commands, write commands,
|
|
272
|
+
blockers, and the chart-list completion proof an external agent should report.
|
|
273
|
+
|
|
259
274
|
Agents should combine `capabilities --include-catalog` with local source-code
|
|
260
275
|
inspection. For example, inspect where the app calls
|
|
261
276
|
`__TP_ANALYTICS.invoke(...)`, propose a chart spec, validate it with
|
package/dist/artifact-memory.js
CHANGED
|
@@ -100,7 +100,8 @@ function assertUsefulMemoryText(input) {
|
|
|
100
100
|
if (lineCount > 240 || byteLength(total) > ARTIFACT_MEMORY_ENTRY_MAX_BYTES) {
|
|
101
101
|
throw new Error(`Memory entries are limited to ${ARTIFACT_MEMORY_ENTRY_MAX_BYTES} bytes. Summarize the relevant context instead.\nNext: tender memory handoff --reason "Need Tender support" --summary "Current blocker and what was tried"`);
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
const allowsLongBody = input.kind === 'summary' || input.kind === 'support_handoff' || input.kind === 'reflection';
|
|
104
|
+
if (lineCount > 40 && !allowsLongBody) {
|
|
104
105
|
throw new Error('Memory entries should be bounded summaries, not raw transcript dumps.\nNext: tender memory add --kind summary --summary "Compact the useful context" --body "Goal, changes, failures, and next step"');
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -162,14 +163,14 @@ async function readExistingNote(input) {
|
|
|
162
163
|
return result.exitCode === 0 ? result.stdout : '';
|
|
163
164
|
}
|
|
164
165
|
export async function appendArtifactMemoryNote(input) {
|
|
165
|
-
|
|
166
|
+
const normalizedKind = normalizeArtifactMemoryKind(input.kind);
|
|
167
|
+
assertUsefulMemoryText({ ...input, kind: normalizedKind.kind });
|
|
166
168
|
const commitSha = input.commitSha ?? (await readArtifactMemoryHead(input));
|
|
167
169
|
const artifactId = input.artifactId ?? (await inferArtifactIdFromRemote(input));
|
|
168
170
|
if (!artifactId) {
|
|
169
171
|
throw new Error('Artifact memory requires an artifact id. Pass --artifact or run inside a checkout with a Tender artifact remote.\nNext: tender app git setup <app-id> --dir .');
|
|
170
172
|
}
|
|
171
173
|
const sessionId = await readArtifactMemorySessionId(input);
|
|
172
|
-
const normalizedKind = normalizeArtifactMemoryKind(input.kind);
|
|
173
174
|
const redactedSummary = redactArtifactMemoryText(input.summary.trim());
|
|
174
175
|
const redactedBody = input.body?.trim() ? redactArtifactMemoryText(input.body.trim()) : null;
|
|
175
176
|
const warnings = [
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,64 @@ 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`;
|
|
37
|
+
const ANALYTICS_AGENT_GUIDANCE = {
|
|
38
|
+
generatedAppDelivery: {
|
|
39
|
+
defaultAnalytics: 'Generated apps should emit useful Tender analytics events and provision saved starter dashboard charts by default unless the user opts out or a concrete blocker is recorded.',
|
|
40
|
+
skipOnlyWhen: ['The user explicitly opts out.', 'A concrete platform or data blocker is reported.'],
|
|
41
|
+
},
|
|
42
|
+
replacementBaseline: {
|
|
43
|
+
requiredBeforeSourceEdits: true,
|
|
44
|
+
guidance: 'Replacement work must inspect and interact with the existing customer-facing surface before source edits, or record the concrete blocker.',
|
|
45
|
+
browserToolPriority: [
|
|
46
|
+
'Codex in-app browser:browser skill',
|
|
47
|
+
'agent-browser',
|
|
48
|
+
'Chrome DevTools MCP',
|
|
49
|
+
'Playwright',
|
|
50
|
+
'another browser-capable tool',
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
shopperOperatorBoundary: {
|
|
54
|
+
guidance: 'Tender analytics dashboards, A/B reports, sessions, and internal counters are operator-facing Tender surfaces, not shopper-facing app UI.',
|
|
55
|
+
shopperUiMustNotInclude: [
|
|
56
|
+
'analytics dashboards',
|
|
57
|
+
'A/B reports',
|
|
58
|
+
'sessions tables',
|
|
59
|
+
'internal counters',
|
|
60
|
+
'dashboard controls',
|
|
61
|
+
'shopper-callable analytics summary routes',
|
|
62
|
+
],
|
|
63
|
+
adminSurfaceException: 'Only build an admin or operator surface when the user explicitly asks for one, and secure it so shopper traffic cannot read internal analytics.',
|
|
64
|
+
},
|
|
65
|
+
dashboardCompletion: {
|
|
66
|
+
completeWhen: [
|
|
67
|
+
'A Tender dashboard exists or is reused.',
|
|
68
|
+
'Required queryable properties are active, already queryable, or blocked with an actionable reason.',
|
|
69
|
+
'Saved starter charts exist for the app journey.',
|
|
70
|
+
'The saved chart list has been queried and returns the chart names or ids.',
|
|
71
|
+
],
|
|
72
|
+
notCompleteWhen: ['Only an empty dashboard shell exists.'],
|
|
73
|
+
proofCommand: 'tender app analytics charts list <app-id> --dashboard <dashboard-id> --json',
|
|
74
|
+
},
|
|
75
|
+
starterDashboardCommands: [
|
|
76
|
+
'tender app analytics authoring --json',
|
|
77
|
+
'tender playbooks get analytics-event-authoring --json',
|
|
78
|
+
'tender app analytics charts templates --json',
|
|
79
|
+
'tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type dimension --dry-run --json',
|
|
80
|
+
'tender app analytics dashboards <app-id> --create "Agent dashboard" --dry-run --json',
|
|
81
|
+
'tender app analytics charts create <app-id> --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json',
|
|
82
|
+
'tender app analytics charts list <app-id> --dashboard <dashboard-id> --json',
|
|
83
|
+
],
|
|
84
|
+
finalAnswerEvidence: [
|
|
85
|
+
'baseline inspection scope or blocker',
|
|
86
|
+
'published URL or preview status',
|
|
87
|
+
'Tender dashboard id',
|
|
88
|
+
'saved chart names or ids from charts list',
|
|
89
|
+
'clear split between shopper-facing UI and operator-facing analytics',
|
|
90
|
+
],
|
|
91
|
+
};
|
|
34
92
|
class TenderCliUsageError extends Error {
|
|
35
93
|
constructor(message) {
|
|
36
94
|
super(message);
|
|
@@ -121,7 +179,9 @@ Commands:
|
|
|
121
179
|
Approve manifest-declared outbound auth grants
|
|
122
180
|
tender app analytics summary <app-id>
|
|
123
181
|
Read artifact analytics totals and trend points
|
|
124
|
-
tender app analytics
|
|
182
|
+
tender app analytics authoring
|
|
183
|
+
Print the current analytics event authoring contract
|
|
184
|
+
tender app analytics query <app-id> --spec <file|->
|
|
125
185
|
Run a server-validated chart spec
|
|
126
186
|
tender app analytics suggestions <app-id>
|
|
127
187
|
Generate useful starter chart suggestions
|
|
@@ -171,6 +231,7 @@ Examples:
|
|
|
171
231
|
tender app outbound-auth list artifact_123 --json
|
|
172
232
|
tender app outbound-auth approve artifact_123 --all --confirm approve --json
|
|
173
233
|
tender app analytics summary artifact_123 --range 30d --json
|
|
234
|
+
tender app analytics authoring --json
|
|
174
235
|
tender app analytics query artifact_123 --spec chart.json --json
|
|
175
236
|
tender app analytics suggestions artifact_123 --range 30d --json
|
|
176
237
|
tender app db capabilities artifact_123 --json
|
|
@@ -344,12 +405,12 @@ Commands:
|
|
|
344
405
|
Rebuild the preview and optionally stream lifecycle events
|
|
345
406
|
tender app preview status <app-id>
|
|
346
407
|
Show preview job status by latest, commit, or job id
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
408
|
+
tender app preview watch <app-id>
|
|
409
|
+
Replay lifecycle events by latest, commit, or job id
|
|
410
|
+
tender app agent heartbeat <app-id>
|
|
411
|
+
Send a concise agent status update to the workspace
|
|
412
|
+
tender app publish <app-id>
|
|
413
|
+
Publish after dry-run or explicit confirmation
|
|
353
414
|
tender app publish status <app-id>
|
|
354
415
|
Show publish job status by job id
|
|
355
416
|
tender app publish watch <app-id>
|
|
@@ -360,10 +421,14 @@ Commands:
|
|
|
360
421
|
Approve manifest-declared outbound auth grants
|
|
361
422
|
tender app analytics summary <app-id>
|
|
362
423
|
Read artifact analytics totals and trend points
|
|
424
|
+
tender app analytics authoring
|
|
425
|
+
Print the current analytics event authoring contract
|
|
363
426
|
tender app analytics query <app-id> --spec <file|->
|
|
364
427
|
Run a server-validated chart spec
|
|
365
428
|
tender app analytics suggestions <app-id>
|
|
366
429
|
Generate useful starter chart suggestions
|
|
430
|
+
tender app analytics provision <app-id>
|
|
431
|
+
Print a starter dashboard provisioning plan
|
|
367
432
|
tender app analytics dashboards <app-id>
|
|
368
433
|
List or create saved dashboards
|
|
369
434
|
tender app analytics charts create <app-id>
|
|
@@ -423,13 +488,14 @@ Examples:
|
|
|
423
488
|
tender app outbound-auth list artifact_123 --json
|
|
424
489
|
tender app outbound-auth approve artifact_123 --all --confirm approve --json
|
|
425
490
|
tender app analytics summary artifact_123 --range 30d --json
|
|
491
|
+
tender app analytics authoring --json
|
|
426
492
|
tender app analytics query artifact_123 --spec chart.json --json
|
|
427
493
|
tender app analytics suggestions artifact_123 --range 30d --json
|
|
428
494
|
tender app analytics dashboards artifact_123 --json
|
|
429
495
|
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
|
|
496
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json
|
|
431
497
|
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
|
|
498
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
|
|
433
499
|
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
|
|
434
500
|
tender app analytics properties list artifact_123 --json
|
|
435
501
|
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
|
|
@@ -515,16 +581,20 @@ Examples:
|
|
|
515
581
|
tender version --json`;
|
|
516
582
|
}
|
|
517
583
|
function analyticsHelp() {
|
|
518
|
-
return `Usage: tender app analytics <command> <app-id> [options]
|
|
584
|
+
return `Usage: tender app analytics <command> [<app-id>] [options]
|
|
519
585
|
|
|
520
586
|
Start here for agents:
|
|
587
|
+
tender app analytics authoring --json
|
|
521
588
|
tender app analytics capabilities <app-id> --include-catalog --range 30d --json
|
|
522
589
|
|
|
523
590
|
Commands:
|
|
591
|
+
tender app analytics authoring Print the current event authoring contract
|
|
524
592
|
tender app analytics summary <app-id> Read totals and trend points
|
|
525
593
|
tender app analytics query <app-id> Run one chart spec
|
|
526
594
|
tender app analytics capabilities <app-id> Describe agent-safe analytics capabilities
|
|
527
595
|
tender app analytics suggestions <app-id> Generate starter chart suggestions
|
|
596
|
+
tender app analytics exact-funnel <app-id> Run exact unit funnel analysis
|
|
597
|
+
tender app analytics provision <app-id> Print a starter dashboard provisioning plan
|
|
528
598
|
tender app analytics dashboards <app-id> List or create dashboards
|
|
529
599
|
tender app analytics export <kind> <app-id> Export catalog metadata or query rows
|
|
530
600
|
tender app analytics charts templates Print reusable chart spec templates
|
|
@@ -536,22 +606,38 @@ Commands:
|
|
|
536
606
|
tender app analytics properties activate <app-id> Make a property queryable
|
|
537
607
|
|
|
538
608
|
Examples:
|
|
609
|
+
tender app analytics authoring --json
|
|
539
610
|
tender app analytics summary artifact_123 --range 30d --json
|
|
540
611
|
tender app analytics capabilities artifact_123 --include-catalog --json
|
|
541
612
|
tender app analytics query artifact_123 --spec chart.json --json
|
|
542
613
|
tender app analytics query artifact_123 --spec - --json < chart.json
|
|
614
|
+
tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json
|
|
543
615
|
tender app analytics export catalog artifact_123 --range 30d --json
|
|
544
616
|
tender app analytics export query artifact_123 --spec chart.json --format csv
|
|
545
617
|
tender app analytics suggestions artifact_123 --range 2026-04-01..2026-04-30 --json
|
|
618
|
+
tender app analytics provision artifact_123 --from-app --json
|
|
546
619
|
tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
|
|
547
620
|
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
|
|
621
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json
|
|
549
622
|
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
|
|
623
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
|
|
551
624
|
tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
|
|
552
625
|
tender app analytics properties list artifact_123 --json
|
|
553
626
|
tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json`;
|
|
554
627
|
}
|
|
628
|
+
function analyticsAuthoringHelp() {
|
|
629
|
+
return `Usage: tender app analytics authoring [--json]
|
|
630
|
+
|
|
631
|
+
Prints the current generated-app analytics event authoring contract. This command
|
|
632
|
+
does not require an app id, checkout, API token, or installed skill.
|
|
633
|
+
|
|
634
|
+
Agent guidance includes default generated-app analytics, replacement baseline
|
|
635
|
+
inspection, shopper/operator surface separation, and dashboard chart-list
|
|
636
|
+
completion criteria.
|
|
637
|
+
|
|
638
|
+
Examples:
|
|
639
|
+
tender app analytics authoring --json`;
|
|
640
|
+
}
|
|
555
641
|
function analyticsSummaryHelp() {
|
|
556
642
|
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
643
|
|
|
@@ -605,6 +691,28 @@ function analyticsSuggestionsHelp() {
|
|
|
605
691
|
Examples:
|
|
606
692
|
tender app analytics suggestions artifact_123 --range 30d --json`;
|
|
607
693
|
}
|
|
694
|
+
function analyticsExactFunnelHelp() {
|
|
695
|
+
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]
|
|
696
|
+
|
|
697
|
+
Runs an explicit raw-event unit funnel audit. This path dedupes by unit id and
|
|
698
|
+
flow step from artifact-scoped raw analytics rows; it is not used for normal
|
|
699
|
+
dashboard page loads.
|
|
700
|
+
|
|
701
|
+
Examples:
|
|
702
|
+
tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json`;
|
|
703
|
+
}
|
|
704
|
+
function analyticsProvisionHelp() {
|
|
705
|
+
return `Usage: tender app analytics provision <app-id> --from-app [--dashboard-name <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
706
|
+
|
|
707
|
+
Prints a machine-readable provisioning plan for the default generated-app
|
|
708
|
+
analytics delivery contract. This command does not mutate dashboards yet; it
|
|
709
|
+
returns the exact commands, starter chart specs, completion criteria, and
|
|
710
|
+
blockers an external coding agent should follow.
|
|
711
|
+
|
|
712
|
+
Examples:
|
|
713
|
+
tender app analytics provision artifact_123 --from-app --json
|
|
714
|
+
tender app analytics provision artifact_123 --from-app --dashboard-name "Recommendation dashboard" --json`;
|
|
715
|
+
}
|
|
608
716
|
function analyticsDashboardsHelp() {
|
|
609
717
|
return `Usage: tender app analytics dashboards <app-id> [--create <name>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
610
718
|
|
|
@@ -630,8 +738,8 @@ Property charts automatically make required custom event properties queryable be
|
|
|
630
738
|
saving when the spec has an event_name = filter.
|
|
631
739
|
|
|
632
740
|
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`;
|
|
741
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --dry-run --json
|
|
742
|
+
tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json`;
|
|
635
743
|
}
|
|
636
744
|
function analyticsChartsListHelp() {
|
|
637
745
|
return `Usage: tender app analytics charts list <app-id> --dashboard <dashboard-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -649,8 +757,8 @@ saving when the spec has an event_name = filter.
|
|
|
649
757
|
|
|
650
758
|
Examples:
|
|
651
759
|
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`;
|
|
760
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --spec unit-funnel.json --dry-run --json
|
|
761
|
+
tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec - --json < unit-funnel.json`;
|
|
654
762
|
}
|
|
655
763
|
function analyticsChartsDeleteHelp() {
|
|
656
764
|
return `Usage: tender app analytics charts delete <app-id> --chart <chart-id> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -1064,6 +1172,15 @@ Examples:
|
|
|
1064
1172
|
tender app agent heartbeat artifact_123 --source claude-code --status needs_attention --summary "Waiting for an API key decision" --requested-action answer_question --json
|
|
1065
1173
|
tender app agent heartbeat artifact_123 --input - --json < heartbeat.json`;
|
|
1066
1174
|
}
|
|
1175
|
+
function appAgentHelp() {
|
|
1176
|
+
return `Usage: tender app agent <command>
|
|
1177
|
+
|
|
1178
|
+
Commands:
|
|
1179
|
+
heartbeat <app-id> Send a concise agent status update to the workspace.
|
|
1180
|
+
|
|
1181
|
+
Examples:
|
|
1182
|
+
tender app agent heartbeat artifact_123 --source codex --status working --summary "Reading files" --json`;
|
|
1183
|
+
}
|
|
1067
1184
|
function publishStatusHelp() {
|
|
1068
1185
|
return `Usage: tender app publish status <app-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
1069
1186
|
|
|
@@ -1135,6 +1252,14 @@ function tenderCliCapabilities() {
|
|
|
1135
1252
|
persistence: 'session_only',
|
|
1136
1253
|
writesToCheckout: false,
|
|
1137
1254
|
},
|
|
1255
|
+
analyticsAuthoring: {
|
|
1256
|
+
command: ANALYTICS_AUTHORING_COMMAND,
|
|
1257
|
+
playbookId: ANALYTICS_AUTHORING_PLAYBOOK_ID,
|
|
1258
|
+
playbookCommand: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
|
|
1259
|
+
doctorCommand: 'tender app doctor --dir <dir> --json',
|
|
1260
|
+
purpose: 'Keep generated app analytics events compatible with Tender dashboards and unit funnels.',
|
|
1261
|
+
guidance: ANALYTICS_AGENT_GUIDANCE,
|
|
1262
|
+
},
|
|
1138
1263
|
description: 'Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.',
|
|
1139
1264
|
discovery: {
|
|
1140
1265
|
primary: 'npm exec --yes @tenderprompt/cli@latest -- capabilities --json',
|
|
@@ -1145,6 +1270,7 @@ function tenderCliCapabilities() {
|
|
|
1145
1270
|
'tender memory --help',
|
|
1146
1271
|
'tender playbooks --help',
|
|
1147
1272
|
'tender app --help',
|
|
1273
|
+
'tender app agent --help',
|
|
1148
1274
|
'tender app agent heartbeat --help',
|
|
1149
1275
|
'tender app share --help',
|
|
1150
1276
|
'tender app claim --help',
|
|
@@ -1244,6 +1370,9 @@ function tenderCliCapabilities() {
|
|
|
1244
1370
|
name: 'analytics_agent_loop',
|
|
1245
1371
|
when: 'When understanding app analytics or creating saved charts.',
|
|
1246
1372
|
commands: [
|
|
1373
|
+
ANALYTICS_AUTHORING_COMMAND,
|
|
1374
|
+
ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
|
|
1375
|
+
'tender app analytics provision <artifact-id> --from-app --json',
|
|
1247
1376
|
'tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json',
|
|
1248
1377
|
'tender app analytics charts templates --json',
|
|
1249
1378
|
'tender app analytics query <artifact-id> --spec chart.json --json',
|
|
@@ -1257,10 +1386,15 @@ function tenderCliCapabilities() {
|
|
|
1257
1386
|
'tender app analytics charts delete <artifact-id> --chart <chart-id> --dry-run --json',
|
|
1258
1387
|
],
|
|
1259
1388
|
notes: [
|
|
1389
|
+
'Run authoring before adding or repairing analytics instrumentation; use capabilities for artifact-scoped read/query details.',
|
|
1390
|
+
'Generated apps should get useful Tender analytics events and saved starter dashboard charts by default unless the user opts out or a concrete blocker is recorded.',
|
|
1391
|
+
'For replacement work, inspect the baseline customer-facing page or widget before source edits; Codex should prefer browser:browser when available.',
|
|
1392
|
+
'Tender analytics dashboards are operator-facing surfaces; do not render dashboards, A/B reports, sessions, or internal counters in shopper UI unless the user explicitly requests a secured admin surface.',
|
|
1260
1393
|
'Combine CLI capabilities with local source inspection of analytics calls.',
|
|
1261
1394
|
'Validate chart specs with query before creating or updating saved charts.',
|
|
1262
1395
|
'Property chart create/update commands automatically make missing custom event properties queryable when the spec has an event_name = filter.',
|
|
1263
1396
|
'Ad hoc query is read-only; if it reports a missing dashboard-query property, activate the property first or save the chart through charts create/update.',
|
|
1397
|
+
'Dashboard setup is not complete until saved charts exist and tender app analytics charts list returns chart names or ids.',
|
|
1264
1398
|
'Use list before writes so agents can avoid duplicates and explain the exact chart they are changing.',
|
|
1265
1399
|
],
|
|
1266
1400
|
},
|
|
@@ -1383,6 +1517,18 @@ function tenderCliCapabilities() {
|
|
|
1383
1517
|
requiresAuth: false,
|
|
1384
1518
|
writes: false,
|
|
1385
1519
|
},
|
|
1520
|
+
{
|
|
1521
|
+
command: ANALYTICS_AUTHORING_COMMAND,
|
|
1522
|
+
purpose: 'Print the generated-app analytics event payload contract for unit funnels and dashboard-ready events.',
|
|
1523
|
+
requiresAuth: false,
|
|
1524
|
+
writes: false,
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
command: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
|
|
1528
|
+
purpose: 'Fetch the remote analytics authoring playbook for examples and repair patterns.',
|
|
1529
|
+
requiresAuth: true,
|
|
1530
|
+
writes: false,
|
|
1531
|
+
},
|
|
1386
1532
|
{
|
|
1387
1533
|
command: 'tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json',
|
|
1388
1534
|
purpose: 'Discover artifact-scoped analytics commands, chart specs, saved dashboards, and bounded observed catalog.',
|
|
@@ -1395,6 +1541,12 @@ function tenderCliCapabilities() {
|
|
|
1395
1541
|
requiresAuth: false,
|
|
1396
1542
|
writes: false,
|
|
1397
1543
|
},
|
|
1544
|
+
{
|
|
1545
|
+
command: 'tender app analytics provision <artifact-id> --from-app --json',
|
|
1546
|
+
purpose: 'Print a guided starter dashboard provisioning plan with chart-list completion proof.',
|
|
1547
|
+
requiresAuth: false,
|
|
1548
|
+
writes: false,
|
|
1549
|
+
},
|
|
1398
1550
|
{
|
|
1399
1551
|
command: 'tender app analytics properties list <artifact-id> --json',
|
|
1400
1552
|
purpose: 'List custom event properties available to dashboard queries.',
|
|
@@ -3937,6 +4089,20 @@ function parseAnalyticsCapabilitiesArgs(args) {
|
|
|
3937
4089
|
...parsed,
|
|
3938
4090
|
};
|
|
3939
4091
|
}
|
|
4092
|
+
function parseAnalyticsAuthoringArgs(args) {
|
|
4093
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
4094
|
+
return { command: 'help', topic: 'app analytics authoring' };
|
|
4095
|
+
}
|
|
4096
|
+
let json = false;
|
|
4097
|
+
for (const arg of args) {
|
|
4098
|
+
if (arg === '--json') {
|
|
4099
|
+
json = true;
|
|
4100
|
+
continue;
|
|
4101
|
+
}
|
|
4102
|
+
throw new TenderCliUsageError(`Unknown analytics authoring option: ${arg}`);
|
|
4103
|
+
}
|
|
4104
|
+
return { command: 'app analytics authoring', json };
|
|
4105
|
+
}
|
|
3940
4106
|
function parseAnalyticsSuggestionsArgs(args) {
|
|
3941
4107
|
if (args.includes('--help') || args.includes('-h')) {
|
|
3942
4108
|
return { command: 'help', topic: 'app analytics suggestions' };
|
|
@@ -3958,6 +4124,53 @@ function parseAnalyticsSuggestionsArgs(args) {
|
|
|
3958
4124
|
}
|
|
3959
4125
|
return { command: 'app analytics suggestions', artifactId, range, ...parsed };
|
|
3960
4126
|
}
|
|
4127
|
+
function parseAnalyticsSteps(value) {
|
|
4128
|
+
const raw = parseRequiredFlagValue(value, '--steps');
|
|
4129
|
+
const steps = raw
|
|
4130
|
+
.split(',')
|
|
4131
|
+
.map((step) => step.trim())
|
|
4132
|
+
.filter((step) => step.length > 0);
|
|
4133
|
+
if (steps.length < 2 || steps.length > 25 || new Set(steps).size !== steps.length) {
|
|
4134
|
+
throw new TenderCliUsageError('--steps must contain 2-25 unique comma-separated flow steps.');
|
|
4135
|
+
}
|
|
4136
|
+
return steps;
|
|
4137
|
+
}
|
|
4138
|
+
function parseAnalyticsExactFunnelArgs(args) {
|
|
4139
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
4140
|
+
return { command: 'help', topic: 'app analytics exact-funnel' };
|
|
4141
|
+
}
|
|
4142
|
+
const artifactId = args[0];
|
|
4143
|
+
if (!artifactId || artifactId.startsWith('-')) {
|
|
4144
|
+
throw new TenderCliUsageError('app id is required. Example: tender app analytics exact-funnel <app-id> --flow checkout_flow --steps started,completed --range 30d --json');
|
|
4145
|
+
}
|
|
4146
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
4147
|
+
let range = '7d';
|
|
4148
|
+
let flowId = null;
|
|
4149
|
+
let steps = null;
|
|
4150
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
4151
|
+
const arg = parsed.remaining[index];
|
|
4152
|
+
if (arg === '--range') {
|
|
4153
|
+
range = parseAnalyticsRange(parsed.remaining[index + 1]);
|
|
4154
|
+
index += 1;
|
|
4155
|
+
continue;
|
|
4156
|
+
}
|
|
4157
|
+
if (arg === '--flow') {
|
|
4158
|
+
flowId = parseRequiredFlagValue(parsed.remaining[index + 1], '--flow');
|
|
4159
|
+
index += 1;
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
if (arg === '--steps') {
|
|
4163
|
+
steps = parseAnalyticsSteps(parsed.remaining[index + 1]);
|
|
4164
|
+
index += 1;
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
throw new TenderCliUsageError(`Unknown analytics exact-funnel option: ${arg}`);
|
|
4168
|
+
}
|
|
4169
|
+
if (!flowId || !steps) {
|
|
4170
|
+
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');
|
|
4171
|
+
}
|
|
4172
|
+
return { command: 'app analytics exact-funnel', artifactId, flowId, steps, range, ...parsed };
|
|
4173
|
+
}
|
|
3961
4174
|
function parseAnalyticsDashboardsArgs(args) {
|
|
3962
4175
|
if (args.includes('--help') || args.includes('-h')) {
|
|
3963
4176
|
return { command: 'help', topic: 'app analytics dashboards' };
|
|
@@ -3987,6 +4200,35 @@ function parseAnalyticsDashboardsArgs(args) {
|
|
|
3987
4200
|
}
|
|
3988
4201
|
return { command: 'app analytics dashboards', artifactId, name, dryRun, ...parsed };
|
|
3989
4202
|
}
|
|
4203
|
+
function parseAnalyticsProvisionArgs(args) {
|
|
4204
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
4205
|
+
return { command: 'help', topic: 'app analytics provision' };
|
|
4206
|
+
}
|
|
4207
|
+
const artifactId = args[0];
|
|
4208
|
+
if (!artifactId || artifactId.startsWith('-')) {
|
|
4209
|
+
throw new TenderCliUsageError('app id is required. Example: tender app analytics provision <app-id> --from-app --json');
|
|
4210
|
+
}
|
|
4211
|
+
const parsed = parseAnalyticsCommonArgs(args, 1);
|
|
4212
|
+
let fromApp = false;
|
|
4213
|
+
let dashboardName = 'Agent dashboard';
|
|
4214
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
4215
|
+
const arg = parsed.remaining[index];
|
|
4216
|
+
if (arg === '--from-app') {
|
|
4217
|
+
fromApp = true;
|
|
4218
|
+
continue;
|
|
4219
|
+
}
|
|
4220
|
+
if (arg === '--dashboard-name') {
|
|
4221
|
+
dashboardName = parseRequiredFlagValue(parsed.remaining[index + 1], '--dashboard-name');
|
|
4222
|
+
index += 1;
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
throw new TenderCliUsageError(`Unknown analytics provision option: ${arg}`);
|
|
4226
|
+
}
|
|
4227
|
+
if (!fromApp) {
|
|
4228
|
+
throw new TenderCliUsageError('--from-app is required. Example: tender app analytics provision artifact_123 --from-app --json');
|
|
4229
|
+
}
|
|
4230
|
+
return { command: 'app analytics provision', artifactId, fromApp, dashboardName, ...parsed };
|
|
4231
|
+
}
|
|
3990
4232
|
function parseAnalyticsChartsTemplatesArgs(args) {
|
|
3991
4233
|
if (args.includes('--help') || args.includes('-h')) {
|
|
3992
4234
|
return { command: 'help', topic: 'app analytics charts templates' };
|
|
@@ -4079,6 +4321,124 @@ function parseAnalyticsExportQueryArgs(args) {
|
|
|
4079
4321
|
...parsed,
|
|
4080
4322
|
};
|
|
4081
4323
|
}
|
|
4324
|
+
function analyticsAuthoringContract() {
|
|
4325
|
+
return {
|
|
4326
|
+
ok: true,
|
|
4327
|
+
surface: 'analytics_authoring',
|
|
4328
|
+
command: 'app analytics authoring',
|
|
4329
|
+
version: 1,
|
|
4330
|
+
playbook: {
|
|
4331
|
+
id: ANALYTICS_AUTHORING_PLAYBOOK_ID,
|
|
4332
|
+
command: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
|
|
4333
|
+
useWhen: [
|
|
4334
|
+
'Creating a generated app with custom analytics.',
|
|
4335
|
+
'Repairing dashboards or funnels that cannot dedupe a shopper journey.',
|
|
4336
|
+
'Retrofitting an existing app before saving unit funnel charts.',
|
|
4337
|
+
'Publishing Shopify Customer Events while keeping Tender dashboards complete.',
|
|
4338
|
+
],
|
|
4339
|
+
},
|
|
4340
|
+
doctor: {
|
|
4341
|
+
command: 'tender app doctor --dir <dir> --json',
|
|
4342
|
+
diagnosticCodes: [
|
|
4343
|
+
'analytics_reserved_unit_step_event',
|
|
4344
|
+
'analytics_reserved_event_type_field',
|
|
4345
|
+
'analytics_metadata_nested_in_properties',
|
|
4346
|
+
'analytics_missing_unit_flow_metadata',
|
|
4347
|
+
'analytics_tender_tracking_skipped_after_shopify_publish',
|
|
4348
|
+
'analytics_unstable_unit_id',
|
|
4349
|
+
],
|
|
4350
|
+
},
|
|
4351
|
+
agentGuidance: ANALYTICS_AGENT_GUIDANCE,
|
|
4352
|
+
analyticsAuthoring: {
|
|
4353
|
+
payloadShape: {
|
|
4354
|
+
event: 'custom_event_name',
|
|
4355
|
+
unit: { type: 'quiz_attempt', id: 'stable-attempt-id' },
|
|
4356
|
+
flow: {
|
|
4357
|
+
id: 'recommendation_quiz',
|
|
4358
|
+
step: 'quiz_completed',
|
|
4359
|
+
order: 3,
|
|
4360
|
+
role: 'outcome',
|
|
4361
|
+
},
|
|
4362
|
+
properties: {
|
|
4363
|
+
experiment_id: 'quiz_copy_v1',
|
|
4364
|
+
experiment_variant: 'guided',
|
|
4365
|
+
score: 84,
|
|
4366
|
+
},
|
|
4367
|
+
},
|
|
4368
|
+
rules: [
|
|
4369
|
+
'Keep event names business-specific and do not use platform-reserved names such as unit_step.',
|
|
4370
|
+
'Put unit and flow metadata at the top level of the analytics payload, not inside properties.',
|
|
4371
|
+
'Use one stable unit.id for the user journey being measured; do not regenerate it for each click.',
|
|
4372
|
+
'Use flow.role values start, milestone, activity, outcome, or error.',
|
|
4373
|
+
'Do not emit event_type from generated app code; the platform derives it.',
|
|
4374
|
+
'When also publishing Shopify Customer Events, still send Tender analytics for Tender dashboards.',
|
|
4375
|
+
'Keep properties low-cardinality and dashboard-safe; avoid PII, secrets, free text, ids, and URLs unless explicitly approved.',
|
|
4376
|
+
],
|
|
4377
|
+
propertyStates: ['tracked', 'observed', 'queryable'],
|
|
4378
|
+
queryablePropertyCommand: 'tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type <dimension|metric> --dry-run --json',
|
|
4379
|
+
},
|
|
4380
|
+
examples: [
|
|
4381
|
+
{
|
|
4382
|
+
scenario: 'game attempt',
|
|
4383
|
+
payload: {
|
|
4384
|
+
event: 'level_completed',
|
|
4385
|
+
unit: { type: 'game_attempt', id: 'attempt-01HXYZ' },
|
|
4386
|
+
flow: { id: 'pasta_game', step: 'level_completed', order: 3, role: 'milestone' },
|
|
4387
|
+
properties: { level: 2, score: 1270, experiment_variant: 'timer_v2' },
|
|
4388
|
+
},
|
|
4389
|
+
},
|
|
4390
|
+
{
|
|
4391
|
+
scenario: 'recommendation quiz',
|
|
4392
|
+
payload: {
|
|
4393
|
+
event: 'quiz_completed',
|
|
4394
|
+
unit: { type: 'quiz_attempt', id: 'quiz-session-42' },
|
|
4395
|
+
flow: { id: 'recommendation_quiz', step: 'quiz_completed', order: 4, role: 'outcome' },
|
|
4396
|
+
properties: { result: 'starter_bundle', score: 84 },
|
|
4397
|
+
},
|
|
4398
|
+
},
|
|
4399
|
+
{
|
|
4400
|
+
scenario: 'bundle builder',
|
|
4401
|
+
payload: {
|
|
4402
|
+
event: 'bundle_checkout_clicked',
|
|
4403
|
+
unit: { type: 'bundle_build', id: 'bundle-build-42' },
|
|
4404
|
+
flow: { id: 'bundle_builder', step: 'checkout_clicked', order: 5, role: 'outcome' },
|
|
4405
|
+
properties: { selected_items: 6, cadence: 'monthly', variant: 'guided' },
|
|
4406
|
+
},
|
|
4407
|
+
},
|
|
4408
|
+
{
|
|
4409
|
+
scenario: 'Shopify Customer Events duplication',
|
|
4410
|
+
notes: [
|
|
4411
|
+
'Publish consent-aware Shopify Customer Events when the embed runs in Shopify.',
|
|
4412
|
+
'Do not return before sending the Tender analytics event needed by Tender dashboards.',
|
|
4413
|
+
],
|
|
4414
|
+
payload: {
|
|
4415
|
+
event: 'product_match_submitted',
|
|
4416
|
+
unit: { type: 'quiz_attempt', id: 'quiz-session-42' },
|
|
4417
|
+
flow: { id: 'recommendation_quiz', step: 'submitted', order: 3, role: 'milestone' },
|
|
4418
|
+
properties: { shopify_customer_event: 'product_match_submitted' },
|
|
4419
|
+
},
|
|
4420
|
+
},
|
|
4421
|
+
],
|
|
4422
|
+
charting: {
|
|
4423
|
+
starterDashboardCommands: ANALYTICS_AGENT_GUIDANCE.starterDashboardCommands,
|
|
4424
|
+
completionProof: ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.proofCommand,
|
|
4425
|
+
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.',
|
|
4426
|
+
exactAudit: 'Use tender app analytics exact-funnel <app-id> --flow <flow-id> --steps <step-a,step-b> --json for repair and low-volume audits.',
|
|
4427
|
+
},
|
|
4428
|
+
};
|
|
4429
|
+
}
|
|
4430
|
+
function runAnalyticsAuthoring(command, io) {
|
|
4431
|
+
const payload = analyticsAuthoringContract();
|
|
4432
|
+
printAnalyticsPayload(command, io, payload, [
|
|
4433
|
+
'Analytics authoring contract',
|
|
4434
|
+
'',
|
|
4435
|
+
`command: ${ANALYTICS_AUTHORING_COMMAND}`,
|
|
4436
|
+
`playbook: ${ANALYTICS_AUTHORING_PLAYBOOK_COMMAND}`,
|
|
4437
|
+
'payload: event plus top-level unit, flow, and properties',
|
|
4438
|
+
'doctor: tender app doctor --dir <dir> --json',
|
|
4439
|
+
]);
|
|
4440
|
+
return 0;
|
|
4441
|
+
}
|
|
4082
4442
|
function analyticsChartTemplates() {
|
|
4083
4443
|
return [
|
|
4084
4444
|
{
|
|
@@ -4145,30 +4505,30 @@ function analyticsChartTemplates() {
|
|
|
4145
4505
|
},
|
|
4146
4506
|
},
|
|
4147
4507
|
{
|
|
4148
|
-
id: '
|
|
4149
|
-
title: '
|
|
4150
|
-
visualization: '
|
|
4151
|
-
purpose: '
|
|
4508
|
+
id: 'milestone_event_totals',
|
|
4509
|
+
title: 'Milestone event totals',
|
|
4510
|
+
visualization: 'table',
|
|
4511
|
+
purpose: 'Compare ordered milestone event totals without treating them as conversion rates.',
|
|
4152
4512
|
usableAsIs: false,
|
|
4153
4513
|
placeholders: {
|
|
4154
|
-
eventNames: ['<start_event>', '<middle_event>', '<
|
|
4514
|
+
eventNames: ['<start_event>', '<middle_event>', '<outcome_event>'],
|
|
4155
4515
|
},
|
|
4156
4516
|
adaptWith: [
|
|
4157
|
-
'Replace placeholders with
|
|
4517
|
+
'Replace placeholders with observed event names from catalog classification or source order.',
|
|
4158
4518
|
'Use only event names observed for the selected app.',
|
|
4159
|
-
'
|
|
4519
|
+
'Use unit_funnel instead when the app emits unit and flow metadata.',
|
|
4160
4520
|
],
|
|
4161
4521
|
spec: {
|
|
4162
4522
|
version: 1,
|
|
4163
4523
|
dataset: 'events',
|
|
4164
|
-
visualization: '
|
|
4524
|
+
visualization: 'table',
|
|
4165
4525
|
metric: { name: 'custom_event_count' },
|
|
4166
4526
|
groupBy: [{ field: 'event_name' }],
|
|
4167
4527
|
filters: [
|
|
4168
4528
|
{
|
|
4169
4529
|
field: 'event_name',
|
|
4170
4530
|
op: 'in',
|
|
4171
|
-
value: ['<start_event>', '<middle_event>', '<
|
|
4531
|
+
value: ['<start_event>', '<middle_event>', '<outcome_event>'],
|
|
4172
4532
|
},
|
|
4173
4533
|
],
|
|
4174
4534
|
range: { preset: '30d' },
|
|
@@ -4177,30 +4537,65 @@ function analyticsChartTemplates() {
|
|
|
4177
4537
|
},
|
|
4178
4538
|
},
|
|
4179
4539
|
{
|
|
4180
|
-
id: '
|
|
4181
|
-
title: '
|
|
4540
|
+
id: 'unit_funnel',
|
|
4541
|
+
title: 'Unit funnel',
|
|
4182
4542
|
visualization: 'funnel',
|
|
4183
|
-
purpose: 'Measure ordered
|
|
4543
|
+
purpose: 'Measure ordered progression with one reach per unit and step.',
|
|
4184
4544
|
usableAsIs: false,
|
|
4185
4545
|
placeholders: {
|
|
4186
|
-
|
|
4546
|
+
flowId: '<flow_id>',
|
|
4547
|
+
unitType: '<unit_type>',
|
|
4548
|
+
steps: ['<start_step>', '<middle_step>', '<outcome_step>'],
|
|
4187
4549
|
},
|
|
4188
4550
|
adaptWith: [
|
|
4189
|
-
'Use when
|
|
4551
|
+
'Use only when the app emits unit and flow metadata and capabilities show unit-step rows.',
|
|
4552
|
+
'Replace placeholders with ordered flow steps, usually matching event names.',
|
|
4553
|
+
'Use activity charts for repeated actions instead of adding raw activity to this funnel.',
|
|
4554
|
+
],
|
|
4555
|
+
spec: {
|
|
4556
|
+
version: 2,
|
|
4557
|
+
dataset: 'events',
|
|
4558
|
+
visualization: 'funnel',
|
|
4559
|
+
metric: { name: 'unit_step_count' },
|
|
4560
|
+
funnel: {
|
|
4561
|
+
flowId: '<flow_id>',
|
|
4562
|
+
unit: { source: 'unit_id', type: '<unit_type>' },
|
|
4563
|
+
steps: [
|
|
4564
|
+
{ event: '<start_step>', label: 'Started' },
|
|
4565
|
+
{ event: '<middle_step>', label: 'Reached milestone' },
|
|
4566
|
+
{ event: '<outcome_step>', label: 'Completed' },
|
|
4567
|
+
],
|
|
4568
|
+
},
|
|
4569
|
+
filters: [],
|
|
4570
|
+
range: { preset: '30d' },
|
|
4571
|
+
limit: 3,
|
|
4572
|
+
},
|
|
4573
|
+
},
|
|
4574
|
+
{
|
|
4575
|
+
id: 'route_request_totals',
|
|
4576
|
+
title: 'Route request totals',
|
|
4577
|
+
visualization: 'table',
|
|
4578
|
+
purpose: 'Compare observed request path totals without treating them as conversion rates.',
|
|
4579
|
+
usableAsIs: false,
|
|
4580
|
+
placeholders: {
|
|
4581
|
+
paths: ['/<start_path>', '/<middle_path>', '/<outcome_path>'],
|
|
4582
|
+
},
|
|
4583
|
+
adaptWith: [
|
|
4584
|
+
'Use when the source code shows ordered route milestones but custom events are unavailable.',
|
|
4190
4585
|
'Replace placeholders with exact observed paths from the selected app.',
|
|
4191
|
-
'
|
|
4586
|
+
'Use custom events with unit and flow metadata for conversion funnels.',
|
|
4192
4587
|
],
|
|
4193
4588
|
spec: {
|
|
4194
4589
|
version: 1,
|
|
4195
4590
|
dataset: 'events',
|
|
4196
|
-
visualization: '
|
|
4591
|
+
visualization: 'table',
|
|
4197
4592
|
metric: { name: 'request_count' },
|
|
4198
4593
|
groupBy: [{ field: 'path' }],
|
|
4199
4594
|
filters: [
|
|
4200
4595
|
{
|
|
4201
4596
|
field: 'path',
|
|
4202
4597
|
op: 'in',
|
|
4203
|
-
value: ['/<start_path>', '/<middle_path>', '/<
|
|
4598
|
+
value: ['/<start_path>', '/<middle_path>', '/<outcome_path>'],
|
|
4204
4599
|
},
|
|
4205
4600
|
],
|
|
4206
4601
|
range: { preset: '30d' },
|
|
@@ -4208,6 +4603,33 @@ function analyticsChartTemplates() {
|
|
|
4208
4603
|
limit: 3,
|
|
4209
4604
|
},
|
|
4210
4605
|
},
|
|
4606
|
+
{
|
|
4607
|
+
id: 'activity_rate',
|
|
4608
|
+
title: 'Activity rate',
|
|
4609
|
+
visualization: 'number',
|
|
4610
|
+
purpose: 'Show an average repeated-activity total recorded on an outcome event.',
|
|
4611
|
+
usableAsIs: false,
|
|
4612
|
+
placeholders: {
|
|
4613
|
+
eventName: '<outcome_event>',
|
|
4614
|
+
metric: 'avg:<queryable_metric_property>',
|
|
4615
|
+
},
|
|
4616
|
+
adaptWith: [
|
|
4617
|
+
'Use when the outcome event records a numeric total such as throws, answers, edits, duration_ms, or attempts.',
|
|
4618
|
+
'Activate the metric property for the selected outcome event before dashboard queries.',
|
|
4619
|
+
'Use raw activity volume charts when the app only emits repeated activity events and no per-unit total.',
|
|
4620
|
+
],
|
|
4621
|
+
spec: {
|
|
4622
|
+
version: 1,
|
|
4623
|
+
dataset: 'events',
|
|
4624
|
+
visualization: 'number',
|
|
4625
|
+
metric: { name: 'avg:<queryable_metric_property>' },
|
|
4626
|
+
groupBy: [],
|
|
4627
|
+
filters: [{ field: 'event_name', op: '=', value: '<outcome_event>' }],
|
|
4628
|
+
range: { preset: '30d' },
|
|
4629
|
+
sort: [],
|
|
4630
|
+
limit: 1,
|
|
4631
|
+
},
|
|
4632
|
+
},
|
|
4211
4633
|
{
|
|
4212
4634
|
id: 'property_breakdown',
|
|
4213
4635
|
title: 'Property breakdown',
|
|
@@ -4235,6 +4657,42 @@ function analyticsChartTemplates() {
|
|
|
4235
4657
|
limit: 10,
|
|
4236
4658
|
},
|
|
4237
4659
|
},
|
|
4660
|
+
{
|
|
4661
|
+
id: 'experiment_readout',
|
|
4662
|
+
title: 'Experiment readout',
|
|
4663
|
+
visualization: 'table',
|
|
4664
|
+
purpose: 'Compare selected milestones by experiment variant in one table.',
|
|
4665
|
+
usableAsIs: false,
|
|
4666
|
+
placeholders: {
|
|
4667
|
+
eventNames: ['<exposure_event>', '<started_event>', '<outcome_event>'],
|
|
4668
|
+
property: 'experiment_variant',
|
|
4669
|
+
},
|
|
4670
|
+
adaptWith: [
|
|
4671
|
+
'Use when each selected event has an active low-cardinality experiment_variant property mapping.',
|
|
4672
|
+
'Keep this as a table; do not render raw event totals as conversion percentages.',
|
|
4673
|
+
'Use exact analysis for low-volume winner decisions.',
|
|
4674
|
+
],
|
|
4675
|
+
spec: {
|
|
4676
|
+
version: 1,
|
|
4677
|
+
dataset: 'events',
|
|
4678
|
+
visualization: 'table',
|
|
4679
|
+
metric: { name: 'custom_event_count' },
|
|
4680
|
+
groupBy: [
|
|
4681
|
+
{ field: 'event_name' },
|
|
4682
|
+
{ field: 'property:experiment_variant' },
|
|
4683
|
+
],
|
|
4684
|
+
filters: [
|
|
4685
|
+
{
|
|
4686
|
+
field: 'event_name',
|
|
4687
|
+
op: 'in',
|
|
4688
|
+
value: ['<exposure_event>', '<started_event>', '<outcome_event>'],
|
|
4689
|
+
},
|
|
4690
|
+
],
|
|
4691
|
+
range: { preset: '30d' },
|
|
4692
|
+
sort: [{ field: 'event_name', direction: 'asc' }],
|
|
4693
|
+
limit: 50,
|
|
4694
|
+
},
|
|
4695
|
+
},
|
|
4238
4696
|
{
|
|
4239
4697
|
id: 'average_metric_property',
|
|
4240
4698
|
title: 'Average metric property',
|
|
@@ -4319,6 +4777,100 @@ function runAnalyticsChartsTemplates(command, io) {
|
|
|
4319
4777
|
}
|
|
4320
4778
|
return 0;
|
|
4321
4779
|
}
|
|
4780
|
+
function quoteCliArgument(value) {
|
|
4781
|
+
return JSON.stringify(value);
|
|
4782
|
+
}
|
|
4783
|
+
function buildAnalyticsProvisionPayload(command) {
|
|
4784
|
+
const starterTemplateIds = new Set(['daily_custom_events', 'top_custom_events']);
|
|
4785
|
+
const starterCharts = analyticsChartTemplates()
|
|
4786
|
+
.filter((template) => starterTemplateIds.has(template.id))
|
|
4787
|
+
.map((template) => ({
|
|
4788
|
+
templateId: template.id,
|
|
4789
|
+
title: template.title,
|
|
4790
|
+
purpose: template.purpose,
|
|
4791
|
+
spec: template.spec,
|
|
4792
|
+
}));
|
|
4793
|
+
const dashboardName = quoteCliArgument(command.dashboardName);
|
|
4794
|
+
return {
|
|
4795
|
+
ok: true,
|
|
4796
|
+
command: command.command,
|
|
4797
|
+
version: 1,
|
|
4798
|
+
artifactId: command.artifactId,
|
|
4799
|
+
mode: 'guided_provisioning_plan',
|
|
4800
|
+
writes: false,
|
|
4801
|
+
fromApp: command.fromApp,
|
|
4802
|
+
dashboardName: command.dashboardName,
|
|
4803
|
+
description: 'Machine-readable starter dashboard provisioning plan for external coding agents. This command does not mutate dashboards yet.',
|
|
4804
|
+
agentGuidance: ANALYTICS_AGENT_GUIDANCE,
|
|
4805
|
+
starterCharts,
|
|
4806
|
+
steps: [
|
|
4807
|
+
{
|
|
4808
|
+
id: 'read_authoring_contract',
|
|
4809
|
+
command: ANALYTICS_AUTHORING_COMMAND,
|
|
4810
|
+
completion: 'Generated app analytics payload contract and surface boundary are loaded.',
|
|
4811
|
+
},
|
|
4812
|
+
{
|
|
4813
|
+
id: 'inspect_app_analytics',
|
|
4814
|
+
commands: [
|
|
4815
|
+
`tender app analytics capabilities ${command.artifactId} --include-catalog --range 30d --json`,
|
|
4816
|
+
'tender app doctor --dir . --json',
|
|
4817
|
+
],
|
|
4818
|
+
completion: 'Observed events, unit/flow metadata, existing dashboards, and local analytics diagnostics are known.',
|
|
4819
|
+
},
|
|
4820
|
+
{
|
|
4821
|
+
id: 'choose_starter_charts',
|
|
4822
|
+
command: 'tender app analytics charts templates --json',
|
|
4823
|
+
defaultTemplates: ['daily_custom_events', 'top_custom_events'],
|
|
4824
|
+
completion: 'Starter chart specs are adapted to observed events or accepted as generic app-owned event charts.',
|
|
4825
|
+
},
|
|
4826
|
+
{
|
|
4827
|
+
id: 'create_or_reuse_dashboard',
|
|
4828
|
+
dryRunCommand: `tender app analytics dashboards ${command.artifactId} --create ${dashboardName} --dry-run --json`,
|
|
4829
|
+
writeCommand: `tender app analytics dashboards ${command.artifactId} --create ${dashboardName} --json`,
|
|
4830
|
+
completion: 'A dashboard id is available, either reused from capabilities or returned by the create command.',
|
|
4831
|
+
},
|
|
4832
|
+
{
|
|
4833
|
+
id: 'validate_chart_specs',
|
|
4834
|
+
command: `tender app analytics query ${command.artifactId} --spec chart.json --json`,
|
|
4835
|
+
completion: 'Each chart spec is server-validated before it is saved.',
|
|
4836
|
+
},
|
|
4837
|
+
{
|
|
4838
|
+
id: 'create_saved_charts',
|
|
4839
|
+
dryRunCommand: `tender app analytics charts create ${command.artifactId} --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json`,
|
|
4840
|
+
writeCommand: `tender app analytics charts create ${command.artifactId} --dashboard <dashboard-id> --title <title> --spec chart.json --json`,
|
|
4841
|
+
completion: 'Saved chart ids exist for starter charts, or a concrete blocker is reported.',
|
|
4842
|
+
},
|
|
4843
|
+
{
|
|
4844
|
+
id: 'prove_completion',
|
|
4845
|
+
command: `tender app analytics charts list ${command.artifactId} --dashboard <dashboard-id> --json`,
|
|
4846
|
+
completion: 'The chart list returns saved chart names or ids; an empty dashboard shell is not complete.',
|
|
4847
|
+
},
|
|
4848
|
+
],
|
|
4849
|
+
completionCriteria: ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.completeWhen,
|
|
4850
|
+
blockers: [
|
|
4851
|
+
'User explicitly opted out of analytics.',
|
|
4852
|
+
'No meaningful generated-app journey exists to instrument yet.',
|
|
4853
|
+
'App source cannot be inspected from the current checkout.',
|
|
4854
|
+
'Dashboard/chart API returns an actionable auth, validation, or property activation error.',
|
|
4855
|
+
],
|
|
4856
|
+
finalAnswerEvidence: ANALYTICS_AGENT_GUIDANCE.finalAnswerEvidence,
|
|
4857
|
+
};
|
|
4858
|
+
}
|
|
4859
|
+
function runAnalyticsProvision(command, io) {
|
|
4860
|
+
const payload = buildAnalyticsProvisionPayload(command);
|
|
4861
|
+
if (command.json) {
|
|
4862
|
+
io.stdout(JSON.stringify(payload, null, 2));
|
|
4863
|
+
}
|
|
4864
|
+
else {
|
|
4865
|
+
io.stdout([
|
|
4866
|
+
`app_id: ${command.artifactId}`,
|
|
4867
|
+
'mode: guided_provisioning_plan',
|
|
4868
|
+
`dashboard: ${command.dashboardName}`,
|
|
4869
|
+
`proof: ${ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.proofCommand}`,
|
|
4870
|
+
].join('\n'));
|
|
4871
|
+
}
|
|
4872
|
+
return 0;
|
|
4873
|
+
}
|
|
4322
4874
|
function parseAnalyticsChartsCreateArgs(args) {
|
|
4323
4875
|
if (args.includes('--help') || args.includes('-h')) {
|
|
4324
4876
|
return { command: 'help', topic: 'app analytics charts create' };
|
|
@@ -5197,7 +5749,7 @@ function parseTenderArgs(args) {
|
|
|
5197
5749
|
return parseContextRefreshArgs(resourceArgs.slice(3));
|
|
5198
5750
|
}
|
|
5199
5751
|
if (resourceArgs[1] === 'agent' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
|
|
5200
|
-
return { command: 'help', topic: 'app agent
|
|
5752
|
+
return { command: 'help', topic: 'app agent' };
|
|
5201
5753
|
}
|
|
5202
5754
|
if (resourceArgs[1] === 'agent' && resourceArgs[2] === 'heartbeat') {
|
|
5203
5755
|
return parseAgentHeartbeatArgs(resourceArgs.slice(3));
|
|
@@ -5235,12 +5787,21 @@ function parseTenderArgs(args) {
|
|
|
5235
5787
|
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'capabilities') {
|
|
5236
5788
|
return parseAnalyticsCapabilitiesArgs(resourceArgs.slice(3));
|
|
5237
5789
|
}
|
|
5790
|
+
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'authoring') {
|
|
5791
|
+
return parseAnalyticsAuthoringArgs(resourceArgs.slice(3));
|
|
5792
|
+
}
|
|
5238
5793
|
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'suggestions') {
|
|
5239
5794
|
return parseAnalyticsSuggestionsArgs(resourceArgs.slice(3));
|
|
5240
5795
|
}
|
|
5796
|
+
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'exact-funnel') {
|
|
5797
|
+
return parseAnalyticsExactFunnelArgs(resourceArgs.slice(3));
|
|
5798
|
+
}
|
|
5241
5799
|
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'dashboards') {
|
|
5242
5800
|
return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
|
|
5243
5801
|
}
|
|
5802
|
+
if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'provision') {
|
|
5803
|
+
return parseAnalyticsProvisionArgs(resourceArgs.slice(3));
|
|
5804
|
+
}
|
|
5244
5805
|
if (resourceArgs[1] === 'analytics' &&
|
|
5245
5806
|
resourceArgs[2] === 'properties' &&
|
|
5246
5807
|
(resourceArgs[3] === undefined || resourceArgs[3] === '--help' || resourceArgs[3] === '-h')) {
|
|
@@ -5790,6 +6351,9 @@ function analyticsCorrectedExample(artifactId, analyticsPath) {
|
|
|
5790
6351
|
if (analyticsPath === 'suggestions') {
|
|
5791
6352
|
return `tender app analytics suggestions ${artifactId} --range 30d --json`;
|
|
5792
6353
|
}
|
|
6354
|
+
if (analyticsPath === 'exact-funnel') {
|
|
6355
|
+
return `tender app analytics exact-funnel ${artifactId} --flow <flow-id> --steps started,completed --range 30d --json`;
|
|
6356
|
+
}
|
|
5793
6357
|
if (analyticsPath === 'properties') {
|
|
5794
6358
|
return `tender app analytics properties list ${artifactId} --json`;
|
|
5795
6359
|
}
|
|
@@ -9268,6 +9832,31 @@ async function runAnalyticsSuggestions(command, io, runtime) {
|
|
|
9268
9832
|
printAnalyticsPayload(command, io, payload, [`app_id: ${command.artifactId}`, `suggestions: ${suggestions.length}`]);
|
|
9269
9833
|
return 0;
|
|
9270
9834
|
}
|
|
9835
|
+
async function runAnalyticsExactFunnel(command, io, runtime) {
|
|
9836
|
+
const credentials = await resolveAnalyticsCredentials(command, runtime);
|
|
9837
|
+
const payload = await requestAnalyticsApi({
|
|
9838
|
+
artifactId: command.artifactId,
|
|
9839
|
+
analyticsPath: 'exact-funnel',
|
|
9840
|
+
method: 'POST',
|
|
9841
|
+
baseUrl: credentials.baseUrl,
|
|
9842
|
+
token: credentials.token,
|
|
9843
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
9844
|
+
range: command.range,
|
|
9845
|
+
body: {
|
|
9846
|
+
flowId: command.flowId,
|
|
9847
|
+
steps: command.steps,
|
|
9848
|
+
},
|
|
9849
|
+
});
|
|
9850
|
+
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
9851
|
+
printAnalyticsPayload(command, io, payload, [
|
|
9852
|
+
`app_id: ${command.artifactId}`,
|
|
9853
|
+
`source: ${String(payload.source ?? 'unknown')}`,
|
|
9854
|
+
`flow_id: ${command.flowId}`,
|
|
9855
|
+
`steps: ${rows.length}`,
|
|
9856
|
+
`exact: ${String(payload.exact ?? false)}`,
|
|
9857
|
+
]);
|
|
9858
|
+
return 0;
|
|
9859
|
+
}
|
|
9271
9860
|
async function runAnalyticsDashboards(command, io, runtime) {
|
|
9272
9861
|
if (command.name && command.dryRun) {
|
|
9273
9862
|
const payload = {
|
|
@@ -9787,6 +10376,9 @@ export async function runTenderCli(args, io = {
|
|
|
9787
10376
|
else if (command.topic === 'app preview watch') {
|
|
9788
10377
|
io.stdout(previewWatchHelp());
|
|
9789
10378
|
}
|
|
10379
|
+
else if (command.topic === 'app agent') {
|
|
10380
|
+
io.stdout(appAgentHelp());
|
|
10381
|
+
}
|
|
9790
10382
|
else if (command.topic === 'app agent heartbeat') {
|
|
9791
10383
|
io.stdout(agentHeartbeatHelp());
|
|
9792
10384
|
}
|
|
@@ -9814,12 +10406,21 @@ export async function runTenderCli(args, io = {
|
|
|
9814
10406
|
else if (command.topic === 'app analytics capabilities') {
|
|
9815
10407
|
io.stdout(analyticsCapabilitiesHelp());
|
|
9816
10408
|
}
|
|
10409
|
+
else if (command.topic === 'app analytics authoring') {
|
|
10410
|
+
io.stdout(analyticsAuthoringHelp());
|
|
10411
|
+
}
|
|
9817
10412
|
else if (command.topic === 'app analytics suggestions') {
|
|
9818
10413
|
io.stdout(analyticsSuggestionsHelp());
|
|
9819
10414
|
}
|
|
10415
|
+
else if (command.topic === 'app analytics exact-funnel') {
|
|
10416
|
+
io.stdout(analyticsExactFunnelHelp());
|
|
10417
|
+
}
|
|
9820
10418
|
else if (command.topic === 'app analytics dashboards') {
|
|
9821
10419
|
io.stdout(analyticsDashboardsHelp());
|
|
9822
10420
|
}
|
|
10421
|
+
else if (command.topic === 'app analytics provision') {
|
|
10422
|
+
io.stdout(analyticsProvisionHelp());
|
|
10423
|
+
}
|
|
9823
10424
|
else if (command.topic === 'app analytics export') {
|
|
9824
10425
|
io.stdout(analyticsExportHelp());
|
|
9825
10426
|
}
|
|
@@ -10002,12 +10603,21 @@ export async function runTenderCli(args, io = {
|
|
|
10002
10603
|
if (command.command === 'app analytics capabilities') {
|
|
10003
10604
|
return await runAnalyticsCapabilities(command, io, runtime);
|
|
10004
10605
|
}
|
|
10606
|
+
if (command.command === 'app analytics authoring') {
|
|
10607
|
+
return runAnalyticsAuthoring(command, io);
|
|
10608
|
+
}
|
|
10005
10609
|
if (command.command === 'app analytics suggestions') {
|
|
10006
10610
|
return await runAnalyticsSuggestions(command, io, runtime);
|
|
10007
10611
|
}
|
|
10612
|
+
if (command.command === 'app analytics exact-funnel') {
|
|
10613
|
+
return await runAnalyticsExactFunnel(command, io, runtime);
|
|
10614
|
+
}
|
|
10008
10615
|
if (command.command === 'app analytics dashboards') {
|
|
10009
10616
|
return await runAnalyticsDashboards(command, io, runtime);
|
|
10010
10617
|
}
|
|
10618
|
+
if (command.command === 'app analytics provision') {
|
|
10619
|
+
return runAnalyticsProvision(command, io);
|
|
10620
|
+
}
|
|
10011
10621
|
if (command.command === 'app analytics export catalog') {
|
|
10012
10622
|
return await runAnalyticsExportCatalog(command, io, runtime);
|
|
10013
10623
|
}
|