@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 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
@@ -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
- if (lineCount > 40 && !/^summary|support_handoff|reflection$/i.test(input.summary)) {
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
- assertUsefulMemoryText(input);
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 query <app-id> --spec <file|->
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
- tender app preview watch <app-id>
348
- Replay lifecycle events by latest, commit, or job id
349
- tender app agent heartbeat <app-id>
350
- Send a concise agent status update to the workspace
351
- tender app publish <app-id>
352
- Publish after dry-run or explicit confirmation
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: 'event_funnel_by_name',
4149
- title: 'Event funnel',
4150
- visualization: 'funnel',
4151
- purpose: 'Measure ordered conversion through observed custom event names.',
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>', '<conversion_event>'],
4514
+ eventNames: ['<start_event>', '<middle_event>', '<outcome_event>'],
4155
4515
  },
4156
4516
  adaptWith: [
4157
- 'Replace placeholders with ordered event names from catalog classification or observed events.',
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
- 'Validate with analytics query before saving because funnel order changes the meaning.',
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: 'funnel',
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>', '<conversion_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: 'request_path_funnel',
4181
- title: 'Request path funnel',
4540
+ id: 'unit_funnel',
4541
+ title: 'Unit funnel',
4182
4542
  visualization: 'funnel',
4183
- purpose: 'Measure ordered conversion through observed request paths.',
4543
+ purpose: 'Measure ordered progression with one reach per unit and step.',
4184
4544
  usableAsIs: false,
4185
4545
  placeholders: {
4186
- paths: ['/<start_path>', '/<middle_path>', '/<conversion_path>'],
4546
+ flowId: '<flow_id>',
4547
+ unitType: '<unit_type>',
4548
+ steps: ['<start_step>', '<middle_step>', '<outcome_step>'],
4187
4549
  },
4188
4550
  adaptWith: [
4189
- 'Use when catalog classification includes a path-based funnel or the source code shows ordered route milestones.',
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
- 'Do not include repeated activity routes unless source context says they are milestones.',
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: 'funnel',
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>', '/<conversion_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 heartbeat' };
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"