@tenderprompt/cli 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +69 -8
  2. package/dist/index.js +703 -275
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -43,14 +43,24 @@ class TenderCliAnalyticsApiError extends Error {
43
43
  reasonCode;
44
44
  constructor(message, reasonCode) {
45
45
  super(message);
46
- this.reasonCode = reasonCode;
47
46
  this.name = "TenderCliAnalyticsApiError";
47
+ this.reasonCode = reasonCode;
48
+ }
49
+ }
50
+ class TenderCliDbApiError extends Error {
51
+ reasonCode;
52
+ constructor(message, reasonCode) {
53
+ super(message);
54
+ this.name = "TenderCliDbApiError";
55
+ this.reasonCode = reasonCode;
48
56
  }
49
57
  }
50
58
  const DEFAULT_PROFILE_NAME = "default";
51
59
  const DEFAULT_BASE_URL = "https://app.tenderprompt.com";
52
60
  const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
53
61
  const ARTIFACT_SOURCE_EXPORT_MAX_FILE_BYTES = 2 * 1024 * 1024;
62
+ const DB_SQL_STATEMENT_MAX_BYTES = 100 * 1024;
63
+ const DB_MAX_BOUND_PARAMETERS = 100;
54
64
  function rootHelp() {
55
65
  return `Usage: tender <command> [options]
56
66
 
@@ -89,6 +99,10 @@ Commands:
89
99
  Run a server-validated chart spec
90
100
  tender app analytics suggestions <app-id>
91
101
  Generate useful starter chart suggestions
102
+ tender app db capabilities <app-id>
103
+ Describe app database query limits and policy
104
+ tender app db query <app-id> --sql <sql>
105
+ Run one read-only app database query
92
106
  tender app context fetch <app-id>
93
107
  Fetch managed agent context into a checkout
94
108
  tender app context status <app-id>
@@ -120,6 +134,10 @@ Examples:
120
134
  tender app analytics summary artifact_123 --range 30d --json
121
135
  tender app analytics query artifact_123 --spec chart.json --json
122
136
  tender app analytics suggestions artifact_123 --range 30d --json
137
+ tender app db capabilities artifact_123 --json
138
+ tender app db tables artifact_123 --json
139
+ tender app db schema artifact_123 --table votes --json
140
+ tender app db query artifact_123 --sql "select * from votes limit 20" --json
123
141
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
124
142
  tender app context status artifact_123 --dir ./widget --json
125
143
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -142,12 +160,12 @@ Examples:
142
160
  tender auth status --json`;
143
161
  }
144
162
  function authLoginHelp() {
145
- return `Usage: tender auth login --device [--base-url <url>] [--profile <edit-preview|publish|create>] [--artifact <artifact-id>] [--ttl <7d|14d|30d>] [--save-profile <name>] [--no-poll] [--json]
163
+ return `Usage: tender auth login --device [--base-url <url>] [--profile <edit-preview|publish|create|db-read>] [--artifact <artifact-id>] [--ttl <7d|14d|30d>] [--save-profile <name>] [--no-poll] [--json]
146
164
 
147
165
  Options:
148
166
  --device Use the OAuth device-code flow.
149
167
  --base-url <url> Tender base URL. Defaults to TENDER_BASE_URL or https://app.tenderprompt.com.
150
- --profile <value> Token permission profile: edit-preview, publish, or create. Defaults to edit-preview.
168
+ --profile <value> Token permission profile: edit-preview, publish, create, or db-read. Defaults to edit-preview.
151
169
  --artifact <artifact-id> Scope the token to one artifact. Required for normal edit-preview agent work.
152
170
  --ttl <value> Token lifetime: 7d, 14d, or 30d. Defaults to 7d.
153
171
  --save-profile <name> Local config profile name. Defaults to default.
@@ -157,7 +175,8 @@ Options:
157
175
  Examples:
158
176
  tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
159
177
  tender auth login --device --profile publish --artifact artifact_123 --ttl 7d --save-profile publish --json
160
- tender auth login --device --profile create --ttl 7d --save-profile account`;
178
+ tender auth login --device --profile create --ttl 7d --save-profile account
179
+ tender auth login --device --profile db-read --artifact artifact_123 --ttl 7d --save-profile db`;
161
180
  }
162
181
  function authStatusHelp() {
163
182
  return `Usage: tender auth status [--profile <name>] [--json]
@@ -213,10 +232,18 @@ Commands:
213
232
  Update a saved chart title or spec
214
233
  tender app analytics charts delete <app-id>
215
234
  Delete a saved chart
216
- tender app analytics property-slots list <app-id>
217
- List promoted property slots
218
- tender app analytics property-slots activate <app-id>
219
- Activate a promoted property slot
235
+ tender app analytics properties list <app-id>
236
+ List properties available to dashboard queries
237
+ tender app analytics properties activate <app-id>
238
+ Make a custom event property queryable
239
+ tender app db capabilities <app-id>
240
+ Describe app database query limits and policy
241
+ tender app db tables <app-id>
242
+ List app database tables and views
243
+ tender app db schema <app-id> --table <name>
244
+ Show table and index schema for one table
245
+ tender app db query <app-id> --sql <sql>
246
+ Run one read-only app database query
220
247
  tender app context fetch <app-id>
221
248
  Fetch AGENTS.md, skills, and Tender App scaffold files
222
249
  tender app context status <app-id>
@@ -255,8 +282,12 @@ Examples:
255
282
  tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
256
283
  tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
257
284
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
258
- tender app analytics property-slots list artifact_123 --json
259
- tender app analytics property-slots activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
285
+ tender app analytics properties list artifact_123 --json
286
+ tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
287
+ tender app db capabilities artifact_123 --json
288
+ tender app db tables artifact_123 --json
289
+ tender app db schema artifact_123 --table votes --json
290
+ tender app db query artifact_123 --sql "select * from votes limit 20" --json
260
291
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
261
292
  tender app context status artifact_123 --dir ./widget --json
262
293
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -294,8 +325,8 @@ Commands:
294
325
  tender app analytics charts list <app-id> List saved charts in one dashboard
295
326
  tender app analytics charts update <app-id> Update a saved chart
296
327
  tender app analytics charts delete <app-id> Delete a saved chart
297
- tender app analytics property-slots list <app-id> List promoted property slots
298
- tender app analytics property-slots activate <app-id> Activate a promoted property slot
328
+ tender app analytics properties list <app-id> List queryable custom event properties
329
+ tender app analytics properties activate <app-id> Make a property queryable
299
330
 
300
331
  Examples:
301
332
  tender app analytics summary artifact_123 --range 30d --json
@@ -311,8 +342,8 @@ Examples:
311
342
  tender app analytics charts list artifact_123 --dashboard analytics_dashboard_123 --json
312
343
  tender app analytics charts update artifact_123 --chart analytics_chart_123 --title "Signup funnel" --spec funnel.json --dry-run --json
313
344
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
314
- tender app analytics property-slots list artifact_123 --json
315
- tender app analytics property-slots activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json`;
345
+ tender app analytics properties list artifact_123 --json
346
+ tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json`;
316
347
  }
317
348
  function analyticsSummaryHelp() {
318
349
  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]
@@ -388,7 +419,7 @@ Examples:
388
419
  function analyticsChartsCreateHelp() {
389
420
  return `Usage: tender app analytics charts create <app-id> --dashboard <dashboard-id> --title <title> --spec <file|-> [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
390
421
 
391
- Property charts automatically activate required promoted property slots before
422
+ Property charts automatically make required custom event properties queryable before
392
423
  saving when the spec has an event_name = filter.
393
424
 
394
425
  Examples:
@@ -406,7 +437,7 @@ function analyticsChartsUpdateHelp() {
406
437
 
407
438
  At least one of --title or --spec is required. When one is omitted, the CLI reads
408
439
  the existing saved chart and keeps that value.
409
- Property charts automatically activate required promoted property slots before
440
+ Property charts automatically make required custom event properties queryable before
410
441
  saving when the spec has an event_name = filter.
411
442
 
412
443
  Examples:
@@ -421,19 +452,70 @@ Examples:
421
452
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
422
453
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --json`;
423
454
  }
424
- function analyticsPropertySlotsListHelp() {
425
- return `Usage: tender app analytics property-slots list <app-id> [--event <event-name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
455
+ function analyticsPropertiesListHelp() {
456
+ return `Usage: tender app analytics properties list <app-id> [--event <event-name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
457
+
458
+ Examples:
459
+ tender app analytics properties list artifact_123 --json
460
+ tender app analytics properties list artifact_123 --event signup_started --json`;
461
+ }
462
+ function analyticsPropertiesActivateHelp() {
463
+ return `Usage: tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type <dimension|metric> [--label <label>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
464
+
465
+ Examples:
466
+ tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
467
+ tender app analytics properties activate artifact_123 --event score_saved --property score --type metric --json`;
468
+ }
469
+ function dbHelp() {
470
+ return `Usage: tender app db <command> <app-id> [options]
471
+
472
+ Commands:
473
+ tender app db capabilities <app-id> Describe app database policy and limits
474
+ tender app db tables <app-id> List app database tables and views
475
+ tender app db schema <app-id> Show schema for one table
476
+ tender app db query <app-id> Run one read-only SQL query
477
+
478
+ Examples:
479
+ tender app db capabilities artifact_123 --json
480
+ tender app db tables artifact_123 --json
481
+ tender app db schema artifact_123 --table votes --json
482
+ tender app db query artifact_123 --sql "select * from votes limit 20" --json
483
+ tender app db query artifact_123 --file ./debug.sql --param active --limit 50 --json`;
484
+ }
485
+ function dbCapabilitiesHelp() {
486
+ return `Usage: tender app db capabilities <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
487
+
488
+ Examples:
489
+ tender app db capabilities artifact_123 --json`;
490
+ }
491
+ function dbTablesHelp() {
492
+ return `Usage: tender app db tables <app-id> [--env preview] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
493
+
494
+ Examples:
495
+ tender app db tables artifact_123 --json
496
+ tender app db tables artifact_123 --env preview --json`;
497
+ }
498
+ function dbSchemaHelp() {
499
+ return `Usage: tender app db schema <app-id> --table <name> [--env preview] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
426
500
 
427
501
  Examples:
428
- tender app analytics property-slots list artifact_123 --json
429
- tender app analytics property-slots list artifact_123 --event signup_started --json`;
502
+ tender app db schema artifact_123 --table votes --json
503
+ tender app db schema artifact_123 --table preference_votes --env preview --json`;
430
504
  }
431
- function analyticsPropertySlotsActivateHelp() {
432
- return `Usage: tender app analytics property-slots activate <app-id> --event <event-name> --property <property-key> --type <dimension|metric> [--slot <slot-name>] [--label <label>] [--dry-run] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
505
+ function dbQueryHelp() {
506
+ return `Usage: tender app db query <app-id> (--sql <sql> | --file <path|->) [--param <value>] [--limit <n>] [--env preview] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
507
+
508
+ Options:
509
+ --sql <sql> SQL statement. Must be one read-only statement.
510
+ --file <path|-> Read SQL from a file or stdin with -.
511
+ --param <value> Bind one positional parameter. Repeat for multiple params. Values parse as JSON scalars, falling back to string.
512
+ --limit <n> Interactive row limit. Server caps the maximum.
433
513
 
434
514
  Examples:
435
- tender app analytics property-slots activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
436
- tender app analytics property-slots activate artifact_123 --event score_saved --property score --type metric --json`;
515
+ tender app db query artifact_123 --sql "select * from votes limit 20" --json
516
+ tender app db query artifact_123 --sql "select * from votes where choice = ?" --param coffee --json
517
+ tender app db query artifact_123 --file ./debug.sql --limit 50 --json
518
+ tender app db query artifact_123 --file - --json < debug.sql`;
437
519
  }
438
520
  function artifactsListHelp() {
439
521
  return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
@@ -767,6 +849,7 @@ function tenderCliCapabilities() {
767
849
  "tender auth --help",
768
850
  "tender app --help",
769
851
  "tender app analytics --help",
852
+ "tender app db --help",
770
853
  "tender app bindings outbound --help",
771
854
  ],
772
855
  },
@@ -816,8 +899,8 @@ function tenderCliCapabilities() {
816
899
  "tender app analytics charts templates --json",
817
900
  "tender app analytics query <artifact-id> --spec chart.json --json",
818
901
  "tender app analytics export query <artifact-id> --spec chart.json --format csv",
819
- "tender app analytics property-slots list <artifact-id> --json",
820
- "tender app analytics property-slots activate <artifact-id> --event <event-name> --property <property-key> --type dimension --dry-run --json",
902
+ "tender app analytics properties list <artifact-id> --json",
903
+ "tender app analytics properties activate <artifact-id> --event <event-name> --property <property-key> --type dimension --dry-run --json",
821
904
  "tender app analytics dashboards <artifact-id> --create \"Agent dashboard\" --dry-run --json",
822
905
  "tender app analytics charts create <artifact-id> --dashboard <dashboard-id> --title <title> --spec chart.json --dry-run --json",
823
906
  "tender app analytics charts list <artifact-id> --dashboard <dashboard-id> --json",
@@ -827,11 +910,28 @@ function tenderCliCapabilities() {
827
910
  notes: [
828
911
  "Combine CLI capabilities with local source inspection of analytics calls.",
829
912
  "Validate chart specs with query before creating or updating saved charts.",
830
- "Property chart create/update commands automatically activate missing promoted property slots when the spec has an event_name = filter.",
831
- "Ad hoc query is read-only; if it reports a missing property slot, activate the slot first or save the chart through charts create/update.",
913
+ "Property chart create/update commands automatically make missing custom event properties queryable when the spec has an event_name = filter.",
914
+ "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.",
832
915
  "Use list before writes so agents can avoid duplicates and explain the exact chart they are changing.",
833
916
  ],
834
917
  },
918
+ {
919
+ name: "app_database_inspection",
920
+ when: "When inspecting app-local database tables from a scoped token.",
921
+ commands: [
922
+ "tender auth login --device --profile db-read --artifact <artifact-id> --ttl 7d --save-profile db --json",
923
+ "tender app db capabilities <artifact-id> --profile db --json",
924
+ "tender app db tables <artifact-id> --profile db --json",
925
+ "tender app db schema <artifact-id> --table <table-name> --profile db --json",
926
+ "tender app db query <artifact-id> --sql \"select * from <table-name> limit 20\" --profile db --json",
927
+ "tender app db query <artifact-id> --file ./debug.sql --profile db --json",
928
+ ],
929
+ notes: [
930
+ "The CLI only accepts artifact ids. It never accepts tenant ids, runner names, provider account ids, or runtime storage identifiers.",
931
+ "Read-only policy is enforced by the server before the runtime shim executes SQL.",
932
+ "Use --file - to pipe SQL from stdin without prompts.",
933
+ ],
934
+ },
835
935
  {
836
936
  name: "outbound_http_binding",
837
937
  when: "When app code needs external APIs, OAuth/token exchange, webhooks, provider SDKs, or secrets.",
@@ -886,14 +986,14 @@ function tenderCliCapabilities() {
886
986
  writes: false,
887
987
  },
888
988
  {
889
- command: "tender app analytics property-slots list <artifact-id> --json",
890
- purpose: "List active promoted property slots that make custom event properties queryable.",
989
+ command: "tender app analytics properties list <artifact-id> --json",
990
+ purpose: "List custom event properties available to dashboard queries.",
891
991
  requiresAuth: true,
892
992
  writes: false,
893
993
  },
894
994
  {
895
- command: "tender app analytics property-slots activate <artifact-id> --event <event-name> --property <property-key> --type <dimension|metric> --dry-run --json",
896
- purpose: "Preview or activate the promoted slot needed for a property group-by or property metric chart.",
995
+ command: "tender app analytics properties activate <artifact-id> --event <event-name> --property <property-key> --type <dimension|metric> --dry-run --json",
996
+ purpose: "Preview or activate the custom event property needed for a property group-by or property metric chart.",
897
997
  requiresAuth: true,
898
998
  writes: true,
899
999
  dryRun: true,
@@ -925,6 +1025,30 @@ function tenderCliCapabilities() {
925
1025
  writes: true,
926
1026
  dryRun: true,
927
1027
  },
1028
+ {
1029
+ command: "tender app db capabilities <artifact-id> --json",
1030
+ purpose: "Inspect app database policy, limits, and supported capabilities for one artifact.",
1031
+ requiresAuth: true,
1032
+ writes: false,
1033
+ },
1034
+ {
1035
+ command: "tender app db tables <artifact-id> --json",
1036
+ purpose: "List app database tables and views for one artifact.",
1037
+ requiresAuth: true,
1038
+ writes: false,
1039
+ },
1040
+ {
1041
+ command: "tender app db schema <artifact-id> --table <table-name> --json",
1042
+ purpose: "Inspect table, index, and trigger schema for one app database table.",
1043
+ requiresAuth: true,
1044
+ writes: false,
1045
+ },
1046
+ {
1047
+ command: "tender app db query <artifact-id> --sql \"select * from <table-name> limit 20\" --json",
1048
+ purpose: "Run one bounded read-only app database query.",
1049
+ requiresAuth: true,
1050
+ writes: false,
1051
+ },
928
1052
  ],
929
1053
  security: [
930
1054
  "Prefer artifact-scoped device tokens.",
@@ -932,6 +1056,7 @@ function tenderCliCapabilities() {
932
1056
  "Do not call Tender HTTP APIs directly from agent workflows.",
933
1057
  "Do not put third-party fetch calls in src/server.ts; use outbound bindings.",
934
1058
  "Do not request direct query credentials or raw SQL for analytics.",
1059
+ "Do not use tenant ids, runner names, provider account ids, or runtime storage identifiers for app database inspection.",
935
1060
  ],
936
1061
  };
937
1062
  }
@@ -2264,10 +2389,11 @@ function parseContextRefreshArgs(args) {
2264
2389
  function parseTokenProfile(value) {
2265
2390
  if (value === "edit-preview" ||
2266
2391
  value === "publish" ||
2267
- value === "create") {
2392
+ value === "create" ||
2393
+ value === "db-read") {
2268
2394
  return value;
2269
2395
  }
2270
- throw new TenderCliUsageError("--profile must be one of edit-preview, publish, or create.");
2396
+ throw new TenderCliUsageError("--profile must be one of edit-preview, publish, create, or db-read.");
2271
2397
  }
2272
2398
  function parsePositiveIntegerFlag(value, flag) {
2273
2399
  const parsed = Number(value);
@@ -2341,6 +2467,9 @@ function parseAuthLoginArgs(args) {
2341
2467
  if (tokenProfile === "create" && artifactId) {
2342
2468
  throw new TenderCliUsageError("--profile create must be account-scoped; remove --artifact.");
2343
2469
  }
2470
+ if (tokenProfile === "db-read" && !artifactId) {
2471
+ throw new TenderCliUsageError("--profile db-read requires --artifact <artifact-id>.");
2472
+ }
2344
2473
  return {
2345
2474
  command: "auth login",
2346
2475
  baseUrl,
@@ -2938,13 +3067,15 @@ function analyticsChartTemplates() {
2938
3067
  id: "property_breakdown",
2939
3068
  title: "Property breakdown",
2940
3069
  visualization: "bar",
2941
- purpose: "Compare a promoted dimension property observed in events.",
3070
+ purpose: "Compare a queryable dimension property observed in events.",
2942
3071
  usableAsIs: false,
2943
3072
  placeholders: {
2944
- property: "<promoted_dimension_property>",
3073
+ eventName: "<event_name>",
3074
+ property: "<queryable_dimension_property>",
2945
3075
  },
2946
3076
  adaptWith: [
2947
- "Use a property key promoted by capabilities chartSpec.groupBy, such as property:plan.",
3077
+ "Use a queryable property key from capabilities chartSpec.groupBy, such as property:plan.",
3078
+ "Choose the event that owns the property and keep the event_name filter.",
2948
3079
  "Choose stable categorical properties with enough observations to compare.",
2949
3080
  ],
2950
3081
  spec: {
@@ -2952,8 +3083,8 @@ function analyticsChartTemplates() {
2952
3083
  dataset: "events",
2953
3084
  visualization: "bar",
2954
3085
  metric: { name: "custom_event_count" },
2955
- groupBy: [{ field: "property:<promoted_dimension_property>" }],
2956
- filters: [],
3086
+ groupBy: [{ field: "property:<queryable_dimension_property>" }],
3087
+ filters: [{ field: "event_name", op: "=", value: "<event_name>" }],
2957
3088
  range: { preset: "30d" },
2958
3089
  sort: [{ field: "value", direction: "desc" }],
2959
3090
  limit: 10,
@@ -2966,19 +3097,21 @@ function analyticsChartTemplates() {
2966
3097
  purpose: "Track an observed numeric event property over time.",
2967
3098
  usableAsIs: false,
2968
3099
  placeholders: {
2969
- metric: "avg:<promoted_metric_property>",
3100
+ eventName: "<event_name>",
3101
+ metric: "avg:<queryable_metric_property>",
2970
3102
  },
2971
3103
  adaptWith: [
2972
- "Use a numeric property key promoted by capabilities chartSpec.metrics.",
3104
+ "Use a queryable numeric property key from capabilities chartSpec.metrics.",
3105
+ "Choose the event that owns the property and keep the event_name filter.",
2973
3106
  "Prefer averages for scores, durations, quantities, or ordinal values.",
2974
3107
  ],
2975
3108
  spec: {
2976
3109
  version: 1,
2977
3110
  dataset: "events",
2978
3111
  visualization: "line",
2979
- metric: { name: "avg:<promoted_metric_property>" },
3112
+ metric: { name: "avg:<queryable_metric_property>" },
2980
3113
  groupBy: [{ field: "day" }],
2981
- filters: [],
3114
+ filters: [{ field: "event_name", op: "=", value: "<event_name>" }],
2982
3115
  range: { preset: "30d" },
2983
3116
  sort: [{ field: "label", direction: "asc" }],
2984
3117
  limit: 30,
@@ -3019,7 +3152,7 @@ function runAnalyticsChartsTemplates(command, io) {
3019
3152
  templates,
3020
3153
  next: [
3021
3154
  "Run tender app analytics capabilities <artifact-id> --include-catalog --range 30d --json.",
3022
- "Choose a template and replace placeholders with observed events, paths, or promoted property keys.",
3155
+ "Choose a template and replace placeholders with observed events, paths, or queryable property keys.",
3023
3156
  "Validate with tender app analytics query <artifact-id> --spec chart.json --json.",
3024
3157
  "Preview saved writes with tender app analytics dashboards ... --dry-run and charts create ... --dry-run.",
3025
3158
  ],
@@ -3206,13 +3339,13 @@ function parseAnalyticsChartsDeleteArgs(args) {
3206
3339
  ...parsed,
3207
3340
  };
3208
3341
  }
3209
- function parseAnalyticsPropertySlotsListArgs(args) {
3342
+ function parseAnalyticsPropertiesListArgs(args) {
3210
3343
  if (args.includes("--help") || args.includes("-h")) {
3211
- return { command: "help", topic: "app analytics property-slots list" };
3344
+ return { command: "help", topic: "app analytics properties list" };
3212
3345
  }
3213
3346
  const artifactId = args[0];
3214
3347
  if (!artifactId || artifactId.startsWith("-")) {
3215
- throw new TenderCliUsageError("app id is required. Example: tender app analytics property-slots list <app-id> --json");
3348
+ throw new TenderCliUsageError("app id is required. Example: tender app analytics properties list <app-id> --json");
3216
3349
  }
3217
3350
  const parsed = parseAnalyticsCommonArgs(args, 1);
3218
3351
  let eventName = null;
@@ -3223,28 +3356,27 @@ function parseAnalyticsPropertySlotsListArgs(args) {
3223
3356
  index += 1;
3224
3357
  continue;
3225
3358
  }
3226
- throw new TenderCliUsageError(`Unknown analytics property-slots list option: ${arg}`);
3359
+ throw new TenderCliUsageError(`Unknown analytics properties list option: ${arg}`);
3227
3360
  }
3228
3361
  return {
3229
- command: "app analytics property-slots list",
3362
+ command: "app analytics properties list",
3230
3363
  artifactId,
3231
3364
  eventName,
3232
3365
  ...parsed,
3233
3366
  };
3234
3367
  }
3235
- function parseAnalyticsPropertySlotsActivateArgs(args) {
3368
+ function parseAnalyticsPropertiesActivateArgs(args) {
3236
3369
  if (args.includes("--help") || args.includes("-h")) {
3237
- return { command: "help", topic: "app analytics property-slots activate" };
3370
+ return { command: "help", topic: "app analytics properties activate" };
3238
3371
  }
3239
3372
  const artifactId = args[0];
3240
3373
  if (!artifactId || artifactId.startsWith("-")) {
3241
- throw new TenderCliUsageError("app id is required. Example: tender app analytics property-slots activate <app-id> --event <event-name> --property <property-key> --type dimension --json");
3374
+ throw new TenderCliUsageError("app id is required. Example: tender app analytics properties activate <app-id> --event <event-name> --property <property-key> --type dimension --json");
3242
3375
  }
3243
3376
  const parsed = parseAnalyticsCommonArgs(args, 1);
3244
3377
  let eventName = null;
3245
3378
  let propertyKey = null;
3246
- let slotType = null;
3247
- let slotName = null;
3379
+ let propertyType = null;
3248
3380
  let label = null;
3249
3381
  let dryRun = false;
3250
3382
  for (let index = 0; index < parsed.remaining.length; index += 1) {
@@ -3264,12 +3396,7 @@ function parseAnalyticsPropertySlotsActivateArgs(args) {
3264
3396
  if (value !== "dimension" && value !== "metric") {
3265
3397
  throw new TenderCliUsageError("--type must be dimension or metric.");
3266
3398
  }
3267
- slotType = value;
3268
- index += 1;
3269
- continue;
3270
- }
3271
- if (arg === "--slot") {
3272
- slotName = parseRequiredFlagValue(parsed.remaining[index + 1], "--slot");
3399
+ propertyType = value;
3273
3400
  index += 1;
3274
3401
  continue;
3275
3402
  }
@@ -3282,23 +3409,227 @@ function parseAnalyticsPropertySlotsActivateArgs(args) {
3282
3409
  dryRun = true;
3283
3410
  continue;
3284
3411
  }
3285
- throw new TenderCliUsageError(`Unknown analytics property-slots activate option: ${arg}`);
3412
+ throw new TenderCliUsageError(`Unknown analytics properties activate option: ${arg}`);
3286
3413
  }
3287
- if (!eventName || !propertyKey || !slotType) {
3288
- throw new TenderCliUsageError("--event, --property, and --type are required. Example: tender app analytics property-slots activate artifact_123 --event signup_started --property plan --type dimension --json");
3414
+ if (!eventName || !propertyKey || !propertyType) {
3415
+ throw new TenderCliUsageError("--event, --property, and --type are required. Example: tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --json");
3289
3416
  }
3290
3417
  return {
3291
- command: "app analytics property-slots activate",
3418
+ command: "app analytics properties activate",
3292
3419
  artifactId,
3293
3420
  eventName,
3294
3421
  propertyKey,
3295
- slotType,
3296
- slotName,
3422
+ propertyType,
3297
3423
  label,
3298
3424
  dryRun,
3299
3425
  ...parsed,
3300
3426
  };
3301
3427
  }
3428
+ function parseDbCommonArgs(args, startIndex) {
3429
+ let baseUrl = null;
3430
+ let flagToken = null;
3431
+ let profileName = null;
3432
+ let json = false;
3433
+ const remaining = [];
3434
+ for (let index = startIndex; index < args.length; index += 1) {
3435
+ const arg = args[index];
3436
+ if (arg === "--json") {
3437
+ json = true;
3438
+ continue;
3439
+ }
3440
+ if (arg === "--base-url") {
3441
+ baseUrl = parseBaseUrlFlag(args, index);
3442
+ index += 1;
3443
+ continue;
3444
+ }
3445
+ if (arg === "--token") {
3446
+ flagToken = parseTokenFlag(args, index);
3447
+ index += 1;
3448
+ continue;
3449
+ }
3450
+ if (arg === "--profile") {
3451
+ profileName = parseProfileName(args[index + 1], "--profile");
3452
+ index += 1;
3453
+ continue;
3454
+ }
3455
+ remaining.push(arg);
3456
+ }
3457
+ return { baseUrl, flagToken, profileName, json, remaining };
3458
+ }
3459
+ function parseDbEnvironment(value) {
3460
+ if (value === undefined || value === "preview") {
3461
+ return "preview";
3462
+ }
3463
+ throw new TenderCliUsageError("--env must be preview.");
3464
+ }
3465
+ function parseDbFlagValue(value, flag) {
3466
+ if (!value || value.startsWith("--")) {
3467
+ throw new TenderCliUsageError(`${flag} requires a value.`);
3468
+ }
3469
+ return value;
3470
+ }
3471
+ function parseDbFilePath(value) {
3472
+ if (!value || (value !== "-" && value.startsWith("-"))) {
3473
+ throw new TenderCliUsageError("--file requires a SQL file path or - for stdin.");
3474
+ }
3475
+ return value;
3476
+ }
3477
+ function parseDbParameter(value) {
3478
+ const raw = parseDbFlagValue(value, "--param");
3479
+ try {
3480
+ const parsed = JSON.parse(raw);
3481
+ if (parsed === null ||
3482
+ typeof parsed === "string" ||
3483
+ typeof parsed === "number") {
3484
+ return parsed;
3485
+ }
3486
+ }
3487
+ catch {
3488
+ // Fall back to a string binding.
3489
+ }
3490
+ return raw;
3491
+ }
3492
+ function parseDbCapabilitiesArgs(args) {
3493
+ if (args.includes("--help") || args.includes("-h")) {
3494
+ return { command: "help", topic: "app db capabilities" };
3495
+ }
3496
+ const artifactId = args[0];
3497
+ if (!artifactId || artifactId.startsWith("-")) {
3498
+ throw new TenderCliUsageError("app id is required. Example: tender app db capabilities <app-id> --json");
3499
+ }
3500
+ const parsed = parseDbCommonArgs(args, 1);
3501
+ if (parsed.remaining.length > 0) {
3502
+ throw new TenderCliUsageError(`Unknown app db capabilities option: ${parsed.remaining[0]}`);
3503
+ }
3504
+ return { command: "app db capabilities", artifactId, ...parsed };
3505
+ }
3506
+ function parseDbTablesArgs(args) {
3507
+ if (args.includes("--help") || args.includes("-h")) {
3508
+ return { command: "help", topic: "app db tables" };
3509
+ }
3510
+ const artifactId = args[0];
3511
+ if (!artifactId || artifactId.startsWith("-")) {
3512
+ throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --json");
3513
+ }
3514
+ const parsed = parseDbCommonArgs(args, 1);
3515
+ let environment = "preview";
3516
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3517
+ const arg = parsed.remaining[index];
3518
+ if (arg === "--env") {
3519
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3520
+ index += 1;
3521
+ continue;
3522
+ }
3523
+ throw new TenderCliUsageError(`Unknown app db tables option: ${arg}`);
3524
+ }
3525
+ return { command: "app db tables", artifactId, environment, ...parsed };
3526
+ }
3527
+ function parseDbSchemaArgs(args) {
3528
+ if (args.includes("--help") || args.includes("-h")) {
3529
+ return { command: "help", topic: "app db schema" };
3530
+ }
3531
+ const artifactId = args[0];
3532
+ if (!artifactId || artifactId.startsWith("-")) {
3533
+ throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --table votes --json");
3534
+ }
3535
+ const parsed = parseDbCommonArgs(args, 1);
3536
+ let environment = "preview";
3537
+ let tableName = null;
3538
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3539
+ const arg = parsed.remaining[index];
3540
+ if (arg === "--env") {
3541
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3542
+ index += 1;
3543
+ continue;
3544
+ }
3545
+ if (arg === "--table") {
3546
+ tableName = parseRequiredFlagValue(parsed.remaining[index + 1], "--table");
3547
+ index += 1;
3548
+ continue;
3549
+ }
3550
+ throw new TenderCliUsageError(`Unknown app db schema option: ${arg}`);
3551
+ }
3552
+ if (!tableName) {
3553
+ throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --table votes --json");
3554
+ }
3555
+ return {
3556
+ command: "app db schema",
3557
+ artifactId,
3558
+ environment,
3559
+ tableName,
3560
+ ...parsed,
3561
+ };
3562
+ }
3563
+ function parseDbQueryArgs(args) {
3564
+ if (args.includes("--help") || args.includes("-h")) {
3565
+ return { command: "help", topic: "app db query" };
3566
+ }
3567
+ const artifactId = args[0];
3568
+ if (!artifactId || artifactId.startsWith("-")) {
3569
+ throw new TenderCliUsageError("app id is required. Example: tender app db query <app-id> --sql \"select * from votes limit 20\" --json");
3570
+ }
3571
+ const parsed = parseDbCommonArgs(args, 1);
3572
+ let environment = "preview";
3573
+ let sqlSource = null;
3574
+ const params = [];
3575
+ let limit = null;
3576
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3577
+ const arg = parsed.remaining[index];
3578
+ if (arg === "--env") {
3579
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3580
+ index += 1;
3581
+ continue;
3582
+ }
3583
+ if (arg === "--sql") {
3584
+ if (sqlSource) {
3585
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3586
+ }
3587
+ sqlSource = {
3588
+ kind: "inline",
3589
+ value: parseDbFlagValue(parsed.remaining[index + 1], "--sql"),
3590
+ };
3591
+ index += 1;
3592
+ continue;
3593
+ }
3594
+ if (arg === "--file") {
3595
+ if (sqlSource) {
3596
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3597
+ }
3598
+ sqlSource = {
3599
+ kind: "file",
3600
+ value: parseDbFilePath(parsed.remaining[index + 1]),
3601
+ };
3602
+ index += 1;
3603
+ continue;
3604
+ }
3605
+ if (arg === "--param") {
3606
+ params.push(parseDbParameter(parsed.remaining[index + 1]));
3607
+ index += 1;
3608
+ continue;
3609
+ }
3610
+ if (arg === "--limit") {
3611
+ limit = parsePositiveIntegerFlag(parsed.remaining[index + 1], "--limit");
3612
+ index += 1;
3613
+ continue;
3614
+ }
3615
+ throw new TenderCliUsageError(`Unknown app db query option: ${arg}`);
3616
+ }
3617
+ if (!sqlSource) {
3618
+ throw new TenderCliUsageError("--sql or --file is required. Example: tender app db query artifact_123 --sql \"select * from votes limit 20\" --json");
3619
+ }
3620
+ if (params.length > DB_MAX_BOUND_PARAMETERS) {
3621
+ throw new TenderCliUsageError(`App database queries are limited to ${DB_MAX_BOUND_PARAMETERS} bound parameters.`);
3622
+ }
3623
+ return {
3624
+ command: "app db query",
3625
+ artifactId,
3626
+ environment,
3627
+ sqlSource,
3628
+ params,
3629
+ limit,
3630
+ ...parsed,
3631
+ };
3632
+ }
3302
3633
  function parseTenderArgs(args) {
3303
3634
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
3304
3635
  return { command: "help", topic: "root" };
@@ -3383,21 +3714,21 @@ function parseTenderArgs(args) {
3383
3714
  return parseAnalyticsDashboardsArgs(resourceArgs.slice(3));
3384
3715
  }
3385
3716
  if (resourceArgs[1] === "analytics" &&
3386
- resourceArgs[2] === "property-slots" &&
3717
+ resourceArgs[2] === "properties" &&
3387
3718
  (resourceArgs[3] === undefined ||
3388
3719
  resourceArgs[3] === "--help" ||
3389
3720
  resourceArgs[3] === "-h")) {
3390
- return { command: "help", topic: "app analytics property-slots list" };
3721
+ return { command: "help", topic: "app analytics properties list" };
3391
3722
  }
3392
3723
  if (resourceArgs[1] === "analytics" &&
3393
- resourceArgs[2] === "property-slots" &&
3724
+ resourceArgs[2] === "properties" &&
3394
3725
  resourceArgs[3] === "list") {
3395
- return parseAnalyticsPropertySlotsListArgs(resourceArgs.slice(4));
3726
+ return parseAnalyticsPropertiesListArgs(resourceArgs.slice(4));
3396
3727
  }
3397
3728
  if (resourceArgs[1] === "analytics" &&
3398
- resourceArgs[2] === "property-slots" &&
3729
+ resourceArgs[2] === "properties" &&
3399
3730
  resourceArgs[3] === "activate") {
3400
- return parseAnalyticsPropertySlotsActivateArgs(resourceArgs.slice(4));
3731
+ return parseAnalyticsPropertiesActivateArgs(resourceArgs.slice(4));
3401
3732
  }
3402
3733
  if (resourceArgs[1] === "analytics" &&
3403
3734
  resourceArgs[2] === "export" &&
@@ -3448,6 +3779,27 @@ function parseTenderArgs(args) {
3448
3779
  resourceArgs[3] === "delete") {
3449
3780
  return parseAnalyticsChartsDeleteArgs(resourceArgs.slice(4));
3450
3781
  }
3782
+ if (resourceArgs[1] === "db" &&
3783
+ (resourceArgs[2] === undefined ||
3784
+ resourceArgs[2] === "--help" ||
3785
+ resourceArgs[2] === "-h")) {
3786
+ return { command: "help", topic: "app db" };
3787
+ }
3788
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "capabilities") {
3789
+ return parseDbCapabilitiesArgs(resourceArgs.slice(3));
3790
+ }
3791
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "tables") {
3792
+ return parseDbTablesArgs(resourceArgs.slice(3));
3793
+ }
3794
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "schema") {
3795
+ return parseDbSchemaArgs(resourceArgs.slice(3));
3796
+ }
3797
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "query") {
3798
+ return parseDbQueryArgs(resourceArgs.slice(3));
3799
+ }
3800
+ if (resourceArgs[1] === "db") {
3801
+ throw new TenderCliUsageError(`Unknown app db command: ${resourceArgs[2]}`);
3802
+ }
3451
3803
  if (resourceArgs[1] === "context" &&
3452
3804
  (resourceArgs[2] === undefined ||
3453
3805
  resourceArgs[2] === "--help" ||
@@ -3849,6 +4201,55 @@ async function requestAnalyticsApi(input) {
3849
4201
  }
3850
4202
  return payload;
3851
4203
  }
4204
+ function appendDbEnvironment(params, environment) {
4205
+ params.set("env", environment);
4206
+ }
4207
+ async function requestDbApi(input) {
4208
+ const params = new URLSearchParams();
4209
+ if (input.environment) {
4210
+ appendDbEnvironment(params, input.environment);
4211
+ }
4212
+ if (input.tableName) {
4213
+ params.set("table", input.tableName);
4214
+ }
4215
+ const query = params.size > 0 ? `?${params.toString()}` : "";
4216
+ const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/db/${input.dbPath}${query}`, {
4217
+ method: input.method,
4218
+ headers: {
4219
+ authorization: `Bearer ${input.token}`,
4220
+ accept: "application/json",
4221
+ ...(input.body === undefined ? {} : { "content-type": "application/json" }),
4222
+ },
4223
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
4224
+ });
4225
+ const payload = await parseJsonResponse(response);
4226
+ if (!response.ok || !payload?.ok) {
4227
+ const reasonCode = (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4228
+ `http_${response.status}`;
4229
+ const message = (typeof payload?.message === "string" && payload.message) ||
4230
+ (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4231
+ `App database request failed with HTTP ${response.status}.`;
4232
+ throw new TenderCliDbApiError([
4233
+ message,
4234
+ `Scope: artifact ${input.artifactId}, db/${input.dbPath}.`,
4235
+ `Reason: ${reasonCode}.`,
4236
+ `Example: ${dbCorrectedExample(input.artifactId, input.dbPath)}.`,
4237
+ ].join("\n"), reasonCode);
4238
+ }
4239
+ return payload;
4240
+ }
4241
+ function dbCorrectedExample(artifactId, dbPath) {
4242
+ if (dbPath === "capabilities") {
4243
+ return `tender app db capabilities ${artifactId} --json`;
4244
+ }
4245
+ if (dbPath === "tables") {
4246
+ return `tender app db tables ${artifactId} --json`;
4247
+ }
4248
+ if (dbPath === "schema") {
4249
+ return `tender app db schema ${artifactId} --table <table-name> --json`;
4250
+ }
4251
+ return `tender app db query ${artifactId} --sql "select * from <table-name> limit 20" --json`;
4252
+ }
3852
4253
  function analyticsCorrectedExample(artifactId, analyticsPath) {
3853
4254
  if (analyticsPath === "capabilities") {
3854
4255
  return `tender app analytics capabilities ${artifactId} --include-catalog --range 30d --json`;
@@ -3859,8 +4260,8 @@ function analyticsCorrectedExample(artifactId, analyticsPath) {
3859
4260
  if (analyticsPath === "suggestions") {
3860
4261
  return `tender app analytics suggestions ${artifactId} --range 30d --json`;
3861
4262
  }
3862
- if (analyticsPath === "property-slots") {
3863
- return `tender app analytics property-slots list ${artifactId} --json`;
4263
+ if (analyticsPath === "properties") {
4264
+ return `tender app analytics properties list ${artifactId} --json`;
3864
4265
  }
3865
4266
  if (analyticsPath === "dashboards") {
3866
4267
  return `tender app analytics dashboards ${artifactId} --json`;
@@ -3877,6 +4278,10 @@ function readCliStringField(record, field) {
3877
4278
  const value = record[field];
3878
4279
  return typeof value === "string" ? value : null;
3879
4280
  }
4281
+ function readCliBooleanField(record, field) {
4282
+ const value = record[field];
4283
+ return typeof value === "boolean" ? value : false;
4284
+ }
3880
4285
  function readAnalyticsDashboardRecords(payload) {
3881
4286
  return Array.isArray(payload.dashboards)
3882
4287
  ? payload.dashboards.filter(isCliJsonRecord)
@@ -3912,9 +4317,9 @@ function readAnalyticsChartSpec(chart) {
3912
4317
  function readAnalyticsChartLayout(chart) {
3913
4318
  return isCliJsonRecord(chart.layout) ? chart.layout : {};
3914
4319
  }
3915
- function readAnalyticsPropertySlotRecords(payload) {
3916
- return Array.isArray(payload.propertySlots)
3917
- ? payload.propertySlots.filter(isCliJsonRecord)
4320
+ function readAnalyticsPropertyRecords(payload) {
4321
+ return Array.isArray(payload.properties)
4322
+ ? payload.properties.filter(isCliJsonRecord)
3918
4323
  : [];
3919
4324
  }
3920
4325
  function readAnalyticsSpecEventNameFilter(spec) {
@@ -3940,34 +4345,34 @@ function readAnalyticsSpecGroupField(spec) {
3940
4345
  const [group] = spec.groupBy.filter(isCliJsonRecord);
3941
4346
  return group ? readCliStringField(group, "field") : null;
3942
4347
  }
3943
- function readRequiredAnalyticsPropertySlotsFromSpec(spec) {
4348
+ function readRequiredAnalyticsPropertiesFromSpec(spec) {
3944
4349
  const eventName = readAnalyticsSpecEventNameFilter(spec);
3945
4350
  const requirements = [];
3946
4351
  const groupField = readAnalyticsSpecGroupField(spec);
3947
4352
  if (groupField?.startsWith("property:")) {
3948
4353
  if (!eventName) {
3949
- throw new TenderCliUsageError("Property group-by chart specs require an event_name = filter so the CLI can activate the promoted property slot.");
4354
+ throw new TenderCliUsageError("Property group-by chart specs require an event_name = filter so the CLI can make the property available to dashboard queries.");
3950
4355
  }
3951
4356
  requirements.push({
3952
4357
  eventName,
3953
4358
  propertyKey: groupField.slice("property:".length),
3954
- slotType: "dimension",
4359
+ propertyType: "dimension",
3955
4360
  });
3956
4361
  }
3957
4362
  const metricName = readAnalyticsSpecMetricName(spec);
3958
4363
  if (metricName?.startsWith("sum:") || metricName?.startsWith("avg:")) {
3959
4364
  if (!eventName) {
3960
- throw new TenderCliUsageError("Property metric chart specs require an event_name = filter so the CLI can activate the promoted property slot.");
4365
+ throw new TenderCliUsageError("Property metric chart specs require an event_name = filter so the CLI can make the property available to dashboard queries.");
3961
4366
  }
3962
4367
  requirements.push({
3963
4368
  eventName,
3964
4369
  propertyKey: metricName.slice(metricName.indexOf(":") + 1),
3965
- slotType: "metric",
4370
+ propertyType: "metric",
3966
4371
  });
3967
4372
  }
3968
4373
  const seen = new Set();
3969
4374
  return requirements.filter((requirement) => {
3970
- const key = `${requirement.slotType}:${requirement.eventName}:${requirement.propertyKey}`;
4375
+ const key = `${requirement.propertyType}:${requirement.eventName}:${requirement.propertyKey}`;
3971
4376
  if (seen.has(key)) {
3972
4377
  return false;
3973
4378
  }
@@ -3975,107 +4380,22 @@ function readRequiredAnalyticsPropertySlotsFromSpec(spec) {
3975
4380
  return true;
3976
4381
  });
3977
4382
  }
3978
- function analyticsPropertySlotNames(slotType) {
3979
- return slotType === "dimension"
3980
- ? Array.from({ length: 13 }, (_, index) => `blob${index + 8}`)
3981
- : Array.from({ length: 19 }, (_, index) => `double${index + 2}`);
3982
- }
3983
- function isValidAnalyticsPropertySlotName(input) {
3984
- return analyticsPropertySlotNames(input.slotType).includes(input.slotName);
3985
- }
3986
- function findExistingAnalyticsPropertySlot(input) {
3987
- return (input.propertySlots.find((slot) => readCliStringField(slot, "status") === "active" &&
3988
- readCliStringField(slot, "eventName") === input.requirement.eventName &&
3989
- readCliStringField(slot, "propertyKey") === input.requirement.propertyKey &&
3990
- readCliStringField(slot, "slotType") === input.requirement.slotType) ?? null);
3991
- }
3992
- function chooseAnalyticsPropertySlotName(input) {
3993
- if (input.requestedSlotName) {
3994
- if (!isValidAnalyticsPropertySlotName({
3995
- slotType: input.requirement.slotType,
3996
- slotName: input.requestedSlotName,
3997
- })) {
3998
- throw new TenderCliUsageError(`--slot must be a valid ${input.requirement.slotType} analytics slot.`);
3999
- }
4000
- const conflictingSlot = input.propertySlots.find((slot) => readCliStringField(slot, "status") === "active" &&
4001
- readCliStringField(slot, "eventName") === input.requirement.eventName &&
4002
- readCliStringField(slot, "slotType") === input.requirement.slotType &&
4003
- readCliStringField(slot, "slotName") === input.requestedSlotName &&
4004
- readCliStringField(slot, "propertyKey") !== input.requirement.propertyKey);
4005
- if (conflictingSlot) {
4006
- throw new TenderCliUsageError(`--slot ${input.requestedSlotName} is already active for another ${input.requirement.slotType} property on ${input.requirement.eventName}.`);
4007
- }
4008
- return input.requestedSlotName;
4009
- }
4010
- const usedSlots = new Set(input.propertySlots
4011
- .filter((slot) => readCliStringField(slot, "status") === "active" &&
4012
- readCliStringField(slot, "eventName") === input.requirement.eventName &&
4013
- readCliStringField(slot, "slotType") === input.requirement.slotType)
4014
- .map((slot) => readCliStringField(slot, "slotName"))
4015
- .filter((slotName) => Boolean(slotName)));
4016
- const slotName = analyticsPropertySlotNames(input.requirement.slotType).find((candidate) => !usedSlots.has(candidate));
4017
- if (!slotName) {
4018
- throw new Error("analytics_property_slots_full");
4019
- }
4020
- return slotName;
4021
- }
4022
- async function fetchAnalyticsPropertySlots(input) {
4023
- const payload = await requestAnalyticsApi({
4024
- artifactId: input.command.artifactId,
4025
- analyticsPath: "property-slots",
4026
- method: "GET",
4027
- baseUrl: input.credentials.baseUrl,
4028
- token: input.credentials.token,
4029
- fetcher: input.runtime.fetcher ?? fetch,
4030
- });
4031
- return { payload, propertySlots: readAnalyticsPropertySlotRecords(payload) };
4032
- }
4033
- async function ensureAnalyticsPropertySlots(input) {
4383
+ async function ensureAnalyticsProperties(input) {
4034
4384
  if (input.requirements.length === 0) {
4035
4385
  return [];
4036
4386
  }
4037
- const { propertySlots } = await fetchAnalyticsPropertySlots({
4038
- command: input.command,
4039
- credentials: input.credentials,
4040
- runtime: input.runtime,
4041
- });
4042
4387
  const plan = [];
4043
4388
  for (const requirement of input.requirements) {
4044
- const existing = findExistingAnalyticsPropertySlot({
4045
- propertySlots,
4046
- requirement,
4047
- });
4048
- if (existing) {
4049
- plan.push({
4050
- ...requirement,
4051
- slotName: readCliStringField(existing, "slotName") ?? "",
4052
- label: readCliStringField(existing, "label") ?? requirement.propertyKey,
4053
- status: "existing",
4054
- propertySlot: existing,
4055
- });
4056
- continue;
4057
- }
4058
- const slotName = chooseAnalyticsPropertySlotName({
4059
- propertySlots,
4060
- requirement,
4061
- });
4062
4389
  const body = {
4063
4390
  eventName: requirement.eventName,
4064
4391
  propertyKey: requirement.propertyKey,
4065
- slotType: requirement.slotType,
4066
- slotName,
4392
+ propertyType: requirement.propertyType,
4067
4393
  label: requirement.propertyKey,
4394
+ dryRun: input.dryRun,
4068
4395
  };
4069
- if (input.dryRun) {
4070
- plan.push({
4071
- ...body,
4072
- status: "planned",
4073
- });
4074
- continue;
4075
- }
4076
4396
  const payload = await requestAnalyticsApi({
4077
4397
  artifactId: input.command.artifactId,
4078
- analyticsPath: "property-slots",
4398
+ analyticsPath: "properties",
4079
4399
  method: "POST",
4080
4400
  baseUrl: input.credentials.baseUrl,
4081
4401
  token: input.credentials.token,
@@ -4083,17 +4403,16 @@ async function ensureAnalyticsPropertySlots(input) {
4083
4403
  body,
4084
4404
  });
4085
4405
  plan.push({
4086
- ...body,
4087
- status: "activated",
4088
- propertySlot: payload.propertySlot,
4089
- });
4090
- propertySlots.push({
4091
- eventName: body.eventName,
4092
- propertyKey: body.propertyKey,
4093
- slotType: body.slotType,
4094
- slotName: body.slotName,
4095
- label: body.label,
4096
- status: "active",
4406
+ eventName: requirement.eventName,
4407
+ propertyKey: requirement.propertyKey,
4408
+ propertyType: requirement.propertyType,
4409
+ label: requirement.propertyKey,
4410
+ status: readCliBooleanField(payload, "alreadyActive")
4411
+ ? "existing"
4412
+ : input.dryRun
4413
+ ? "planned"
4414
+ : "activated",
4415
+ property: payload.property,
4097
4416
  });
4098
4417
  }
4099
4418
  return plan;
@@ -4108,12 +4427,12 @@ function analyticsCommandAuthFlags(command) {
4108
4427
  }
4109
4428
  return flags;
4110
4429
  }
4111
- function analyticsPropertySlotActivationCommand(input) {
4430
+ function analyticsPropertyActivationCommand(input) {
4112
4431
  return [
4113
4432
  "tender",
4114
4433
  "app",
4115
4434
  "analytics",
4116
- "property-slots",
4435
+ "properties",
4117
4436
  "activate",
4118
4437
  shellQuote(input.command.artifactId),
4119
4438
  "--event",
@@ -4121,16 +4440,16 @@ function analyticsPropertySlotActivationCommand(input) {
4121
4440
  "--property",
4122
4441
  shellQuote(input.requirement.propertyKey),
4123
4442
  "--type",
4124
- input.requirement.slotType,
4443
+ input.requirement.propertyType,
4125
4444
  ...(input.dryRun ? ["--dry-run"] : []),
4126
4445
  ...analyticsCommandAuthFlags(input.command),
4127
4446
  "--json",
4128
4447
  ].join(" ");
4129
4448
  }
4130
- function createAnalyticsPropertySlotGuidance(input) {
4449
+ function createAnalyticsPropertyGuidance(input) {
4131
4450
  let requirements;
4132
4451
  try {
4133
- requirements = readRequiredAnalyticsPropertySlotsFromSpec(input.spec);
4452
+ requirements = readRequiredAnalyticsPropertiesFromSpec(input.spec);
4134
4453
  }
4135
4454
  catch {
4136
4455
  return null;
@@ -4142,11 +4461,11 @@ function createAnalyticsPropertySlotGuidance(input) {
4142
4461
  "To make this chart available for dashboard queries, activate the custom event property first:",
4143
4462
  ];
4144
4463
  for (const requirement of requirements) {
4145
- lines.push(analyticsPropertySlotActivationCommand({
4464
+ lines.push(analyticsPropertyActivationCommand({
4146
4465
  command: input.command,
4147
4466
  requirement,
4148
4467
  dryRun: true,
4149
- }), analyticsPropertySlotActivationCommand({
4468
+ }), analyticsPropertyActivationCommand({
4150
4469
  command: input.command,
4151
4470
  requirement,
4152
4471
  dryRun: false,
@@ -4172,8 +4491,8 @@ async function requestAnalyticsQueryWithPropertyGuidance(input) {
4172
4491
  }
4173
4492
  catch (error) {
4174
4493
  if (error instanceof TenderCliAnalyticsApiError &&
4175
- error.reasonCode === "analytics_property_slot_not_found") {
4176
- const guidance = createAnalyticsPropertySlotGuidance({
4494
+ error.reasonCode === "analytics_property_not_queryable") {
4495
+ const guidance = createAnalyticsPropertyGuidance({
4177
4496
  command: input.command,
4178
4497
  spec: input.spec,
4179
4498
  });
@@ -6216,6 +6535,131 @@ async function resolveAnalyticsCredentials(command, runtime) {
6216
6535
  env: runtime.env ?? process.env,
6217
6536
  });
6218
6537
  }
6538
+ async function resolveDbCredentials(command, runtime) {
6539
+ return await resolveApiCredentials({
6540
+ baseUrl: command.baseUrl,
6541
+ flagToken: command.flagToken,
6542
+ profileName: command.profileName,
6543
+ env: runtime.env ?? process.env,
6544
+ });
6545
+ }
6546
+ function validateDbSql(sql) {
6547
+ if (!sql.trim()) {
6548
+ throw new TenderCliUsageError("SQL statement is required.");
6549
+ }
6550
+ const byteLength = new TextEncoder().encode(sql).byteLength;
6551
+ if (byteLength > DB_SQL_STATEMENT_MAX_BYTES) {
6552
+ throw new TenderCliUsageError(`SQL statement is limited to ${DB_SQL_STATEMENT_MAX_BYTES} bytes.`);
6553
+ }
6554
+ }
6555
+ async function readDbSql(command, runtime) {
6556
+ const sql = command.sqlSource.kind === "inline"
6557
+ ? command.sqlSource.value
6558
+ : command.sqlSource.value === "-"
6559
+ ? runtime.stdin
6560
+ : await readFile(path.resolve(command.sqlSource.value), "utf8");
6561
+ if (!sql) {
6562
+ throw new TenderCliUsageError("--file - requires SQL on stdin.");
6563
+ }
6564
+ validateDbSql(sql);
6565
+ return sql;
6566
+ }
6567
+ function printDbPayload(command, io, payload, fallbackLines) {
6568
+ if (command.json) {
6569
+ io.stdout(JSON.stringify(payload, null, 2));
6570
+ }
6571
+ else {
6572
+ io.stdout(fallbackLines.join("\n"));
6573
+ }
6574
+ }
6575
+ async function runDbCapabilities(command, io, runtime) {
6576
+ const credentials = await resolveDbCredentials(command, runtime);
6577
+ const payload = await requestDbApi({
6578
+ artifactId: command.artifactId,
6579
+ dbPath: "capabilities",
6580
+ method: "GET",
6581
+ baseUrl: credentials.baseUrl,
6582
+ token: credentials.token,
6583
+ fetcher: runtime.fetcher ?? fetch,
6584
+ });
6585
+ const limits = isCliJsonRecord(payload.limits) ? payload.limits : {};
6586
+ const runtimeInfo = isCliJsonRecord(payload.runtime) ? payload.runtime : {};
6587
+ printDbPayload(command, io, payload, [
6588
+ `app_id: ${command.artifactId}`,
6589
+ `backend: ${String(runtimeInfo.backend ?? "app-database")}`,
6590
+ `default_limit: ${String(limits.defaultLimit ?? "")}`,
6591
+ `max_limit: ${String(limits.maxInteractiveLimit ?? "")}`,
6592
+ ]);
6593
+ return 0;
6594
+ }
6595
+ async function runDbTables(command, io, runtime) {
6596
+ const credentials = await resolveDbCredentials(command, runtime);
6597
+ const payload = await requestDbApi({
6598
+ artifactId: command.artifactId,
6599
+ dbPath: "tables",
6600
+ method: "GET",
6601
+ baseUrl: credentials.baseUrl,
6602
+ token: credentials.token,
6603
+ fetcher: runtime.fetcher ?? fetch,
6604
+ environment: command.environment,
6605
+ });
6606
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6607
+ printDbPayload(command, io, payload, [
6608
+ `app_id: ${command.artifactId}`,
6609
+ `environment: ${command.environment}`,
6610
+ `tables: ${rows.length}`,
6611
+ ]);
6612
+ return 0;
6613
+ }
6614
+ async function runDbSchema(command, io, runtime) {
6615
+ const credentials = await resolveDbCredentials(command, runtime);
6616
+ const payload = await requestDbApi({
6617
+ artifactId: command.artifactId,
6618
+ dbPath: "schema",
6619
+ method: "GET",
6620
+ baseUrl: credentials.baseUrl,
6621
+ token: credentials.token,
6622
+ fetcher: runtime.fetcher ?? fetch,
6623
+ environment: command.environment,
6624
+ tableName: command.tableName,
6625
+ });
6626
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6627
+ printDbPayload(command, io, payload, [
6628
+ `app_id: ${command.artifactId}`,
6629
+ `environment: ${command.environment}`,
6630
+ `table: ${command.tableName}`,
6631
+ `schema_rows: ${rows.length}`,
6632
+ ]);
6633
+ return 0;
6634
+ }
6635
+ async function runDbQuery(command, io, runtime) {
6636
+ const credentials = await resolveDbCredentials(command, runtime);
6637
+ const sql = await readDbSql(command, runtime);
6638
+ const body = {
6639
+ environment: command.environment,
6640
+ mode: "read",
6641
+ sql,
6642
+ params: command.params,
6643
+ ...(command.limit === null ? {} : { limit: command.limit }),
6644
+ };
6645
+ const payload = await requestDbApi({
6646
+ artifactId: command.artifactId,
6647
+ dbPath: "query",
6648
+ method: "POST",
6649
+ baseUrl: credentials.baseUrl,
6650
+ token: credentials.token,
6651
+ fetcher: runtime.fetcher ?? fetch,
6652
+ body,
6653
+ });
6654
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6655
+ printDbPayload(command, io, payload, [
6656
+ `app_id: ${command.artifactId}`,
6657
+ `environment: ${command.environment}`,
6658
+ `rows: ${rows.length}`,
6659
+ `truncated: ${String(payload.truncated ?? false)}`,
6660
+ ]);
6661
+ return 0;
6662
+ }
6219
6663
  async function runAnalyticsSummary(command, io, runtime) {
6220
6664
  const credentials = await resolveAnalyticsCredentials(command, runtime);
6221
6665
  const payload = await requestAnalyticsApi({
@@ -6380,93 +6824,43 @@ async function runAnalyticsExportQuery(command, io, runtime) {
6380
6824
  io.stdout(JSON.stringify(payload, null, 2));
6381
6825
  return 0;
6382
6826
  }
6383
- async function runAnalyticsPropertySlotsList(command, io, runtime) {
6827
+ async function runAnalyticsPropertiesList(command, io, runtime) {
6384
6828
  const credentials = await resolveAnalyticsCredentials(command, runtime);
6385
6829
  const payload = await requestAnalyticsApi({
6386
6830
  artifactId: command.artifactId,
6387
- analyticsPath: "property-slots",
6831
+ analyticsPath: "properties",
6388
6832
  method: "GET",
6389
6833
  baseUrl: credentials.baseUrl,
6390
6834
  token: credentials.token,
6391
6835
  fetcher: runtime.fetcher ?? fetch,
6392
6836
  });
6393
- const propertySlots = readAnalyticsPropertySlotRecords(payload).filter((slot) => !command.eventName ||
6394
- readCliStringField(slot, "eventName") === command.eventName);
6837
+ const properties = readAnalyticsPropertyRecords(payload).filter((property) => !command.eventName ||
6838
+ readCliStringField(property, "eventName") === command.eventName);
6395
6839
  printAnalyticsPayload(command, io, {
6396
6840
  ok: true,
6397
6841
  command: command.command,
6398
6842
  artifactId: command.artifactId,
6399
6843
  eventName: command.eventName,
6400
- propertySlots,
6844
+ properties,
6401
6845
  }, [
6402
6846
  `app_id: ${command.artifactId}`,
6403
6847
  command.eventName ? `event: ${command.eventName}` : "event: all",
6404
- `property_slots: ${propertySlots.length}`,
6848
+ `properties: ${properties.length}`,
6405
6849
  ]);
6406
6850
  return 0;
6407
6851
  }
6408
- async function runAnalyticsPropertySlotsActivate(command, io, runtime) {
6852
+ async function runAnalyticsPropertiesActivate(command, io, runtime) {
6409
6853
  const credentials = await resolveAnalyticsCredentials(command, runtime);
6410
- const requirement = {
6411
- eventName: command.eventName,
6412
- propertyKey: command.propertyKey,
6413
- slotType: command.slotType,
6414
- };
6415
- const { propertySlots } = await fetchAnalyticsPropertySlots({
6416
- command,
6417
- credentials,
6418
- runtime,
6419
- });
6420
- const existing = findExistingAnalyticsPropertySlot({
6421
- propertySlots,
6422
- requirement,
6423
- });
6424
- if (existing) {
6425
- const payload = {
6426
- ok: true,
6427
- command: command.command,
6428
- artifactId: command.artifactId,
6429
- alreadyActive: true,
6430
- propertySlot: existing,
6431
- };
6432
- printAnalyticsPayload(command, io, payload, [
6433
- `app_id: ${command.artifactId}`,
6434
- "already_active: true",
6435
- `slot_name: ${readCliStringField(existing, "slotName") ?? ""}`,
6436
- ]);
6437
- return 0;
6438
- }
6439
- const slotName = chooseAnalyticsPropertySlotName({
6440
- propertySlots,
6441
- requirement,
6442
- requestedSlotName: command.slotName,
6443
- });
6444
6854
  const request = {
6445
6855
  eventName: command.eventName,
6446
6856
  propertyKey: command.propertyKey,
6447
- slotType: command.slotType,
6448
- slotName,
6857
+ propertyType: command.propertyType,
6449
6858
  label: command.label ?? command.propertyKey,
6859
+ dryRun: command.dryRun,
6450
6860
  };
6451
- if (command.dryRun) {
6452
- printAnalyticsPayload(command, io, {
6453
- ok: true,
6454
- command: command.command,
6455
- dryRun: true,
6456
- artifactId: command.artifactId,
6457
- request,
6458
- }, [
6459
- `app_id: ${command.artifactId}`,
6460
- `event: ${command.eventName}`,
6461
- `property: ${command.propertyKey}`,
6462
- `slot_name: ${slotName}`,
6463
- "dry_run: true",
6464
- ]);
6465
- return 0;
6466
- }
6467
6861
  const payload = await requestAnalyticsApi({
6468
6862
  artifactId: command.artifactId,
6469
- analyticsPath: "property-slots",
6863
+ analyticsPath: "properties",
6470
6864
  method: "POST",
6471
6865
  baseUrl: credentials.baseUrl,
6472
6866
  token: credentials.token,
@@ -6477,7 +6871,8 @@ async function runAnalyticsPropertySlotsActivate(command, io, runtime) {
6477
6871
  `app_id: ${command.artifactId}`,
6478
6872
  `event: ${command.eventName}`,
6479
6873
  `property: ${command.propertyKey}`,
6480
- `slot_name: ${slotName}`,
6874
+ `type: ${command.propertyType}`,
6875
+ ...(command.dryRun ? ["dry_run: true"] : []),
6481
6876
  ]);
6482
6877
  return 0;
6483
6878
  }
@@ -6492,16 +6887,16 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
6492
6887
  spec,
6493
6888
  layout: {},
6494
6889
  };
6495
- const requiredPropertySlots = readRequiredAnalyticsPropertySlotsFromSpec(spec);
6496
- const credentials = !command.dryRun || requiredPropertySlots.length > 0
6890
+ const requiredProperties = readRequiredAnalyticsPropertiesFromSpec(spec);
6891
+ const credentials = !command.dryRun || requiredProperties.length > 0
6497
6892
  ? await resolveAnalyticsCredentials(command, runtime)
6498
6893
  : null;
6499
- const propertySlots = credentials
6500
- ? await ensureAnalyticsPropertySlots({
6894
+ const properties = credentials
6895
+ ? await ensureAnalyticsProperties({
6501
6896
  command,
6502
6897
  credentials,
6503
6898
  runtime,
6504
- requirements: requiredPropertySlots,
6899
+ requirements: requiredProperties,
6505
6900
  dryRun: command.dryRun,
6506
6901
  })
6507
6902
  : [];
@@ -6510,7 +6905,7 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
6510
6905
  ok: true,
6511
6906
  dryRun: true,
6512
6907
  artifactId: command.artifactId,
6513
- propertySlots,
6908
+ properties,
6514
6909
  request: body,
6515
6910
  };
6516
6911
  printAnalyticsPayload(command, io, payload, [
@@ -6533,10 +6928,10 @@ async function runAnalyticsChartsCreate(command, io, runtime) {
6533
6928
  fetcher: runtime.fetcher ?? fetch,
6534
6929
  body,
6535
6930
  });
6536
- printAnalyticsPayload(command, io, { ...payload, propertySlots }, [
6931
+ printAnalyticsPayload(command, io, { ...payload, properties }, [
6537
6932
  `app_id: ${command.artifactId}`,
6538
6933
  `chart_id: ${String(payload.chart?.id ?? "")}`,
6539
- `property_slots: ${propertySlots.length}`,
6934
+ `properties: ${properties.length}`,
6540
6935
  ]);
6541
6936
  return 0;
6542
6937
  }
@@ -6583,11 +6978,11 @@ async function runAnalyticsChartsUpdate(command, io, runtime) {
6583
6978
  spec,
6584
6979
  layout: readAnalyticsChartLayout(existing.chart),
6585
6980
  };
6586
- const propertySlots = await ensureAnalyticsPropertySlots({
6981
+ const properties = await ensureAnalyticsProperties({
6587
6982
  command,
6588
6983
  credentials,
6589
6984
  runtime,
6590
- requirements: readRequiredAnalyticsPropertySlotsFromSpec(spec),
6985
+ requirements: readRequiredAnalyticsPropertiesFromSpec(spec),
6591
6986
  dryRun: command.dryRun,
6592
6987
  });
6593
6988
  const dashboardId = readCliStringField(existing.dashboard, "id");
@@ -6599,7 +6994,7 @@ async function runAnalyticsChartsUpdate(command, io, runtime) {
6599
6994
  chartId: command.chartId,
6600
6995
  dashboardId,
6601
6996
  currentChart: existing.chart,
6602
- propertySlots,
6997
+ properties,
6603
6998
  request: body,
6604
6999
  }, [
6605
7000
  `app_id: ${command.artifactId}`,
@@ -6619,11 +7014,11 @@ async function runAnalyticsChartsUpdate(command, io, runtime) {
6619
7014
  fetcher: runtime.fetcher ?? fetch,
6620
7015
  body,
6621
7016
  });
6622
- printAnalyticsPayload(command, io, { ...payload, propertySlots }, [
7017
+ printAnalyticsPayload(command, io, { ...payload, properties }, [
6623
7018
  `app_id: ${command.artifactId}`,
6624
7019
  `chart_id: ${command.chartId}`,
6625
7020
  `title: ${String(payload.chart?.title ?? title)}`,
6626
- `property_slots: ${propertySlots.length}`,
7021
+ `properties: ${properties.length}`,
6627
7022
  ]);
6628
7023
  return 0;
6629
7024
  }
@@ -6792,11 +7187,26 @@ export async function runTenderCli(args, io = {
6792
7187
  else if (command.topic === "app analytics charts delete") {
6793
7188
  io.stdout(analyticsChartsDeleteHelp());
6794
7189
  }
6795
- else if (command.topic === "app analytics property-slots list") {
6796
- io.stdout(analyticsPropertySlotsListHelp());
7190
+ else if (command.topic === "app analytics properties list") {
7191
+ io.stdout(analyticsPropertiesListHelp());
6797
7192
  }
6798
- else if (command.topic === "app analytics property-slots activate") {
6799
- io.stdout(analyticsPropertySlotsActivateHelp());
7193
+ else if (command.topic === "app analytics properties activate") {
7194
+ io.stdout(analyticsPropertiesActivateHelp());
7195
+ }
7196
+ else if (command.topic === "app db") {
7197
+ io.stdout(dbHelp());
7198
+ }
7199
+ else if (command.topic === "app db capabilities") {
7200
+ io.stdout(dbCapabilitiesHelp());
7201
+ }
7202
+ else if (command.topic === "app db tables") {
7203
+ io.stdout(dbTablesHelp());
7204
+ }
7205
+ else if (command.topic === "app db schema") {
7206
+ io.stdout(dbSchemaHelp());
7207
+ }
7208
+ else if (command.topic === "app db query") {
7209
+ io.stdout(dbQueryHelp());
6800
7210
  }
6801
7211
  else if (command.topic === "app") {
6802
7212
  io.stdout(appHelp());
@@ -6908,11 +7318,23 @@ export async function runTenderCli(args, io = {
6908
7318
  if (command.command === "app analytics charts delete") {
6909
7319
  return await runAnalyticsChartsDelete(command, io, runtime);
6910
7320
  }
6911
- if (command.command === "app analytics property-slots list") {
6912
- return await runAnalyticsPropertySlotsList(command, io, runtime);
7321
+ if (command.command === "app analytics properties list") {
7322
+ return await runAnalyticsPropertiesList(command, io, runtime);
6913
7323
  }
6914
- if (command.command === "app analytics property-slots activate") {
6915
- return await runAnalyticsPropertySlotsActivate(command, io, runtime);
7324
+ if (command.command === "app analytics properties activate") {
7325
+ return await runAnalyticsPropertiesActivate(command, io, runtime);
7326
+ }
7327
+ if (command.command === "app db capabilities") {
7328
+ return await runDbCapabilities(command, io, runtime);
7329
+ }
7330
+ if (command.command === "app db tables") {
7331
+ return await runDbTables(command, io, runtime);
7332
+ }
7333
+ if (command.command === "app db schema") {
7334
+ return await runDbSchema(command, io, runtime);
7335
+ }
7336
+ if (command.command === "app db query") {
7337
+ return await runDbQuery(command, io, runtime);
6916
7338
  }
6917
7339
  if (command.command === "app git remote") {
6918
7340
  return await runGitRemote(command, io, runtime);
@@ -6926,10 +7348,16 @@ export async function runTenderCli(args, io = {
6926
7348
  }
6927
7349
  }
6928
7350
  const currentFile = fileURLToPath(import.meta.url);
7351
+ function hasFlagValue(args, flag, value) {
7352
+ return args.some((arg, index) => arg === flag && args[index + 1] === value);
7353
+ }
6929
7354
  function shouldReadEntrypointStdin(args) {
6930
7355
  return (args[0] === "app" &&
6931
7356
  ((args[1] === "git" && args[2] === "credential") ||
6932
- (args[1] === "analytics" && args.includes("--spec") && args.includes("-"))));
7357
+ (args[1] === "analytics" && hasFlagValue(args, "--spec", "-")) ||
7358
+ (args[1] === "db" &&
7359
+ args[2] === "query" &&
7360
+ hasFlagValue(args, "--file", "-"))));
6933
7361
  }
6934
7362
  async function readEntrypointStdin() {
6935
7363
  if (process.stdin.isTTY) {