@tenderprompt/cli 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,6 +34,61 @@ const CLI_PACKAGE_VERSION = readCliPackageVersion();
34
34
  const ANALYTICS_AUTHORING_PLAYBOOK_ID = 'analytics-event-authoring';
35
35
  const ANALYTICS_AUTHORING_COMMAND = 'tender app analytics authoring --json';
36
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
+ };
37
92
  class TenderCliUsageError extends Error {
38
93
  constructor(message) {
39
94
  super(message);
@@ -134,6 +189,8 @@ Commands:
134
189
  Describe app database query limits and policy
135
190
  tender app db query <app-id> --env <preview|published> --sql <sql>
136
191
  Run one read-only app database query
192
+ tender app db dashboards <app-id>
193
+ List or create private app admin dashboards
137
194
  tender app context fetch <app-id>
138
195
  Fetch managed agent context into a checkout
139
196
  tender app context status <app-id>
@@ -350,12 +407,12 @@ Commands:
350
407
  Rebuild the preview and optionally stream lifecycle events
351
408
  tender app preview status <app-id>
352
409
  Show preview job status by latest, commit, or job id
353
- tender app preview watch <app-id>
354
- Replay lifecycle events by latest, commit, or job id
355
- tender app agent heartbeat <app-id>
356
- Send a concise agent status update to the workspace
357
- tender app publish <app-id>
358
- Publish after dry-run or explicit confirmation
410
+ tender app preview watch <app-id>
411
+ Replay lifecycle events by latest, commit, or job id
412
+ tender app agent heartbeat <app-id>
413
+ Send a concise agent status update to the workspace
414
+ tender app publish <app-id>
415
+ Publish after dry-run or explicit confirmation
359
416
  tender app publish status <app-id>
360
417
  Show publish job status by job id
361
418
  tender app publish watch <app-id>
@@ -372,6 +429,8 @@ Commands:
372
429
  Run a server-validated chart spec
373
430
  tender app analytics suggestions <app-id>
374
431
  Generate useful starter chart suggestions
432
+ tender app analytics provision <app-id>
433
+ Print a starter dashboard provisioning plan
375
434
  tender app analytics dashboards <app-id>
376
435
  List or create saved dashboards
377
436
  tender app analytics charts create <app-id>
@@ -394,6 +453,12 @@ Commands:
394
453
  Show table and index schema for one table
395
454
  tender app db query <app-id> --env <preview|published> --sql <sql>
396
455
  Run one read-only app database query
456
+ tender app db dashboards <app-id>
457
+ List or create private app admin dashboards
458
+ tender app db resources create <app-id>
459
+ Add a table-backed dashboard resource
460
+ tender app db widgets create <app-id>
461
+ Add a saved dashboard widget
397
462
  tender app context fetch <app-id>
398
463
  Fetch AGENTS.md, skills, and Tender project context
399
464
  tender app context status <app-id>
@@ -537,6 +602,7 @@ Commands:
537
602
  tender app analytics capabilities <app-id> Describe agent-safe analytics capabilities
538
603
  tender app analytics suggestions <app-id> Generate starter chart suggestions
539
604
  tender app analytics exact-funnel <app-id> Run exact unit funnel analysis
605
+ tender app analytics provision <app-id> Print a starter dashboard provisioning plan
540
606
  tender app analytics dashboards <app-id> List or create dashboards
541
607
  tender app analytics export <kind> <app-id> Export catalog metadata or query rows
542
608
  tender app analytics charts templates Print reusable chart spec templates
@@ -557,6 +623,7 @@ Examples:
557
623
  tender app analytics export catalog artifact_123 --range 30d --json
558
624
  tender app analytics export query artifact_123 --spec chart.json --format csv
559
625
  tender app analytics suggestions artifact_123 --range 2026-04-01..2026-04-30 --json
626
+ tender app analytics provision artifact_123 --from-app --json
560
627
  tender app analytics dashboards artifact_123 --create "Agent dashboard" --json
561
628
  tender app analytics charts templates --json
562
629
  tender app analytics charts create artifact_123 --dashboard analytics_dashboard_123 --title "Signup funnel" --spec unit-funnel.json --json
@@ -572,6 +639,10 @@ function analyticsAuthoringHelp() {
572
639
  Prints the current generated-app analytics event authoring contract. This command
573
640
  does not require an app id, checkout, API token, or installed skill.
574
641
 
642
+ Agent guidance includes default generated-app analytics, replacement baseline
643
+ inspection, shopper/operator surface separation, and dashboard chart-list
644
+ completion criteria.
645
+
575
646
  Examples:
576
647
  tender app analytics authoring --json`;
577
648
  }
@@ -638,6 +709,18 @@ dashboard page loads.
638
709
  Examples:
639
710
  tender app analytics exact-funnel artifact_123 --flow checkout_flow --steps started,completed --range 30d --json`;
640
711
  }
712
+ function analyticsProvisionHelp() {
713
+ return `Usage: tender app analytics provision <app-id> --from-app [--dashboard-name <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
714
+
715
+ Prints a machine-readable provisioning plan for the default generated-app
716
+ analytics delivery contract. This command does not mutate dashboards yet; it
717
+ returns the exact commands, starter chart specs, completion criteria, and
718
+ blockers an external coding agent should follow.
719
+
720
+ Examples:
721
+ tender app analytics provision artifact_123 --from-app --json
722
+ tender app analytics provision artifact_123 --from-app --dashboard-name "Recommendation dashboard" --json`;
723
+ }
641
724
  function analyticsDashboardsHelp() {
642
725
  return `Usage: tender app analytics dashboards <app-id> [--create <name>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
643
726
 
@@ -717,12 +800,27 @@ Commands:
717
800
  Show schema for one table
718
801
  tender app db query <app-id> --env <preview|published>
719
802
  Run one read-only SQL query
803
+ tender app db dashboards <app-id>
804
+ List or create private app admin dashboards
805
+ tender app db resources suggest <app-id>
806
+ Suggest private app admin resources from schema
807
+ tender app db dashboards render <app-id>
808
+ Render saved private app admin queries
809
+ tender app db resources create <app-id>
810
+ Add a table-backed dashboard resource from JSON
811
+ tender app db widgets create <app-id>
812
+ Add a saved dashboard widget from JSON
720
813
 
721
814
  Examples:
722
815
  tender app db capabilities artifact_123 --json
723
816
  tender app db tables artifact_123 --env published --json
724
817
  tender app db schema artifact_123 --env published --table votes --json
725
818
  tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
819
+ tender app db resources suggest artifact_123 --env published --json
820
+ tender app db dashboards artifact_123 --create "Coffee Matcher Admin" --json
821
+ tender app db dashboards render artifact_123 --dashboard app_db_dashboard_123 --env published --json
822
+ tender app db resources create artifact_123 --dashboard app_db_dashboard_123 --spec resource.json --json
823
+ tender app db widgets create artifact_123 --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json
726
824
  tender app db query artifact_123 --env published --file ./debug.sql --param active --limit 50 --json`;
727
825
  }
728
826
  function dbCapabilitiesHelp() {
@@ -762,6 +860,38 @@ Examples:
762
860
  tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --json
763
861
  tender app db query artifact_123 --env published --file - --json < debug.sql`;
764
862
  }
863
+ function dbDashboardsHelp() {
864
+ return `Usage: tender app db dashboards <app-id> [--create <name>] [--default-env <preview|published>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
865
+
866
+ Examples:
867
+ tender app db dashboards artifact_123 --json
868
+ tender app db dashboards artifact_123 --create "Coffee Matcher Admin" --default-env published --json`;
869
+ }
870
+ function dbDashboardsRenderHelp() {
871
+ return `Usage: tender app db dashboards render <app-id> --dashboard <dashboard-id> --env <preview|published> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
872
+
873
+ Examples:
874
+ tender app db dashboards render artifact_123 --dashboard app_db_dashboard_123 --env published --json`;
875
+ }
876
+ function dbResourcesSuggestHelp() {
877
+ return `Usage: tender app db resources suggest <app-id> --env <preview|published> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
878
+
879
+ Examples:
880
+ tender app db resources suggest artifact_123 --env published --json`;
881
+ }
882
+ function dbResourcesCreateHelp() {
883
+ return `Usage: tender app db resources create <app-id> --dashboard <dashboard-id> --spec <resource.json|-> [--view <view.json|->] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
884
+
885
+ Examples:
886
+ tender app db resources create artifact_123 --dashboard app_db_dashboard_123 --spec resource.json --json
887
+ tender app db resources create artifact_123 --dashboard app_db_dashboard_123 --spec resource.json --view needs-review.json --json`;
888
+ }
889
+ function dbWidgetsCreateHelp() {
890
+ return `Usage: tender app db widgets create <app-id> --dashboard <dashboard-id> --title <title> --spec <widget.json|-> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
891
+
892
+ Examples:
893
+ tender app db widgets create artifact_123 --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json`;
894
+ }
765
895
  function artifactsListHelp() {
766
896
  return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
767
897
 
@@ -1097,6 +1227,15 @@ Examples:
1097
1227
  tender app agent heartbeat artifact_123 --source claude-code --status needs_attention --summary "Waiting for an API key decision" --requested-action answer_question --json
1098
1228
  tender app agent heartbeat artifact_123 --input - --json < heartbeat.json`;
1099
1229
  }
1230
+ function appAgentHelp() {
1231
+ return `Usage: tender app agent <command>
1232
+
1233
+ Commands:
1234
+ heartbeat <app-id> Send a concise agent status update to the workspace.
1235
+
1236
+ Examples:
1237
+ tender app agent heartbeat artifact_123 --source codex --status working --summary "Reading files" --json`;
1238
+ }
1100
1239
  function publishStatusHelp() {
1101
1240
  return `Usage: tender app publish status <app-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
1102
1241
 
@@ -1174,6 +1313,7 @@ function tenderCliCapabilities() {
1174
1313
  playbookCommand: ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
1175
1314
  doctorCommand: 'tender app doctor --dir <dir> --json',
1176
1315
  purpose: 'Keep generated app analytics events compatible with Tender dashboards and unit funnels.',
1316
+ guidance: ANALYTICS_AGENT_GUIDANCE,
1177
1317
  },
1178
1318
  description: 'Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.',
1179
1319
  discovery: {
@@ -1185,6 +1325,7 @@ function tenderCliCapabilities() {
1185
1325
  'tender memory --help',
1186
1326
  'tender playbooks --help',
1187
1327
  'tender app --help',
1328
+ 'tender app agent --help',
1188
1329
  'tender app agent heartbeat --help',
1189
1330
  'tender app share --help',
1190
1331
  'tender app claim --help',
@@ -1286,6 +1427,7 @@ function tenderCliCapabilities() {
1286
1427
  commands: [
1287
1428
  ANALYTICS_AUTHORING_COMMAND,
1288
1429
  ANALYTICS_AUTHORING_PLAYBOOK_COMMAND,
1430
+ 'tender app analytics provision <artifact-id> --from-app --json',
1289
1431
  'tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json',
1290
1432
  'tender app analytics charts templates --json',
1291
1433
  'tender app analytics query <artifact-id> --spec chart.json --json',
@@ -1300,10 +1442,14 @@ function tenderCliCapabilities() {
1300
1442
  ],
1301
1443
  notes: [
1302
1444
  'Run authoring before adding or repairing analytics instrumentation; use capabilities for artifact-scoped read/query details.',
1445
+ '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.',
1446
+ 'For replacement work, inspect the baseline customer-facing page or widget before source edits; Codex should prefer browser:browser when available.',
1447
+ '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.',
1303
1448
  'Combine CLI capabilities with local source inspection of analytics calls.',
1304
1449
  'Validate chart specs with query before creating or updating saved charts.',
1305
1450
  'Property chart create/update commands automatically make missing custom event properties queryable when the spec has an event_name = filter.',
1306
1451
  '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.',
1452
+ 'Dashboard setup is not complete until saved charts exist and tender app analytics charts list returns chart names or ids.',
1307
1453
  'Use list before writes so agents can avoid duplicates and explain the exact chart they are changing.',
1308
1454
  ],
1309
1455
  },
@@ -1317,11 +1463,14 @@ function tenderCliCapabilities() {
1317
1463
  'tender app db schema <artifact-id> --env published --table <table-name> --profile db --json',
1318
1464
  'tender app db query <artifact-id> --env published --sql "select * from <table-name> limit 20" --profile db --json',
1319
1465
  'tender app db query <artifact-id> --env published --file ./debug.sql --profile db --json',
1466
+ 'tender app db resources suggest <artifact-id> --env published --profile db --json',
1467
+ 'tender app db dashboards render <artifact-id> --dashboard <dashboard-id> --env published --profile db --json',
1320
1468
  ],
1321
1469
  notes: [
1322
1470
  'The CLI only accepts artifact ids. It never accepts tenant ids, runner names, provider account ids, or runtime storage identifiers.',
1323
1471
  'Read-only policy is enforced by the server before the runtime shim executes SQL.',
1324
1472
  'Use --file - to pipe SQL from stdin without prompts.',
1473
+ 'Use resources suggest and dashboards render for private operator/admin surfaces instead of building database tables into shopper UI.',
1325
1474
  ],
1326
1475
  },
1327
1476
  {
@@ -1450,6 +1599,12 @@ function tenderCliCapabilities() {
1450
1599
  requiresAuth: false,
1451
1600
  writes: false,
1452
1601
  },
1602
+ {
1603
+ command: 'tender app analytics provision <artifact-id> --from-app --json',
1604
+ purpose: 'Print a guided starter dashboard provisioning plan with chart-list completion proof.',
1605
+ requiresAuth: false,
1606
+ writes: false,
1607
+ },
1453
1608
  {
1454
1609
  command: 'tender app analytics properties list <artifact-id> --json',
1455
1610
  purpose: 'List custom event properties available to dashboard queries.',
@@ -1514,6 +1669,39 @@ function tenderCliCapabilities() {
1514
1669
  requiresAuth: true,
1515
1670
  writes: false,
1516
1671
  },
1672
+ {
1673
+ command: 'tender app db resources suggest <artifact-id> --env published --json',
1674
+ purpose: 'Suggest private app admin resources, saved views, and widgets from app database schema and samples.',
1675
+ requiresAuth: true,
1676
+ writes: false,
1677
+ },
1678
+ {
1679
+ command: 'tender app db dashboards <artifact-id> --create "App admin" --dry-run --json',
1680
+ purpose: 'Preview or create a private app database dashboard definition for one artifact.',
1681
+ requiresAuth: true,
1682
+ writes: true,
1683
+ dryRun: true,
1684
+ },
1685
+ {
1686
+ command: 'tender app db dashboards render <artifact-id> --dashboard <dashboard-id> --env published --json',
1687
+ purpose: 'Render saved private app admin resource and widget queries for completion proof.',
1688
+ requiresAuth: true,
1689
+ writes: false,
1690
+ },
1691
+ {
1692
+ command: 'tender app db resources create <artifact-id> --dashboard <dashboard-id> --spec resource.json --dry-run --json',
1693
+ purpose: 'Preview or save a table-backed resource definition for a private app admin dashboard.',
1694
+ requiresAuth: true,
1695
+ writes: true,
1696
+ dryRun: true,
1697
+ },
1698
+ {
1699
+ command: 'tender app db widgets create <artifact-id> --dashboard <dashboard-id> --title <title> --spec widget.json --dry-run --json',
1700
+ purpose: 'Preview or save a bounded widget definition for a private app admin dashboard.',
1701
+ requiresAuth: true,
1702
+ writes: true,
1703
+ dryRun: true,
1704
+ },
1517
1705
  ],
1518
1706
  security: [
1519
1707
  'Prefer artifact-scoped device tokens.',
@@ -4103,6 +4291,35 @@ function parseAnalyticsDashboardsArgs(args) {
4103
4291
  }
4104
4292
  return { command: 'app analytics dashboards', artifactId, name, dryRun, ...parsed };
4105
4293
  }
4294
+ function parseAnalyticsProvisionArgs(args) {
4295
+ if (args.includes('--help') || args.includes('-h')) {
4296
+ return { command: 'help', topic: 'app analytics provision' };
4297
+ }
4298
+ const artifactId = args[0];
4299
+ if (!artifactId || artifactId.startsWith('-')) {
4300
+ throw new TenderCliUsageError('app id is required. Example: tender app analytics provision <app-id> --from-app --json');
4301
+ }
4302
+ const parsed = parseAnalyticsCommonArgs(args, 1);
4303
+ let fromApp = false;
4304
+ let dashboardName = 'Agent dashboard';
4305
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
4306
+ const arg = parsed.remaining[index];
4307
+ if (arg === '--from-app') {
4308
+ fromApp = true;
4309
+ continue;
4310
+ }
4311
+ if (arg === '--dashboard-name') {
4312
+ dashboardName = parseRequiredFlagValue(parsed.remaining[index + 1], '--dashboard-name');
4313
+ index += 1;
4314
+ continue;
4315
+ }
4316
+ throw new TenderCliUsageError(`Unknown analytics provision option: ${arg}`);
4317
+ }
4318
+ if (!fromApp) {
4319
+ throw new TenderCliUsageError('--from-app is required. Example: tender app analytics provision artifact_123 --from-app --json');
4320
+ }
4321
+ return { command: 'app analytics provision', artifactId, fromApp, dashboardName, ...parsed };
4322
+ }
4106
4323
  function parseAnalyticsChartsTemplatesArgs(args) {
4107
4324
  if (args.includes('--help') || args.includes('-h')) {
4108
4325
  return { command: 'help', topic: 'app analytics charts templates' };
@@ -4222,6 +4439,7 @@ function analyticsAuthoringContract() {
4222
4439
  'analytics_unstable_unit_id',
4223
4440
  ],
4224
4441
  },
4442
+ agentGuidance: ANALYTICS_AGENT_GUIDANCE,
4225
4443
  analyticsAuthoring: {
4226
4444
  payloadShape: {
4227
4445
  event: 'custom_event_name',
@@ -4293,6 +4511,8 @@ function analyticsAuthoringContract() {
4293
4511
  },
4294
4512
  ],
4295
4513
  charting: {
4514
+ starterDashboardCommands: ANALYTICS_AGENT_GUIDANCE.starterDashboardCommands,
4515
+ completionProof: ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.proofCommand,
4296
4516
  unitFunnel: 'After traffic exists, use tender app analytics capabilities <app-id> --include-catalog --json and create version 2 unit_step_count funnel specs from observedFlows and observedUnitTypes.',
4297
4517
  exactAudit: 'Use tender app analytics exact-funnel <app-id> --flow <flow-id> --steps <step-a,step-b> --json for repair and low-volume audits.',
4298
4518
  },
@@ -4648,6 +4868,100 @@ function runAnalyticsChartsTemplates(command, io) {
4648
4868
  }
4649
4869
  return 0;
4650
4870
  }
4871
+ function quoteCliArgument(value) {
4872
+ return JSON.stringify(value);
4873
+ }
4874
+ function buildAnalyticsProvisionPayload(command) {
4875
+ const starterTemplateIds = new Set(['daily_custom_events', 'top_custom_events']);
4876
+ const starterCharts = analyticsChartTemplates()
4877
+ .filter((template) => starterTemplateIds.has(template.id))
4878
+ .map((template) => ({
4879
+ templateId: template.id,
4880
+ title: template.title,
4881
+ purpose: template.purpose,
4882
+ spec: template.spec,
4883
+ }));
4884
+ const dashboardName = quoteCliArgument(command.dashboardName);
4885
+ return {
4886
+ ok: true,
4887
+ command: command.command,
4888
+ version: 1,
4889
+ artifactId: command.artifactId,
4890
+ mode: 'guided_provisioning_plan',
4891
+ writes: false,
4892
+ fromApp: command.fromApp,
4893
+ dashboardName: command.dashboardName,
4894
+ description: 'Machine-readable starter dashboard provisioning plan for external coding agents. This command does not mutate dashboards yet.',
4895
+ agentGuidance: ANALYTICS_AGENT_GUIDANCE,
4896
+ starterCharts,
4897
+ steps: [
4898
+ {
4899
+ id: 'read_authoring_contract',
4900
+ command: ANALYTICS_AUTHORING_COMMAND,
4901
+ completion: 'Generated app analytics payload contract and surface boundary are loaded.',
4902
+ },
4903
+ {
4904
+ id: 'inspect_app_analytics',
4905
+ commands: [
4906
+ `tender app analytics capabilities ${command.artifactId} --include-catalog --range 30d --json`,
4907
+ 'tender app doctor --dir . --json',
4908
+ ],
4909
+ completion: 'Observed events, unit/flow metadata, existing dashboards, and local analytics diagnostics are known.',
4910
+ },
4911
+ {
4912
+ id: 'choose_starter_charts',
4913
+ command: 'tender app analytics charts templates --json',
4914
+ defaultTemplates: ['daily_custom_events', 'top_custom_events'],
4915
+ completion: 'Starter chart specs are adapted to observed events or accepted as generic app-owned event charts.',
4916
+ },
4917
+ {
4918
+ id: 'create_or_reuse_dashboard',
4919
+ dryRunCommand: `tender app analytics dashboards ${command.artifactId} --create ${dashboardName} --dry-run --json`,
4920
+ writeCommand: `tender app analytics dashboards ${command.artifactId} --create ${dashboardName} --json`,
4921
+ completion: 'A dashboard id is available, either reused from capabilities or returned by the create command.',
4922
+ },
4923
+ {
4924
+ id: 'validate_chart_specs',
4925
+ command: `tender app analytics query ${command.artifactId} --spec chart.json --json`,
4926
+ completion: 'Each chart spec is server-validated before it is saved.',
4927
+ },
4928
+ {
4929
+ id: 'create_saved_charts',
4930
+ dryRunCommand: `tender app analytics charts create ${command.artifactId} --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json`,
4931
+ writeCommand: `tender app analytics charts create ${command.artifactId} --dashboard <dashboard-id> --title <title> --spec chart.json --json`,
4932
+ completion: 'Saved chart ids exist for starter charts, or a concrete blocker is reported.',
4933
+ },
4934
+ {
4935
+ id: 'prove_completion',
4936
+ command: `tender app analytics charts list ${command.artifactId} --dashboard <dashboard-id> --json`,
4937
+ completion: 'The chart list returns saved chart names or ids; an empty dashboard shell is not complete.',
4938
+ },
4939
+ ],
4940
+ completionCriteria: ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.completeWhen,
4941
+ blockers: [
4942
+ 'User explicitly opted out of analytics.',
4943
+ 'No meaningful generated-app journey exists to instrument yet.',
4944
+ 'App source cannot be inspected from the current checkout.',
4945
+ 'Dashboard/chart API returns an actionable auth, validation, or property activation error.',
4946
+ ],
4947
+ finalAnswerEvidence: ANALYTICS_AGENT_GUIDANCE.finalAnswerEvidence,
4948
+ };
4949
+ }
4950
+ function runAnalyticsProvision(command, io) {
4951
+ const payload = buildAnalyticsProvisionPayload(command);
4952
+ if (command.json) {
4953
+ io.stdout(JSON.stringify(payload, null, 2));
4954
+ }
4955
+ else {
4956
+ io.stdout([
4957
+ `app_id: ${command.artifactId}`,
4958
+ 'mode: guided_provisioning_plan',
4959
+ `dashboard: ${command.dashboardName}`,
4960
+ `proof: ${ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.proofCommand}`,
4961
+ ].join('\n'));
4962
+ }
4963
+ return 0;
4964
+ }
4651
4965
  function parseAnalyticsChartsCreateArgs(args) {
4652
4966
  if (args.includes('--help') || args.includes('-h')) {
4653
4967
  return { command: 'help', topic: 'app analytics charts create' };
@@ -5191,6 +5505,239 @@ function parseDbQueryArgs(args) {
5191
5505
  ...parsed,
5192
5506
  };
5193
5507
  }
5508
+ function parseDbDashboardsArgs(args) {
5509
+ if (args.includes('--help') || args.includes('-h')) {
5510
+ return { command: 'help', topic: 'app db dashboards' };
5511
+ }
5512
+ const artifactId = args[0];
5513
+ if (!artifactId || artifactId.startsWith('-')) {
5514
+ throw new TenderCliUsageError('app id is required. Example: tender app db dashboards <app-id> --json');
5515
+ }
5516
+ const parsed = parseDbCommonArgs(args, 1);
5517
+ let name = null;
5518
+ let defaultEnvironment = null;
5519
+ let dryRun = false;
5520
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
5521
+ const arg = parsed.remaining[index];
5522
+ const defaultEnvAssignment = parseFlagAssignment(arg, '--default-env');
5523
+ if (defaultEnvAssignment !== null) {
5524
+ defaultEnvironment = parseDbEnvironment(defaultEnvAssignment);
5525
+ continue;
5526
+ }
5527
+ if (arg === '--create') {
5528
+ name = parseRequiredFlagValue(parsed.remaining[index + 1], '--create');
5529
+ index += 1;
5530
+ continue;
5531
+ }
5532
+ if (arg === '--default-env') {
5533
+ defaultEnvironment = parseDbEnvironment(parsed.remaining[index + 1]);
5534
+ index += 1;
5535
+ continue;
5536
+ }
5537
+ if (arg === '--dry-run') {
5538
+ dryRun = true;
5539
+ continue;
5540
+ }
5541
+ throw new TenderCliUsageError(`Unknown app db dashboards option: ${arg}`);
5542
+ }
5543
+ if (dryRun && !name) {
5544
+ throw new TenderCliUsageError('--dry-run is only valid with --create. Example: tender app db dashboards artifact_123 --create "App admin" --dry-run --json');
5545
+ }
5546
+ return { command: 'app db dashboards', artifactId, name, defaultEnvironment, dryRun, ...parsed };
5547
+ }
5548
+ function parseDbDashboardsRenderArgs(args) {
5549
+ if (args.includes('--help') || args.includes('-h')) {
5550
+ return { command: 'help', topic: 'app db dashboards render' };
5551
+ }
5552
+ const artifactId = args[0];
5553
+ if (!artifactId || artifactId.startsWith('-')) {
5554
+ throw new TenderCliUsageError('app id is required. Example: tender app db dashboards render <app-id> --dashboard app_db_dashboard_123 --env published --json');
5555
+ }
5556
+ const parsed = parseDbCommonArgs(args, 1);
5557
+ let dashboardId = null;
5558
+ let environment = null;
5559
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
5560
+ const arg = parsed.remaining[index];
5561
+ const dashboardAssignment = parseFlagAssignment(arg, '--dashboard');
5562
+ if (dashboardAssignment !== null) {
5563
+ dashboardId = parseRequiredFlagValue(dashboardAssignment, '--dashboard');
5564
+ continue;
5565
+ }
5566
+ const envAssignment = parseFlagAssignment(arg, '--env');
5567
+ if (envAssignment !== null) {
5568
+ environment = parseDbEnvironment(envAssignment);
5569
+ continue;
5570
+ }
5571
+ if (arg === '--dashboard') {
5572
+ dashboardId = parseRequiredFlagValue(parsed.remaining[index + 1], '--dashboard');
5573
+ index += 1;
5574
+ continue;
5575
+ }
5576
+ if (arg === '--env') {
5577
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
5578
+ index += 1;
5579
+ continue;
5580
+ }
5581
+ throw new TenderCliUsageError(`Unknown app db dashboards render option: ${arg}`);
5582
+ }
5583
+ if (!dashboardId) {
5584
+ throw new TenderCliUsageError('--dashboard is required. Example: tender app db dashboards render artifact_123 --dashboard app_db_dashboard_123 --env published --json');
5585
+ }
5586
+ if (!environment) {
5587
+ throw new TenderCliUsageError('--env is required. Example: tender app db dashboards render artifact_123 --dashboard app_db_dashboard_123 --env published --json');
5588
+ }
5589
+ return { command: 'app db dashboards render', artifactId, dashboardId, environment, ...parsed };
5590
+ }
5591
+ function parseDbResourcesSuggestArgs(args) {
5592
+ if (args.includes('--help') || args.includes('-h')) {
5593
+ return { command: 'help', topic: 'app db resources suggest' };
5594
+ }
5595
+ const artifactId = args[0];
5596
+ if (!artifactId || artifactId.startsWith('-')) {
5597
+ throw new TenderCliUsageError('app id is required. Example: tender app db resources suggest <app-id> --env published --json');
5598
+ }
5599
+ const parsed = parseDbCommonArgs(args, 1);
5600
+ let environment = null;
5601
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
5602
+ const arg = parsed.remaining[index];
5603
+ const envAssignment = parseFlagAssignment(arg, '--env');
5604
+ if (envAssignment !== null) {
5605
+ environment = parseDbEnvironment(envAssignment);
5606
+ continue;
5607
+ }
5608
+ if (arg === '--env') {
5609
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
5610
+ index += 1;
5611
+ continue;
5612
+ }
5613
+ throw new TenderCliUsageError(`Unknown app db resources suggest option: ${arg}`);
5614
+ }
5615
+ if (!environment) {
5616
+ throw new TenderCliUsageError('--env is required. Example: tender app db resources suggest artifact_123 --env published --json');
5617
+ }
5618
+ return { command: 'app db resources suggest', artifactId, environment, ...parsed };
5619
+ }
5620
+ function parseDbResourcesCreateArgs(args) {
5621
+ if (args.includes('--help') || args.includes('-h')) {
5622
+ return { command: 'help', topic: 'app db resources create' };
5623
+ }
5624
+ const artifactId = args[0];
5625
+ if (!artifactId || artifactId.startsWith('-')) {
5626
+ throw new TenderCliUsageError('app id is required. Example: tender app db resources create <app-id> --dashboard app_db_dashboard_123 --spec resource.json --json');
5627
+ }
5628
+ const parsed = parseDbCommonArgs(args, 1);
5629
+ let dashboardId = null;
5630
+ let specPath = null;
5631
+ const viewPaths = [];
5632
+ let dryRun = false;
5633
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
5634
+ const arg = parsed.remaining[index];
5635
+ const dashboardAssignment = parseFlagAssignment(arg, '--dashboard');
5636
+ if (dashboardAssignment !== null) {
5637
+ dashboardId = parseRequiredFlagValue(dashboardAssignment, '--dashboard');
5638
+ continue;
5639
+ }
5640
+ const specAssignment = parseFlagAssignment(arg, '--spec');
5641
+ if (specAssignment !== null) {
5642
+ specPath = parseSpecPath(specAssignment);
5643
+ continue;
5644
+ }
5645
+ const viewAssignment = parseFlagAssignment(arg, '--view');
5646
+ if (viewAssignment !== null) {
5647
+ viewPaths.push(parseSpecPath(viewAssignment));
5648
+ continue;
5649
+ }
5650
+ if (arg === '--dashboard') {
5651
+ dashboardId = parseRequiredFlagValue(parsed.remaining[index + 1], '--dashboard');
5652
+ index += 1;
5653
+ continue;
5654
+ }
5655
+ if (arg === '--spec') {
5656
+ specPath = parseSpecPath(parsed.remaining[index + 1]);
5657
+ index += 1;
5658
+ continue;
5659
+ }
5660
+ if (arg === '--view') {
5661
+ viewPaths.push(parseSpecPath(parsed.remaining[index + 1]));
5662
+ index += 1;
5663
+ continue;
5664
+ }
5665
+ if (arg === '--dry-run') {
5666
+ dryRun = true;
5667
+ continue;
5668
+ }
5669
+ throw new TenderCliUsageError(`Unknown app db resources create option: ${arg}`);
5670
+ }
5671
+ if (!dashboardId) {
5672
+ throw new TenderCliUsageError('--dashboard is required. Example: tender app db resources create artifact_123 --dashboard app_db_dashboard_123 --spec resource.json --json');
5673
+ }
5674
+ if (!specPath) {
5675
+ throw new TenderCliUsageError('--spec is required. Example: tender app db resources create artifact_123 --dashboard app_db_dashboard_123 --spec resource.json --json');
5676
+ }
5677
+ return { command: 'app db resources create', artifactId, dashboardId, specPath, viewPaths, dryRun, ...parsed };
5678
+ }
5679
+ function parseDbWidgetsCreateArgs(args) {
5680
+ if (args.includes('--help') || args.includes('-h')) {
5681
+ return { command: 'help', topic: 'app db widgets create' };
5682
+ }
5683
+ const artifactId = args[0];
5684
+ if (!artifactId || artifactId.startsWith('-')) {
5685
+ throw new TenderCliUsageError('app id is required. Example: tender app db widgets create <app-id> --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json');
5686
+ }
5687
+ const parsed = parseDbCommonArgs(args, 1);
5688
+ let dashboardId = null;
5689
+ let title = null;
5690
+ let specPath = null;
5691
+ let dryRun = false;
5692
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
5693
+ const arg = parsed.remaining[index];
5694
+ const dashboardAssignment = parseFlagAssignment(arg, '--dashboard');
5695
+ if (dashboardAssignment !== null) {
5696
+ dashboardId = parseRequiredFlagValue(dashboardAssignment, '--dashboard');
5697
+ continue;
5698
+ }
5699
+ const titleAssignment = parseFlagAssignment(arg, '--title');
5700
+ if (titleAssignment !== null) {
5701
+ title = parseRequiredFlagValue(titleAssignment, '--title');
5702
+ continue;
5703
+ }
5704
+ const specAssignment = parseFlagAssignment(arg, '--spec');
5705
+ if (specAssignment !== null) {
5706
+ specPath = parseSpecPath(specAssignment);
5707
+ continue;
5708
+ }
5709
+ if (arg === '--dashboard') {
5710
+ dashboardId = parseRequiredFlagValue(parsed.remaining[index + 1], '--dashboard');
5711
+ index += 1;
5712
+ continue;
5713
+ }
5714
+ if (arg === '--title') {
5715
+ title = parseRequiredFlagValue(parsed.remaining[index + 1], '--title');
5716
+ index += 1;
5717
+ continue;
5718
+ }
5719
+ if (arg === '--spec') {
5720
+ specPath = parseSpecPath(parsed.remaining[index + 1]);
5721
+ index += 1;
5722
+ continue;
5723
+ }
5724
+ if (arg === '--dry-run') {
5725
+ dryRun = true;
5726
+ continue;
5727
+ }
5728
+ throw new TenderCliUsageError(`Unknown app db widgets create option: ${arg}`);
5729
+ }
5730
+ if (!dashboardId) {
5731
+ throw new TenderCliUsageError('--dashboard is required. Example: tender app db widgets create artifact_123 --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json');
5732
+ }
5733
+ if (!title) {
5734
+ throw new TenderCliUsageError('--title is required. Example: tender app db widgets create artifact_123 --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json');
5735
+ }
5736
+ if (!specPath) {
5737
+ throw new TenderCliUsageError('--spec is required. Example: tender app db widgets create artifact_123 --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json');
5738
+ }
5739
+ return { command: 'app db widgets create', artifactId, dashboardId, title, specPath, dryRun, ...parsed };
5740
+ }
5194
5741
  const AGENT_HEARTBEAT_SOURCE_VALUES = new Set(['codex', 'claude-code', 'tender-cli', 'git', 'other']);
5195
5742
  const AGENT_HEARTBEAT_STATUS_VALUES = new Set(['connected', 'working', 'waiting_for_user', 'needs_attention']);
5196
5743
  const AGENT_HEARTBEAT_PHASE_VALUES = new Set([
@@ -5526,7 +6073,7 @@ function parseTenderArgs(args) {
5526
6073
  return parseContextRefreshArgs(resourceArgs.slice(3));
5527
6074
  }
5528
6075
  if (resourceArgs[1] === 'agent' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
5529
- return { command: 'help', topic: 'app agent heartbeat' };
6076
+ return { command: 'help', topic: 'app agent' };
5530
6077
  }
5531
6078
  if (resourceArgs[1] === 'agent' && resourceArgs[2] === 'heartbeat') {
5532
6079
  return parseAgentHeartbeatArgs(resourceArgs.slice(3));
@@ -5576,6 +6123,9 @@ function parseTenderArgs(args) {
5576
6123
  if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'dashboards') {
5577
6124
  return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
5578
6125
  }
6126
+ if (resourceArgs[1] === 'analytics' && resourceArgs[2] === 'provision') {
6127
+ return parseAnalyticsProvisionArgs(resourceArgs.slice(3));
6128
+ }
5579
6129
  if (resourceArgs[1] === 'analytics' &&
5580
6130
  resourceArgs[2] === 'properties' &&
5581
6131
  (resourceArgs[3] === undefined || resourceArgs[3] === '--help' || resourceArgs[3] === '-h')) {
@@ -5633,6 +6183,31 @@ function parseTenderArgs(args) {
5633
6183
  if (resourceArgs[1] === 'db' && resourceArgs[2] === 'query') {
5634
6184
  return parseDbQueryArgs(resourceArgs.slice(3));
5635
6185
  }
6186
+ if (resourceArgs[1] === 'db' && resourceArgs[2] === 'dashboards' && resourceArgs[3] === 'render') {
6187
+ return parseDbDashboardsRenderArgs(resourceArgs.slice(4));
6188
+ }
6189
+ if (resourceArgs[1] === 'db' && resourceArgs[2] === 'dashboards') {
6190
+ return parseDbDashboardsArgs(resourceArgs.slice(3));
6191
+ }
6192
+ if (resourceArgs[1] === 'db' &&
6193
+ resourceArgs[2] === 'resources' &&
6194
+ (resourceArgs[3] === undefined || resourceArgs[3] === '--help' || resourceArgs[3] === '-h')) {
6195
+ return { command: 'help', topic: 'app db resources create' };
6196
+ }
6197
+ if (resourceArgs[1] === 'db' && resourceArgs[2] === 'resources' && resourceArgs[3] === 'suggest') {
6198
+ return parseDbResourcesSuggestArgs(resourceArgs.slice(4));
6199
+ }
6200
+ if (resourceArgs[1] === 'db' && resourceArgs[2] === 'resources' && resourceArgs[3] === 'create') {
6201
+ return parseDbResourcesCreateArgs(resourceArgs.slice(4));
6202
+ }
6203
+ if (resourceArgs[1] === 'db' &&
6204
+ resourceArgs[2] === 'widgets' &&
6205
+ (resourceArgs[3] === undefined || resourceArgs[3] === '--help' || resourceArgs[3] === '-h')) {
6206
+ return { command: 'help', topic: 'app db widgets create' };
6207
+ }
6208
+ if (resourceArgs[1] === 'db' && resourceArgs[2] === 'widgets' && resourceArgs[3] === 'create') {
6209
+ return parseDbWidgetsCreateArgs(resourceArgs.slice(4));
6210
+ }
5636
6211
  if (resourceArgs[1] === 'db') {
5637
6212
  throw new TenderCliUsageError(`Unknown app db command: ${resourceArgs[2]}`);
5638
6213
  }
@@ -6074,6 +6649,66 @@ async function requestDbApi(input) {
6074
6649
  }
6075
6650
  return payload;
6076
6651
  }
6652
+ function appDbDashboardApiSuffix(input) {
6653
+ if (input.path === 'dashboards') {
6654
+ return '';
6655
+ }
6656
+ if (input.path === 'suggest') {
6657
+ return '/suggest';
6658
+ }
6659
+ if (!input.dashboardId) {
6660
+ throw new TenderCliUsageError('--dashboard is required.');
6661
+ }
6662
+ if (input.path === 'dashboard') {
6663
+ return `/${encodeURIComponent(input.dashboardId)}`;
6664
+ }
6665
+ if (input.path === 'resources') {
6666
+ return `/${encodeURIComponent(input.dashboardId)}/resources`;
6667
+ }
6668
+ if (input.path === 'resourceQuery') {
6669
+ if (!input.resourceId) {
6670
+ throw new TenderCliUsageError('--resource is required.');
6671
+ }
6672
+ return `/${encodeURIComponent(input.dashboardId)}/resources/${encodeURIComponent(input.resourceId)}/query`;
6673
+ }
6674
+ if (input.path === 'widgets') {
6675
+ return `/${encodeURIComponent(input.dashboardId)}/widgets`;
6676
+ }
6677
+ if (!input.widgetId) {
6678
+ throw new TenderCliUsageError('--widget is required.');
6679
+ }
6680
+ return `/${encodeURIComponent(input.dashboardId)}/widgets/${encodeURIComponent(input.widgetId)}/query`;
6681
+ }
6682
+ async function requestAppDbDashboardApi(input) {
6683
+ const suffix = appDbDashboardApiSuffix(input);
6684
+ const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/app-db-dashboards${suffix}`, {
6685
+ method: input.method,
6686
+ headers: {
6687
+ authorization: `Bearer ${input.token}`,
6688
+ accept: 'application/json',
6689
+ ...(input.body === undefined ? {} : { 'content-type': 'application/json' }),
6690
+ },
6691
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
6692
+ });
6693
+ const payload = await parseJsonResponse(response);
6694
+ if (!response.ok || !payload?.ok) {
6695
+ const reasonCode = (typeof payload?.reasonCode === 'string' && payload.reasonCode) || `http_${response.status}`;
6696
+ const message = (typeof payload?.message === 'string' && payload.message) ||
6697
+ (typeof payload?.reasonCode === 'string' && payload.reasonCode) ||
6698
+ `App database dashboard request failed with HTTP ${response.status}.`;
6699
+ const example = appDbDashboardCorrectedExample(input.artifactId, input.path);
6700
+ throw new TenderCliDbApiError([
6701
+ message,
6702
+ `Scope: artifact ${input.artifactId}, app-db-dashboards.`,
6703
+ `Reason: ${reasonCode}.`,
6704
+ `Example: ${example}.`,
6705
+ ].join('\n'), reasonCode, {
6706
+ artifactId: input.artifactId,
6707
+ surface: 'db/dashboards',
6708
+ }, example);
6709
+ }
6710
+ return payload;
6711
+ }
6077
6712
  function encodePathSegments(filePath) {
6078
6713
  return filePath.split('/').map(encodeURIComponent).join('/');
6079
6714
  }
@@ -6115,6 +6750,21 @@ function dbCorrectedExample(artifactId, dbPath) {
6115
6750
  }
6116
6751
  return `tender app db query ${artifactId} --env published --sql "select * from <table-name> limit 20" --json`;
6117
6752
  }
6753
+ function appDbDashboardCorrectedExample(artifactId, path) {
6754
+ if (path === 'suggest') {
6755
+ return `tender app db resources suggest ${artifactId} --env published --json`;
6756
+ }
6757
+ if (path === 'dashboard' || path === 'resourceQuery' || path === 'widgetQuery') {
6758
+ return `tender app db dashboards render ${artifactId} --dashboard app_db_dashboard_123 --env published --json`;
6759
+ }
6760
+ if (path === 'resources') {
6761
+ return `tender app db resources create ${artifactId} --dashboard app_db_dashboard_123 --spec resource.json --json`;
6762
+ }
6763
+ if (path === 'widgets') {
6764
+ return `tender app db widgets create ${artifactId} --dashboard app_db_dashboard_123 --title "Total signups" --spec widget.json --json`;
6765
+ }
6766
+ return `tender app db dashboards ${artifactId} --json`;
6767
+ }
6118
6768
  function analyticsCorrectedExample(artifactId, analyticsPath) {
6119
6769
  if (analyticsPath === 'capabilities') {
6120
6770
  return `tender app analytics capabilities ${artifactId} --include-catalog --range 30d --json`;
@@ -9529,6 +10179,310 @@ async function runDbQuery(command, io, runtime) {
9529
10179
  ]);
9530
10180
  return 0;
9531
10181
  }
10182
+ async function runDbDashboards(command, io, runtime) {
10183
+ if (command.name && command.dryRun) {
10184
+ const payload = {
10185
+ ok: true,
10186
+ dryRun: true,
10187
+ artifactId: command.artifactId,
10188
+ request: {
10189
+ name: command.name,
10190
+ defaultEnvironment: command.defaultEnvironment ?? 'published',
10191
+ },
10192
+ };
10193
+ printDbPayload(command, io, payload, [`app_id: ${command.artifactId}`, `name: ${command.name}`, 'dry_run: true']);
10194
+ return 0;
10195
+ }
10196
+ const credentials = await resolveDbCredentials(command, runtime);
10197
+ const payload = await requestAppDbDashboardApi({
10198
+ artifactId: command.artifactId,
10199
+ path: 'dashboards',
10200
+ method: command.name ? 'POST' : 'GET',
10201
+ baseUrl: credentials.baseUrl,
10202
+ token: credentials.token,
10203
+ fetcher: runtime.fetcher ?? fetch,
10204
+ body: command.name
10205
+ ? {
10206
+ name: command.name,
10207
+ defaultEnvironment: command.defaultEnvironment ?? 'published',
10208
+ }
10209
+ : undefined,
10210
+ });
10211
+ const dashboards = Array.isArray(payload.dashboards) ? payload.dashboards : [];
10212
+ printDbPayload(command, io, payload, [
10213
+ `app_id: ${command.artifactId}`,
10214
+ command.name
10215
+ ? `dashboard_id: ${String(payload.dashboard?.id ?? '')}`
10216
+ : `dashboards: ${dashboards.length}`,
10217
+ ]);
10218
+ return 0;
10219
+ }
10220
+ function isAppDbRecord(value) {
10221
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
10222
+ }
10223
+ function appDbRecordArray(value) {
10224
+ return Array.isArray(value) ? value.filter(isAppDbRecord) : [];
10225
+ }
10226
+ function appDbStringField(record, field) {
10227
+ const value = record[field];
10228
+ return typeof value === 'string' && value.trim() ? value : null;
10229
+ }
10230
+ function appDbResourcePageSize(resource) {
10231
+ const spec = isAppDbRecord(resource.spec) ? resource.spec : null;
10232
+ const pageSize = spec && typeof spec.pageSize === 'number' && Number.isInteger(spec.pageSize) ? spec.pageSize : 50;
10233
+ return Math.min(Math.max(pageSize, 1), 50);
10234
+ }
10235
+ function appDbDateRangeDays(value) {
10236
+ if (value === 'last_7_days') {
10237
+ return 7;
10238
+ }
10239
+ if (value === 'last_90_days') {
10240
+ return 90;
10241
+ }
10242
+ return 30;
10243
+ }
10244
+ function appDbDashboardDateRangeRequest(dashboard, now) {
10245
+ const spec = isAppDbRecord(dashboard.spec) ? dashboard.spec : {};
10246
+ const controls = isAppDbRecord(spec.controls) ? spec.controls : {};
10247
+ const dateRange = isAppDbRecord(controls.dateRange) ? controls.dateRange : {};
10248
+ const enabled = dateRange.enabled === undefined ? true : dateRange.enabled === true;
10249
+ const defaultRange = dateRange.defaultRange ?? 'last_30_days';
10250
+ if (enabled &&
10251
+ isAppDbRecord(defaultRange) &&
10252
+ defaultRange.type === 'fixed' &&
10253
+ (typeof defaultRange.start === 'string' || typeof defaultRange.start === 'number') &&
10254
+ (typeof defaultRange.end === 'string' || typeof defaultRange.end === 'number')) {
10255
+ return { start: defaultRange.start, end: defaultRange.end };
10256
+ }
10257
+ const preset = typeof defaultRange === 'string' ? defaultRange : 'last_30_days';
10258
+ if (!enabled || preset === 'all_time') {
10259
+ return null;
10260
+ }
10261
+ const end = new Date(now);
10262
+ const start = new Date(end);
10263
+ start.setDate(start.getDate() - (appDbDateRangeDays(preset) - 1));
10264
+ return {
10265
+ start: start.toISOString().slice(0, 10),
10266
+ end: end.toISOString().slice(0, 10),
10267
+ };
10268
+ }
10269
+ async function runDbResourcesSuggest(command, io, runtime) {
10270
+ const credentials = await resolveDbCredentials(command, runtime);
10271
+ const payload = await requestAppDbDashboardApi({
10272
+ artifactId: command.artifactId,
10273
+ path: 'suggest',
10274
+ method: 'POST',
10275
+ baseUrl: credentials.baseUrl,
10276
+ token: credentials.token,
10277
+ fetcher: runtime.fetcher ?? fetch,
10278
+ body: { environment: command.environment },
10279
+ });
10280
+ const resources = Array.isArray(payload.resources) ? payload.resources : [];
10281
+ printDbPayload(command, io, payload, [
10282
+ `app_id: ${command.artifactId}`,
10283
+ `environment: ${command.environment}`,
10284
+ `suggested_resources: ${resources.length}`,
10285
+ ]);
10286
+ return 0;
10287
+ }
10288
+ async function runDbDashboardsRender(command, io, runtime) {
10289
+ const credentials = await resolveDbCredentials(command, runtime);
10290
+ const dashboardPayload = await requestAppDbDashboardApi({
10291
+ artifactId: command.artifactId,
10292
+ path: 'dashboard',
10293
+ dashboardId: command.dashboardId,
10294
+ method: 'GET',
10295
+ baseUrl: credentials.baseUrl,
10296
+ token: credentials.token,
10297
+ fetcher: runtime.fetcher ?? fetch,
10298
+ });
10299
+ const dashboard = isAppDbRecord(dashboardPayload.dashboard) ? dashboardPayload.dashboard : {};
10300
+ const dateRange = appDbDashboardDateRangeRequest(dashboard, runtime.now ? runtime.now() : Date.now());
10301
+ const resources = appDbRecordArray(dashboard.resources).slice(0, 6);
10302
+ const widgets = appDbRecordArray(dashboard.widgets).slice(0, 12);
10303
+ const resourceResults = [];
10304
+ for (const resource of resources) {
10305
+ const resourceId = appDbStringField(resource, 'id');
10306
+ if (!resourceId) {
10307
+ continue;
10308
+ }
10309
+ const views = [
10310
+ { viewId: null, name: 'Default' },
10311
+ ...appDbRecordArray(resource.views)
10312
+ .slice(0, 4)
10313
+ .map((view) => ({
10314
+ viewId: appDbStringField(view, 'id'),
10315
+ name: appDbStringField(view, 'name') ?? 'Saved view',
10316
+ }))
10317
+ .filter((view) => view.viewId !== null),
10318
+ ];
10319
+ const viewResults = [];
10320
+ for (const view of views) {
10321
+ const result = await requestAppDbDashboardApi({
10322
+ artifactId: command.artifactId,
10323
+ path: 'resourceQuery',
10324
+ dashboardId: command.dashboardId,
10325
+ resourceId,
10326
+ method: 'POST',
10327
+ baseUrl: credentials.baseUrl,
10328
+ token: credentials.token,
10329
+ fetcher: runtime.fetcher ?? fetch,
10330
+ body: {
10331
+ environment: command.environment,
10332
+ ...(view.viewId ? { viewId: view.viewId } : {}),
10333
+ ...(dateRange ? { dateRange } : {}),
10334
+ limit: appDbResourcePageSize(resource),
10335
+ offset: 0,
10336
+ },
10337
+ });
10338
+ viewResults.push({
10339
+ viewId: view.viewId,
10340
+ name: view.name,
10341
+ result,
10342
+ });
10343
+ }
10344
+ resourceResults.push({
10345
+ resourceId,
10346
+ label: appDbStringField(resource, 'label') ?? resourceId,
10347
+ views: viewResults,
10348
+ });
10349
+ }
10350
+ const widgetResults = [];
10351
+ for (const widget of widgets) {
10352
+ const widgetId = appDbStringField(widget, 'id');
10353
+ if (!widgetId) {
10354
+ continue;
10355
+ }
10356
+ const spec = isAppDbRecord(widget.spec) ? widget.spec : null;
10357
+ const specLimit = spec && typeof spec.limit === 'number' && Number.isInteger(spec.limit) ? spec.limit : 20;
10358
+ const result = await requestAppDbDashboardApi({
10359
+ artifactId: command.artifactId,
10360
+ path: 'widgetQuery',
10361
+ dashboardId: command.dashboardId,
10362
+ widgetId,
10363
+ method: 'POST',
10364
+ baseUrl: credentials.baseUrl,
10365
+ token: credentials.token,
10366
+ fetcher: runtime.fetcher ?? fetch,
10367
+ body: {
10368
+ environment: command.environment,
10369
+ ...(dateRange ? { dateRange } : {}),
10370
+ limit: Math.min(Math.max(specLimit, 1), 50),
10371
+ offset: 0,
10372
+ },
10373
+ });
10374
+ widgetResults.push({
10375
+ widgetId,
10376
+ title: appDbStringField(widget, 'title') ?? widgetId,
10377
+ result,
10378
+ });
10379
+ }
10380
+ const payload = {
10381
+ ok: true,
10382
+ artifactId: command.artifactId,
10383
+ environment: command.environment,
10384
+ dashboard,
10385
+ resources: resourceResults,
10386
+ widgets: widgetResults,
10387
+ };
10388
+ printDbPayload(command, io, payload, [
10389
+ `app_id: ${command.artifactId}`,
10390
+ `dashboard_id: ${command.dashboardId}`,
10391
+ `environment: ${command.environment}`,
10392
+ `resources_rendered: ${resourceResults.length}`,
10393
+ `widgets_rendered: ${widgetResults.length}`,
10394
+ ]);
10395
+ return 0;
10396
+ }
10397
+ async function readJsonSpecFile(input) {
10398
+ return readAnalyticsSpec({
10399
+ specPath: input.specPath,
10400
+ stdin: input.stdin,
10401
+ });
10402
+ }
10403
+ async function runDbResourcesCreate(command, io, runtime) {
10404
+ const spec = await readJsonSpecFile({
10405
+ specPath: command.specPath,
10406
+ stdin: runtime.stdin,
10407
+ });
10408
+ const views = [];
10409
+ for (const viewPath of command.viewPaths) {
10410
+ views.push({
10411
+ spec: await readJsonSpecFile({
10412
+ specPath: viewPath,
10413
+ stdin: runtime.stdin,
10414
+ }),
10415
+ });
10416
+ }
10417
+ const body = {
10418
+ spec,
10419
+ ...(views.length > 0 ? { views } : {}),
10420
+ };
10421
+ if (command.dryRun) {
10422
+ printDbPayload(command, io, {
10423
+ ok: true,
10424
+ dryRun: true,
10425
+ artifactId: command.artifactId,
10426
+ dashboardId: command.dashboardId,
10427
+ request: body,
10428
+ }, [`app_id: ${command.artifactId}`, `dashboard_id: ${command.dashboardId}`, 'dry_run: true']);
10429
+ return 0;
10430
+ }
10431
+ const credentials = await resolveDbCredentials(command, runtime);
10432
+ const payload = await requestAppDbDashboardApi({
10433
+ artifactId: command.artifactId,
10434
+ path: 'resources',
10435
+ dashboardId: command.dashboardId,
10436
+ method: 'POST',
10437
+ baseUrl: credentials.baseUrl,
10438
+ token: credentials.token,
10439
+ fetcher: runtime.fetcher ?? fetch,
10440
+ body,
10441
+ });
10442
+ printDbPayload(command, io, payload, [
10443
+ `app_id: ${command.artifactId}`,
10444
+ `dashboard_id: ${command.dashboardId}`,
10445
+ `resource_id: ${String(payload.resource?.id ?? '')}`,
10446
+ ]);
10447
+ return 0;
10448
+ }
10449
+ async function runDbWidgetsCreate(command, io, runtime) {
10450
+ const spec = await readJsonSpecFile({
10451
+ specPath: command.specPath,
10452
+ stdin: runtime.stdin,
10453
+ });
10454
+ const body = {
10455
+ title: command.title,
10456
+ spec,
10457
+ };
10458
+ if (command.dryRun) {
10459
+ printDbPayload(command, io, {
10460
+ ok: true,
10461
+ dryRun: true,
10462
+ artifactId: command.artifactId,
10463
+ dashboardId: command.dashboardId,
10464
+ request: body,
10465
+ }, [`app_id: ${command.artifactId}`, `dashboard_id: ${command.dashboardId}`, `title: ${command.title}`, 'dry_run: true']);
10466
+ return 0;
10467
+ }
10468
+ const credentials = await resolveDbCredentials(command, runtime);
10469
+ const payload = await requestAppDbDashboardApi({
10470
+ artifactId: command.artifactId,
10471
+ path: 'widgets',
10472
+ dashboardId: command.dashboardId,
10473
+ method: 'POST',
10474
+ baseUrl: credentials.baseUrl,
10475
+ token: credentials.token,
10476
+ fetcher: runtime.fetcher ?? fetch,
10477
+ body,
10478
+ });
10479
+ printDbPayload(command, io, payload, [
10480
+ `app_id: ${command.artifactId}`,
10481
+ `dashboard_id: ${command.dashboardId}`,
10482
+ `widget_id: ${String(payload.widget?.id ?? '')}`,
10483
+ ]);
10484
+ return 0;
10485
+ }
9532
10486
  async function runAnalyticsSummary(command, io, runtime) {
9533
10487
  const credentials = await resolveAnalyticsCredentials(command, runtime);
9534
10488
  const payload = await requestAnalyticsApi({
@@ -10150,6 +11104,9 @@ export async function runTenderCli(args, io = {
10150
11104
  else if (command.topic === 'app preview watch') {
10151
11105
  io.stdout(previewWatchHelp());
10152
11106
  }
11107
+ else if (command.topic === 'app agent') {
11108
+ io.stdout(appAgentHelp());
11109
+ }
10153
11110
  else if (command.topic === 'app agent heartbeat') {
10154
11111
  io.stdout(agentHeartbeatHelp());
10155
11112
  }
@@ -10189,6 +11146,9 @@ export async function runTenderCli(args, io = {
10189
11146
  else if (command.topic === 'app analytics dashboards') {
10190
11147
  io.stdout(analyticsDashboardsHelp());
10191
11148
  }
11149
+ else if (command.topic === 'app analytics provision') {
11150
+ io.stdout(analyticsProvisionHelp());
11151
+ }
10192
11152
  else if (command.topic === 'app analytics export') {
10193
11153
  io.stdout(analyticsExportHelp());
10194
11154
  }
@@ -10234,6 +11194,21 @@ export async function runTenderCli(args, io = {
10234
11194
  else if (command.topic === 'app db query') {
10235
11195
  io.stdout(dbQueryHelp());
10236
11196
  }
11197
+ else if (command.topic === 'app db dashboards') {
11198
+ io.stdout(dbDashboardsHelp());
11199
+ }
11200
+ else if (command.topic === 'app db dashboards render') {
11201
+ io.stdout(dbDashboardsRenderHelp());
11202
+ }
11203
+ else if (command.topic === 'app db resources suggest') {
11204
+ io.stdout(dbResourcesSuggestHelp());
11205
+ }
11206
+ else if (command.topic === 'app db resources create') {
11207
+ io.stdout(dbResourcesCreateHelp());
11208
+ }
11209
+ else if (command.topic === 'app db widgets create') {
11210
+ io.stdout(dbWidgetsCreateHelp());
11211
+ }
10237
11212
  else if (command.topic === 'app') {
10238
11213
  io.stdout(appHelp());
10239
11214
  }
@@ -10383,6 +11358,9 @@ export async function runTenderCli(args, io = {
10383
11358
  if (command.command === 'app analytics dashboards') {
10384
11359
  return await runAnalyticsDashboards(command, io, runtime);
10385
11360
  }
11361
+ if (command.command === 'app analytics provision') {
11362
+ return runAnalyticsProvision(command, io);
11363
+ }
10386
11364
  if (command.command === 'app analytics export catalog') {
10387
11365
  return await runAnalyticsExportCatalog(command, io, runtime);
10388
11366
  }
@@ -10422,6 +11400,21 @@ export async function runTenderCli(args, io = {
10422
11400
  if (command.command === 'app db query') {
10423
11401
  return await runDbQuery(command, io, runtime);
10424
11402
  }
11403
+ if (command.command === 'app db dashboards') {
11404
+ return await runDbDashboards(command, io, runtime);
11405
+ }
11406
+ if (command.command === 'app db resources suggest') {
11407
+ return await runDbResourcesSuggest(command, io, runtime);
11408
+ }
11409
+ if (command.command === 'app db dashboards render') {
11410
+ return await runDbDashboardsRender(command, io, runtime);
11411
+ }
11412
+ if (command.command === 'app db resources create') {
11413
+ return await runDbResourcesCreate(command, io, runtime);
11414
+ }
11415
+ if (command.command === 'app db widgets create') {
11416
+ return await runDbWidgetsCreate(command, io, runtime);
11417
+ }
10425
11418
  if (command.command === 'app git remote') {
10426
11419
  return await runGitRemote(command, io, runtime);
10427
11420
  }
@@ -10450,6 +11443,8 @@ export function shouldReadEntrypointStdin(args) {
10450
11443
  ((args[1] === 'git' && args[2] === 'credential') ||
10451
11444
  (args[1] === 'analytics' && hasFlagValue(args, '--spec', '-')) ||
10452
11445
  (args[1] === 'agent' && args[2] === 'heartbeat' && hasFlagValue(args, '--input', '-')) ||
11446
+ (args[1] === 'db' && (args[2] === 'resources' || args[2] === 'widgets') && hasFlagValue(args, '--spec', '-')) ||
11447
+ (args[1] === 'db' && args[2] === 'resources' && hasFlagValue(args, '--view', '-')) ||
10453
11448
  (args[1] === 'db' && args[2] === 'query' && hasFlagValue(args, '--file', '-'))));
10454
11449
  }
10455
11450
  async function readEntrypointStdin() {