@tenderprompt/cli 0.1.12 → 0.1.14

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 +66 -0
  2. package/dist/index.js +804 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,6 +34,8 @@ session:
34
34
  - build and preview a commit through the Tender API
35
35
  - publish only after a dry run or explicit `--confirm publish`
36
36
  - inspect artifact-scoped analytics without SQL or platform credentials
37
+ - inspect artifact-scoped app database tables, schema, and bounded read-only
38
+ query output
37
39
  - run server-validated chart specs and export aggregate rows
38
40
  - generate starter analytics suggestions
39
41
  - create, list, update, and delete saved analytics dashboards and charts, with
@@ -87,6 +89,8 @@ TTL values are intentionally bounded:
87
89
 
88
90
  Use `--profile publish` only when the token should be allowed to publish.
89
91
  Publish still requires explicit confirmation with `--confirm publish`.
92
+ Use `--profile db-read --artifact <app-id>` for read-only app database
93
+ inspection.
90
94
 
91
95
  Credential precedence is explicit-first:
92
96
 
@@ -128,6 +132,11 @@ tender app validate artifact_123 --json
128
132
  tender app build artifact_123 --commit "$(git rev-parse HEAD)" --json
129
133
  tender app preview artifact_123 --commit "$(git rev-parse HEAD)" --stream --json
130
134
  tender app publish artifact_123 --dry-run --json
135
+ tender app db capabilities artifact_123 --json
136
+ tender app db tables artifact_123 --env published --json
137
+ tender app db schema artifact_123 --env published --table votes --json
138
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
139
+ tender app db query artifact_123 --env published --sql "select * from votes order by created_at" --limit 50 --offset 50 --json
131
140
  ```
132
141
 
133
142
  ## Analytics
@@ -226,6 +235,61 @@ filter. `analytics query` is read-only, so use
226
235
  query fails for a missing dashboard-query property, the CLI prints the exact
227
236
  activation commands to run and does not perform that write automatically.
228
237
 
238
+ Event payload properties can be tracked by the app, observed in bounded catalog
239
+ samples, and still not be queryable in fast dashboard charts. Register intended
240
+ dashboard dimensions and metrics before meaningful traffic starts. Use
241
+ dimensions for grouping values such as variant, plan, step, source, or outcome,
242
+ and metrics for numeric values such as score, amount, duration, or quantity.
243
+ Making a property queryable does not require an app redeploy, but older
244
+ dashboard-query rows may not fully populate grouped charts until new traffic
245
+ arrives. Do not make every property queryable; avoid PII, secrets, free text,
246
+ ids, URLs, or high-cardinality values unless the chart has a clear purpose.
247
+
248
+ ## App Database
249
+
250
+ App database commands are artifact-scoped and go through Tender's server-side
251
+ authorization and read-only query checks. The CLI only accepts an app id; it
252
+ does not accept tenant ids, runner names, provider account ids, or runtime
253
+ storage identifiers. Commands that read data require an explicit `--env preview`
254
+ or `--env published`; there is no default environment.
255
+
256
+ Start with capabilities:
257
+
258
+ ```bash
259
+ tender auth login --device --profile db-read --artifact artifact_123 --ttl 7d --save-profile db --json
260
+
261
+ tender app db capabilities artifact_123 --profile db --json
262
+ ```
263
+
264
+ Common read-only commands:
265
+
266
+ ```bash
267
+ tender app db tables artifact_123 --env published --profile db --json
268
+
269
+ tender app db schema artifact_123 --env published --table votes --profile db --json
270
+
271
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --profile db --json
272
+
273
+ tender app db query artifact_123 \
274
+ --env published \
275
+ --sql "select * from votes where choice = ?" \
276
+ --param coffee \
277
+ --profile db \
278
+ --json
279
+
280
+ tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --profile db --json
281
+
282
+ tender app db query artifact_123 --env published --file - --offset 100 --profile db --json < debug.sql
283
+ ```
284
+
285
+ The query command sends one read-only statement with positional bindings. Query
286
+ responses include `requestedLimit`, `appliedLimit`, `maxInteractiveLimit`,
287
+ `offset`, and `nextOffset`; agents must request each page explicitly and should
288
+ not auto-drain large tables. The interactive row cap is 50 rows per request.
289
+ Client and server limits include a 100 KB statement cap and at most 100 bound
290
+ parameters. The server enforces the final policy, row limits, response limits,
291
+ artifact scope, and audit logging.
292
+
229
293
  ## Safety Model
230
294
 
231
295
  - Local agents run in the user's environment, not in Tender.
@@ -242,6 +306,8 @@ activation commands to run and does not perform that write automatically.
242
306
  tenant/artifact-scoped.
243
307
  - Raw analytics event export is intentionally not available; exports are bounded
244
308
  catalog metadata or validated aggregate query rows.
309
+ - App database commands require `db:read`, should use artifact-scoped tokens,
310
+ and never require provider account ids or runtime storage identifiers.
245
311
 
246
312
  ## Related Package
247
313
 
package/dist/index.js CHANGED
@@ -41,16 +41,34 @@ class TenderCliUsageError extends Error {
41
41
  }
42
42
  class TenderCliAnalyticsApiError extends Error {
43
43
  reasonCode;
44
- constructor(message, reasonCode) {
44
+ scope;
45
+ example;
46
+ constructor(message, reasonCode, scope = null, example = null) {
45
47
  super(message);
46
- this.reasonCode = reasonCode;
47
48
  this.name = "TenderCliAnalyticsApiError";
49
+ this.reasonCode = reasonCode;
50
+ this.scope = scope;
51
+ this.example = example;
52
+ }
53
+ }
54
+ class TenderCliDbApiError extends Error {
55
+ reasonCode;
56
+ scope;
57
+ example;
58
+ constructor(message, reasonCode, scope, example) {
59
+ super(message);
60
+ this.name = "TenderCliDbApiError";
61
+ this.reasonCode = reasonCode;
62
+ this.scope = scope;
63
+ this.example = example;
48
64
  }
49
65
  }
50
66
  const DEFAULT_PROFILE_NAME = "default";
51
67
  const DEFAULT_BASE_URL = "https://app.tenderprompt.com";
52
68
  const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
53
69
  const ARTIFACT_SOURCE_EXPORT_MAX_FILE_BYTES = 2 * 1024 * 1024;
70
+ const DB_SQL_STATEMENT_MAX_BYTES = 100 * 1024;
71
+ const DB_MAX_BOUND_PARAMETERS = 100;
54
72
  function rootHelp() {
55
73
  return `Usage: tender <command> [options]
56
74
 
@@ -89,6 +107,10 @@ Commands:
89
107
  Run a server-validated chart spec
90
108
  tender app analytics suggestions <app-id>
91
109
  Generate useful starter chart suggestions
110
+ tender app db capabilities <app-id>
111
+ Describe app database query limits and policy
112
+ tender app db query <app-id> --env <preview|published> --sql <sql>
113
+ Run one read-only app database query
92
114
  tender app context fetch <app-id>
93
115
  Fetch managed agent context into a checkout
94
116
  tender app context status <app-id>
@@ -120,6 +142,10 @@ Examples:
120
142
  tender app analytics summary artifact_123 --range 30d --json
121
143
  tender app analytics query artifact_123 --spec chart.json --json
122
144
  tender app analytics suggestions artifact_123 --range 30d --json
145
+ tender app db capabilities artifact_123 --json
146
+ tender app db tables artifact_123 --env published --json
147
+ tender app db schema artifact_123 --env published --table votes --json
148
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
123
149
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
124
150
  tender app context status artifact_123 --dir ./widget --json
125
151
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -142,12 +168,12 @@ Examples:
142
168
  tender auth status --json`;
143
169
  }
144
170
  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]
171
+ 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
172
 
147
173
  Options:
148
174
  --device Use the OAuth device-code flow.
149
175
  --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.
176
+ --profile <value> Token permission profile: edit-preview, publish, create, or db-read. Defaults to edit-preview.
151
177
  --artifact <artifact-id> Scope the token to one artifact. Required for normal edit-preview agent work.
152
178
  --ttl <value> Token lifetime: 7d, 14d, or 30d. Defaults to 7d.
153
179
  --save-profile <name> Local config profile name. Defaults to default.
@@ -157,7 +183,8 @@ Options:
157
183
  Examples:
158
184
  tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
159
185
  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`;
186
+ tender auth login --device --profile create --ttl 7d --save-profile account
187
+ tender auth login --device --profile db-read --artifact artifact_123 --ttl 7d --save-profile db`;
161
188
  }
162
189
  function authStatusHelp() {
163
190
  return `Usage: tender auth status [--profile <name>] [--json]
@@ -217,6 +244,14 @@ Commands:
217
244
  List properties available to dashboard queries
218
245
  tender app analytics properties activate <app-id>
219
246
  Make a custom event property queryable
247
+ tender app db capabilities <app-id>
248
+ Describe app database query limits and policy
249
+ tender app db tables <app-id> --env <preview|published>
250
+ List app database tables and views
251
+ tender app db schema <app-id> --env <preview|published> --table <name>
252
+ Show table and index schema for one table
253
+ tender app db query <app-id> --env <preview|published> --sql <sql>
254
+ Run one read-only app database query
220
255
  tender app context fetch <app-id>
221
256
  Fetch AGENTS.md, skills, and Tender App scaffold files
222
257
  tender app context status <app-id>
@@ -257,6 +292,10 @@ Examples:
257
292
  tender app analytics charts delete artifact_123 --chart analytics_chart_123 --dry-run --json
258
293
  tender app analytics properties list artifact_123 --json
259
294
  tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
295
+ tender app db capabilities artifact_123 --json
296
+ tender app db tables artifact_123 --env published --json
297
+ tender app db schema artifact_123 --env published --table votes --json
298
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
260
299
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
261
300
  tender app context status artifact_123 --dir ./widget --json
262
301
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -435,6 +474,62 @@ Examples:
435
474
  tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
436
475
  tender app analytics properties activate artifact_123 --event score_saved --property score --type metric --json`;
437
476
  }
477
+ function dbHelp() {
478
+ return `Usage: tender app db <command> <app-id> [options]
479
+
480
+ Commands:
481
+ tender app db capabilities <app-id> Describe app database policy and limits
482
+ tender app db tables <app-id> --env <preview|published>
483
+ List app database tables and views
484
+ tender app db schema <app-id> --env <preview|published>
485
+ Show schema for one table
486
+ tender app db query <app-id> --env <preview|published>
487
+ Run one read-only SQL query
488
+
489
+ Examples:
490
+ tender app db capabilities artifact_123 --json
491
+ tender app db tables artifact_123 --env published --json
492
+ tender app db schema artifact_123 --env published --table votes --json
493
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
494
+ tender app db query artifact_123 --env published --file ./debug.sql --param active --limit 50 --json`;
495
+ }
496
+ function dbCapabilitiesHelp() {
497
+ return `Usage: tender app db capabilities <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
498
+
499
+ Examples:
500
+ tender app db capabilities artifact_123 --json`;
501
+ }
502
+ function dbTablesHelp() {
503
+ return `Usage: tender app db tables <app-id> --env <preview|published> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
504
+
505
+ Examples:
506
+ tender app db tables artifact_123 --env published --json
507
+ tender app db tables artifact_123 --env preview --json`;
508
+ }
509
+ function dbSchemaHelp() {
510
+ return `Usage: tender app db schema <app-id> --env <preview|published> --table <name> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
511
+
512
+ Examples:
513
+ tender app db schema artifact_123 --env published --table votes --json
514
+ tender app db schema artifact_123 --table preference_votes --env preview --json`;
515
+ }
516
+ function dbQueryHelp() {
517
+ return `Usage: tender app db query <app-id> --env <preview|published> (--sql <sql> | --file <path|->) [--param <value>] [--limit <n>] [--offset <n>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
518
+
519
+ Options:
520
+ --env <value> Target app environment: preview or published. Required.
521
+ --sql <sql> SQL statement. Must be one read-only statement.
522
+ --file <path|-> Read SQL from a file or stdin with -.
523
+ --param <value> Bind one positional parameter. Repeat for multiple params. Values parse as numbers or null; other values are passed as strings.
524
+ --limit <n> Interactive row limit. Server caps the maximum at 50.
525
+ --offset <n> Skip rows before returning results. Server caps the maximum offset.
526
+
527
+ Examples:
528
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --json
529
+ tender app db query artifact_123 --env published --sql "select * from votes where choice = ?" --param coffee --json
530
+ tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --json
531
+ tender app db query artifact_123 --env published --file - --json < debug.sql`;
532
+ }
438
533
  function artifactsListHelp() {
439
534
  return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
440
535
 
@@ -767,6 +862,7 @@ function tenderCliCapabilities() {
767
862
  "tender auth --help",
768
863
  "tender app --help",
769
864
  "tender app analytics --help",
865
+ "tender app db --help",
770
866
  "tender app bindings outbound --help",
771
867
  ],
772
868
  },
@@ -832,6 +928,23 @@ function tenderCliCapabilities() {
832
928
  "Use list before writes so agents can avoid duplicates and explain the exact chart they are changing.",
833
929
  ],
834
930
  },
931
+ {
932
+ name: "app_database_inspection",
933
+ when: "When inspecting app-local database tables from a scoped token.",
934
+ commands: [
935
+ "tender auth login --device --profile db-read --artifact <artifact-id> --ttl 7d --save-profile db --json",
936
+ "tender app db capabilities <artifact-id> --profile db --json",
937
+ "tender app db tables <artifact-id> --env published --profile db --json",
938
+ "tender app db schema <artifact-id> --env published --table <table-name> --profile db --json",
939
+ "tender app db query <artifact-id> --env published --sql \"select * from <table-name> limit 20\" --profile db --json",
940
+ "tender app db query <artifact-id> --env published --file ./debug.sql --profile db --json",
941
+ ],
942
+ notes: [
943
+ "The CLI only accepts artifact ids. It never accepts tenant ids, runner names, provider account ids, or runtime storage identifiers.",
944
+ "Read-only policy is enforced by the server before the runtime shim executes SQL.",
945
+ "Use --file - to pipe SQL from stdin without prompts.",
946
+ ],
947
+ },
835
948
  {
836
949
  name: "outbound_http_binding",
837
950
  when: "When app code needs external APIs, OAuth/token exchange, webhooks, provider SDKs, or secrets.",
@@ -925,6 +1038,30 @@ function tenderCliCapabilities() {
925
1038
  writes: true,
926
1039
  dryRun: true,
927
1040
  },
1041
+ {
1042
+ command: "tender app db capabilities <artifact-id> --json",
1043
+ purpose: "Inspect app database policy, limits, and supported capabilities for one artifact.",
1044
+ requiresAuth: true,
1045
+ writes: false,
1046
+ },
1047
+ {
1048
+ command: "tender app db tables <artifact-id> --env published --json",
1049
+ purpose: "List app database tables and views for one artifact.",
1050
+ requiresAuth: true,
1051
+ writes: false,
1052
+ },
1053
+ {
1054
+ command: "tender app db schema <artifact-id> --env published --table <table-name> --json",
1055
+ purpose: "Inspect table, index, and trigger schema for one app database table.",
1056
+ requiresAuth: true,
1057
+ writes: false,
1058
+ },
1059
+ {
1060
+ command: "tender app db query <artifact-id> --env published --sql \"select * from <table-name> limit 20\" --json",
1061
+ purpose: "Run one bounded read-only app database query.",
1062
+ requiresAuth: true,
1063
+ writes: false,
1064
+ },
928
1065
  ],
929
1066
  security: [
930
1067
  "Prefer artifact-scoped device tokens.",
@@ -932,6 +1069,7 @@ function tenderCliCapabilities() {
932
1069
  "Do not call Tender HTTP APIs directly from agent workflows.",
933
1070
  "Do not put third-party fetch calls in src/server.ts; use outbound bindings.",
934
1071
  "Do not request direct query credentials or raw SQL for analytics.",
1072
+ "Do not use tenant ids, runner names, provider account ids, or runtime storage identifiers for app database inspection.",
935
1073
  ],
936
1074
  };
937
1075
  }
@@ -2264,10 +2402,11 @@ function parseContextRefreshArgs(args) {
2264
2402
  function parseTokenProfile(value) {
2265
2403
  if (value === "edit-preview" ||
2266
2404
  value === "publish" ||
2267
- value === "create") {
2405
+ value === "create" ||
2406
+ value === "db-read") {
2268
2407
  return value;
2269
2408
  }
2270
- throw new TenderCliUsageError("--profile must be one of edit-preview, publish, or create.");
2409
+ throw new TenderCliUsageError("--profile must be one of edit-preview, publish, create, or db-read.");
2271
2410
  }
2272
2411
  function parsePositiveIntegerFlag(value, flag) {
2273
2412
  const parsed = Number(value);
@@ -2276,6 +2415,13 @@ function parsePositiveIntegerFlag(value, flag) {
2276
2415
  }
2277
2416
  return parsed;
2278
2417
  }
2418
+ function parseNonNegativeIntegerFlag(value, flag) {
2419
+ const parsed = Number(value);
2420
+ if (!Number.isInteger(parsed) || parsed < 0) {
2421
+ throw new TenderCliUsageError(`${flag} requires a non-negative integer.`);
2422
+ }
2423
+ return parsed;
2424
+ }
2279
2425
  function parseAuthLoginArgs(args) {
2280
2426
  if (args.includes("--help") || args.includes("-h")) {
2281
2427
  return { command: "help", topic: "auth login" };
@@ -2341,6 +2487,9 @@ function parseAuthLoginArgs(args) {
2341
2487
  if (tokenProfile === "create" && artifactId) {
2342
2488
  throw new TenderCliUsageError("--profile create must be account-scoped; remove --artifact.");
2343
2489
  }
2490
+ if (tokenProfile === "db-read" && !artifactId) {
2491
+ throw new TenderCliUsageError("--profile db-read requires --artifact <artifact-id>.");
2492
+ }
2344
2493
  return {
2345
2494
  command: "auth login",
2346
2495
  baseUrl,
@@ -2941,10 +3090,12 @@ function analyticsChartTemplates() {
2941
3090
  purpose: "Compare a queryable dimension property observed in events.",
2942
3091
  usableAsIs: false,
2943
3092
  placeholders: {
3093
+ eventName: "<event_name>",
2944
3094
  property: "<queryable_dimension_property>",
2945
3095
  },
2946
3096
  adaptWith: [
2947
3097
  "Use a queryable property key from capabilities chartSpec.groupBy, such as property:plan.",
3098
+ "Choose the event that owns the property and keep the event_name filter.",
2948
3099
  "Choose stable categorical properties with enough observations to compare.",
2949
3100
  ],
2950
3101
  spec: {
@@ -2953,7 +3104,7 @@ function analyticsChartTemplates() {
2953
3104
  visualization: "bar",
2954
3105
  metric: { name: "custom_event_count" },
2955
3106
  groupBy: [{ field: "property:<queryable_dimension_property>" }],
2956
- filters: [],
3107
+ filters: [{ field: "event_name", op: "=", value: "<event_name>" }],
2957
3108
  range: { preset: "30d" },
2958
3109
  sort: [{ field: "value", direction: "desc" }],
2959
3110
  limit: 10,
@@ -2966,10 +3117,12 @@ function analyticsChartTemplates() {
2966
3117
  purpose: "Track an observed numeric event property over time.",
2967
3118
  usableAsIs: false,
2968
3119
  placeholders: {
3120
+ eventName: "<event_name>",
2969
3121
  metric: "avg:<queryable_metric_property>",
2970
3122
  },
2971
3123
  adaptWith: [
2972
3124
  "Use a queryable numeric property key from capabilities chartSpec.metrics.",
3125
+ "Choose the event that owns the property and keep the event_name filter.",
2973
3126
  "Prefer averages for scores, durations, quantities, or ordinal values.",
2974
3127
  ],
2975
3128
  spec: {
@@ -2978,7 +3131,7 @@ function analyticsChartTemplates() {
2978
3131
  visualization: "line",
2979
3132
  metric: { name: "avg:<queryable_metric_property>" },
2980
3133
  groupBy: [{ field: "day" }],
2981
- filters: [],
3134
+ filters: [{ field: "event_name", op: "=", value: "<event_name>" }],
2982
3135
  range: { preset: "30d" },
2983
3136
  sort: [{ field: "label", direction: "asc" }],
2984
3137
  limit: 30,
@@ -3292,6 +3445,303 @@ function parseAnalyticsPropertiesActivateArgs(args) {
3292
3445
  ...parsed,
3293
3446
  };
3294
3447
  }
3448
+ function parseDbCommonArgs(args, startIndex) {
3449
+ let baseUrl = null;
3450
+ let flagToken = null;
3451
+ let profileName = null;
3452
+ let json = false;
3453
+ const remaining = [];
3454
+ for (let index = startIndex; index < args.length; index += 1) {
3455
+ const arg = args[index];
3456
+ if (arg === "--json") {
3457
+ json = true;
3458
+ continue;
3459
+ }
3460
+ if (arg === "--base-url") {
3461
+ baseUrl = parseBaseUrlFlag(args, index);
3462
+ index += 1;
3463
+ continue;
3464
+ }
3465
+ if (arg === "--token") {
3466
+ flagToken = parseTokenFlag(args, index);
3467
+ index += 1;
3468
+ continue;
3469
+ }
3470
+ if (arg === "--profile") {
3471
+ profileName = parseProfileName(args[index + 1], "--profile");
3472
+ index += 1;
3473
+ continue;
3474
+ }
3475
+ remaining.push(arg);
3476
+ }
3477
+ return { baseUrl, flagToken, profileName, json, remaining };
3478
+ }
3479
+ function parseDbEnvironment(value) {
3480
+ if (!value || value.startsWith("--")) {
3481
+ throw new TenderCliUsageError("--env requires a value. Use preview or published.");
3482
+ }
3483
+ if (value === "preview") {
3484
+ return "preview";
3485
+ }
3486
+ if (value === "published" ||
3487
+ value === "live" ||
3488
+ value === "production" ||
3489
+ value === "prod") {
3490
+ return "published";
3491
+ }
3492
+ throw new TenderCliUsageError("--env must be preview or published.");
3493
+ }
3494
+ function parseFlagAssignment(arg, flag) {
3495
+ const prefix = `${flag}=`;
3496
+ return arg.startsWith(prefix) ? arg.slice(prefix.length) : null;
3497
+ }
3498
+ function parseDbFlagValue(value, flag) {
3499
+ if (!value || value.startsWith("--")) {
3500
+ throw new TenderCliUsageError(`${flag} requires a value.`);
3501
+ }
3502
+ return value;
3503
+ }
3504
+ function parseDbSqlFlagValue(value) {
3505
+ if (value === undefined || value.length === 0) {
3506
+ throw new TenderCliUsageError("--sql requires a value.");
3507
+ }
3508
+ return value;
3509
+ }
3510
+ function parseDbFilePath(value) {
3511
+ if (!value || (value !== "-" && value.startsWith("-"))) {
3512
+ throw new TenderCliUsageError("--file requires a SQL file path or - for stdin.");
3513
+ }
3514
+ return value;
3515
+ }
3516
+ function parseDbParameter(value) {
3517
+ const raw = parseDbFlagValue(value, "--param");
3518
+ try {
3519
+ const parsed = JSON.parse(raw);
3520
+ if (parsed === null ||
3521
+ typeof parsed === "string" ||
3522
+ typeof parsed === "number") {
3523
+ return parsed;
3524
+ }
3525
+ }
3526
+ catch {
3527
+ // Fall back to a string binding.
3528
+ }
3529
+ return raw;
3530
+ }
3531
+ function parseDbCapabilitiesArgs(args) {
3532
+ if (args.includes("--help") || args.includes("-h")) {
3533
+ return { command: "help", topic: "app db capabilities" };
3534
+ }
3535
+ const artifactId = args[0];
3536
+ if (!artifactId || artifactId.startsWith("-")) {
3537
+ throw new TenderCliUsageError("app id is required. Example: tender app db capabilities <app-id> --json");
3538
+ }
3539
+ const parsed = parseDbCommonArgs(args, 1);
3540
+ if (parsed.remaining.length > 0) {
3541
+ throw new TenderCliUsageError(`Unknown app db capabilities option: ${parsed.remaining[0]}`);
3542
+ }
3543
+ return { command: "app db capabilities", artifactId, ...parsed };
3544
+ }
3545
+ function parseDbTablesArgs(args) {
3546
+ if (args.includes("--help") || args.includes("-h")) {
3547
+ return { command: "help", topic: "app db tables" };
3548
+ }
3549
+ const artifactId = args[0];
3550
+ if (!artifactId || artifactId.startsWith("-")) {
3551
+ throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --env published --json");
3552
+ }
3553
+ const parsed = parseDbCommonArgs(args, 1);
3554
+ let environment = null;
3555
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3556
+ const arg = parsed.remaining[index];
3557
+ const envAssignment = parseFlagAssignment(arg, "--env");
3558
+ if (envAssignment !== null) {
3559
+ environment = parseDbEnvironment(envAssignment);
3560
+ continue;
3561
+ }
3562
+ if (arg === "--env") {
3563
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3564
+ index += 1;
3565
+ continue;
3566
+ }
3567
+ throw new TenderCliUsageError(`Unknown app db tables option: ${arg}`);
3568
+ }
3569
+ if (!environment) {
3570
+ throw new TenderCliUsageError("--env is required. Example: tender app db tables artifact_123 --env published --json");
3571
+ }
3572
+ return { command: "app db tables", artifactId, environment, ...parsed };
3573
+ }
3574
+ function parseDbSchemaArgs(args) {
3575
+ if (args.includes("--help") || args.includes("-h")) {
3576
+ return { command: "help", topic: "app db schema" };
3577
+ }
3578
+ const artifactId = args[0];
3579
+ if (!artifactId || artifactId.startsWith("-")) {
3580
+ throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --env published --table votes --json");
3581
+ }
3582
+ const parsed = parseDbCommonArgs(args, 1);
3583
+ let environment = null;
3584
+ let tableName = null;
3585
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3586
+ const arg = parsed.remaining[index];
3587
+ const envAssignment = parseFlagAssignment(arg, "--env");
3588
+ if (envAssignment !== null) {
3589
+ environment = parseDbEnvironment(envAssignment);
3590
+ continue;
3591
+ }
3592
+ const tableAssignment = parseFlagAssignment(arg, "--table");
3593
+ if (tableAssignment !== null) {
3594
+ tableName = parseRequiredFlagValue(tableAssignment, "--table");
3595
+ continue;
3596
+ }
3597
+ if (arg === "--env") {
3598
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3599
+ index += 1;
3600
+ continue;
3601
+ }
3602
+ if (arg === "--table") {
3603
+ tableName = parseRequiredFlagValue(parsed.remaining[index + 1], "--table");
3604
+ index += 1;
3605
+ continue;
3606
+ }
3607
+ throw new TenderCliUsageError(`Unknown app db schema option: ${arg}`);
3608
+ }
3609
+ if (!environment) {
3610
+ throw new TenderCliUsageError("--env is required. Example: tender app db schema artifact_123 --env published --table votes --json");
3611
+ }
3612
+ if (!tableName) {
3613
+ throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --env published --table votes --json");
3614
+ }
3615
+ return {
3616
+ command: "app db schema",
3617
+ artifactId,
3618
+ environment,
3619
+ tableName,
3620
+ ...parsed,
3621
+ };
3622
+ }
3623
+ function parseDbQueryArgs(args) {
3624
+ if (args.includes("--help") || args.includes("-h")) {
3625
+ return { command: "help", topic: "app db query" };
3626
+ }
3627
+ const artifactId = args[0];
3628
+ if (!artifactId || artifactId.startsWith("-")) {
3629
+ throw new TenderCliUsageError("app id is required. Example: tender app db query <app-id> --env published --sql \"select * from votes limit 20\" --json");
3630
+ }
3631
+ const parsed = parseDbCommonArgs(args, 1);
3632
+ let environment = null;
3633
+ let sqlSource = null;
3634
+ const params = [];
3635
+ let limit = null;
3636
+ let offset = 0;
3637
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
3638
+ const arg = parsed.remaining[index];
3639
+ const envAssignment = parseFlagAssignment(arg, "--env");
3640
+ if (envAssignment !== null) {
3641
+ environment = parseDbEnvironment(envAssignment);
3642
+ continue;
3643
+ }
3644
+ const sqlAssignment = parseFlagAssignment(arg, "--sql");
3645
+ if (sqlAssignment !== null) {
3646
+ if (sqlSource) {
3647
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3648
+ }
3649
+ sqlSource = {
3650
+ kind: "inline",
3651
+ value: parseDbSqlFlagValue(sqlAssignment),
3652
+ };
3653
+ continue;
3654
+ }
3655
+ const fileAssignment = parseFlagAssignment(arg, "--file");
3656
+ if (fileAssignment !== null) {
3657
+ if (sqlSource) {
3658
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3659
+ }
3660
+ sqlSource = {
3661
+ kind: "file",
3662
+ value: parseDbFilePath(fileAssignment),
3663
+ };
3664
+ continue;
3665
+ }
3666
+ const paramAssignment = parseFlagAssignment(arg, "--param");
3667
+ if (paramAssignment !== null) {
3668
+ params.push(parseDbParameter(paramAssignment));
3669
+ continue;
3670
+ }
3671
+ const limitAssignment = parseFlagAssignment(arg, "--limit");
3672
+ if (limitAssignment !== null) {
3673
+ limit = parsePositiveIntegerFlag(limitAssignment, "--limit");
3674
+ continue;
3675
+ }
3676
+ const offsetAssignment = parseFlagAssignment(arg, "--offset");
3677
+ if (offsetAssignment !== null) {
3678
+ offset = parseNonNegativeIntegerFlag(offsetAssignment, "--offset");
3679
+ continue;
3680
+ }
3681
+ if (arg === "--env") {
3682
+ environment = parseDbEnvironment(parsed.remaining[index + 1]);
3683
+ index += 1;
3684
+ continue;
3685
+ }
3686
+ if (arg === "--sql") {
3687
+ if (sqlSource) {
3688
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3689
+ }
3690
+ sqlSource = {
3691
+ kind: "inline",
3692
+ value: parseDbSqlFlagValue(parsed.remaining[index + 1]),
3693
+ };
3694
+ index += 1;
3695
+ continue;
3696
+ }
3697
+ if (arg === "--file") {
3698
+ if (sqlSource) {
3699
+ throw new TenderCliUsageError("--sql and --file cannot be repeated or combined.");
3700
+ }
3701
+ sqlSource = {
3702
+ kind: "file",
3703
+ value: parseDbFilePath(parsed.remaining[index + 1]),
3704
+ };
3705
+ index += 1;
3706
+ continue;
3707
+ }
3708
+ if (arg === "--param") {
3709
+ params.push(parseDbParameter(parsed.remaining[index + 1]));
3710
+ index += 1;
3711
+ continue;
3712
+ }
3713
+ if (arg === "--limit") {
3714
+ limit = parsePositiveIntegerFlag(parsed.remaining[index + 1], "--limit");
3715
+ index += 1;
3716
+ continue;
3717
+ }
3718
+ if (arg === "--offset") {
3719
+ offset = parseNonNegativeIntegerFlag(parsed.remaining[index + 1], "--offset");
3720
+ index += 1;
3721
+ continue;
3722
+ }
3723
+ throw new TenderCliUsageError(`Unknown app db query option: ${arg}`);
3724
+ }
3725
+ if (!environment) {
3726
+ throw new TenderCliUsageError("--env is required. Example: tender app db query artifact_123 --env published --sql \"select * from votes limit 20\" --json");
3727
+ }
3728
+ if (!sqlSource) {
3729
+ throw new TenderCliUsageError("--sql or --file is required. Example: tender app db query artifact_123 --env published --sql \"select * from votes limit 20\" --json");
3730
+ }
3731
+ if (params.length > DB_MAX_BOUND_PARAMETERS) {
3732
+ throw new TenderCliUsageError(`App database queries are limited to ${DB_MAX_BOUND_PARAMETERS} bound parameters.`);
3733
+ }
3734
+ return {
3735
+ command: "app db query",
3736
+ artifactId,
3737
+ environment,
3738
+ sqlSource,
3739
+ params,
3740
+ limit,
3741
+ offset,
3742
+ ...parsed,
3743
+ };
3744
+ }
3295
3745
  function parseTenderArgs(args) {
3296
3746
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
3297
3747
  return { command: "help", topic: "root" };
@@ -3441,6 +3891,27 @@ function parseTenderArgs(args) {
3441
3891
  resourceArgs[3] === "delete") {
3442
3892
  return parseAnalyticsChartsDeleteArgs(resourceArgs.slice(4));
3443
3893
  }
3894
+ if (resourceArgs[1] === "db" &&
3895
+ (resourceArgs[2] === undefined ||
3896
+ resourceArgs[2] === "--help" ||
3897
+ resourceArgs[2] === "-h")) {
3898
+ return { command: "help", topic: "app db" };
3899
+ }
3900
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "capabilities") {
3901
+ return parseDbCapabilitiesArgs(resourceArgs.slice(3));
3902
+ }
3903
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "tables") {
3904
+ return parseDbTablesArgs(resourceArgs.slice(3));
3905
+ }
3906
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "schema") {
3907
+ return parseDbSchemaArgs(resourceArgs.slice(3));
3908
+ }
3909
+ if (resourceArgs[1] === "db" && resourceArgs[2] === "query") {
3910
+ return parseDbQueryArgs(resourceArgs.slice(3));
3911
+ }
3912
+ if (resourceArgs[1] === "db") {
3913
+ throw new TenderCliUsageError(`Unknown app db command: ${resourceArgs[2]}`);
3914
+ }
3444
3915
  if (resourceArgs[1] === "context" &&
3445
3916
  (resourceArgs[2] === undefined ||
3446
3917
  resourceArgs[2] === "--help" ||
@@ -3833,15 +4304,72 @@ async function requestAnalyticsApi(input) {
3833
4304
  const message = (typeof payload?.message === "string" && payload.message) ||
3834
4305
  (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
3835
4306
  `Analytics request failed with HTTP ${response.status}.`;
4307
+ const example = analyticsCorrectedExample(input.artifactId, input.analyticsPath);
3836
4308
  throw new TenderCliAnalyticsApiError([
3837
4309
  message,
3838
4310
  `Scope: artifact ${input.artifactId}, analytics/${input.analyticsPath || "summary"}.`,
3839
4311
  `Reason: ${reasonCode}.`,
3840
- `Example: ${analyticsCorrectedExample(input.artifactId, input.analyticsPath)}.`,
3841
- ].join("\n"), reasonCode);
4312
+ `Example: ${example}.`,
4313
+ ].join("\n"), reasonCode, {
4314
+ artifactId: input.artifactId,
4315
+ surface: `analytics/${input.analyticsPath || "summary"}`,
4316
+ }, example);
3842
4317
  }
3843
4318
  return payload;
3844
4319
  }
4320
+ function appendDbEnvironment(params, environment) {
4321
+ params.set("env", environment);
4322
+ }
4323
+ async function requestDbApi(input) {
4324
+ const params = new URLSearchParams();
4325
+ if (input.environment) {
4326
+ appendDbEnvironment(params, input.environment);
4327
+ }
4328
+ if (input.tableName) {
4329
+ params.set("table", input.tableName);
4330
+ }
4331
+ const query = params.size > 0 ? `?${params.toString()}` : "";
4332
+ const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/db/${input.dbPath}${query}`, {
4333
+ method: input.method,
4334
+ headers: {
4335
+ authorization: `Bearer ${input.token}`,
4336
+ accept: "application/json",
4337
+ ...(input.body === undefined ? {} : { "content-type": "application/json" }),
4338
+ },
4339
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
4340
+ });
4341
+ const payload = await parseJsonResponse(response);
4342
+ if (!response.ok || !payload?.ok) {
4343
+ const reasonCode = (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4344
+ `http_${response.status}`;
4345
+ const message = (typeof payload?.message === "string" && payload.message) ||
4346
+ (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4347
+ `App database request failed with HTTP ${response.status}.`;
4348
+ const example = dbCorrectedExample(input.artifactId, input.dbPath);
4349
+ throw new TenderCliDbApiError([
4350
+ message,
4351
+ `Scope: artifact ${input.artifactId}, db/${input.dbPath}.`,
4352
+ `Reason: ${reasonCode}.`,
4353
+ `Example: ${example}.`,
4354
+ ].join("\n"), reasonCode, {
4355
+ artifactId: input.artifactId,
4356
+ surface: `db/${input.dbPath}`,
4357
+ }, example);
4358
+ }
4359
+ return payload;
4360
+ }
4361
+ function dbCorrectedExample(artifactId, dbPath) {
4362
+ if (dbPath === "capabilities") {
4363
+ return `tender app db capabilities ${artifactId} --json`;
4364
+ }
4365
+ if (dbPath === "tables") {
4366
+ return `tender app db tables ${artifactId} --env published --json`;
4367
+ }
4368
+ if (dbPath === "schema") {
4369
+ return `tender app db schema ${artifactId} --env published --table <table-name> --json`;
4370
+ }
4371
+ return `tender app db query ${artifactId} --env published --sql "select * from <table-name> limit 20" --json`;
4372
+ }
3845
4373
  function analyticsCorrectedExample(artifactId, analyticsPath) {
3846
4374
  if (analyticsPath === "capabilities") {
3847
4375
  return `tender app analytics capabilities ${artifactId} --include-catalog --range 30d --json`;
@@ -4089,7 +4617,7 @@ async function requestAnalyticsQueryWithPropertyGuidance(input) {
4089
4617
  spec: input.spec,
4090
4618
  });
4091
4619
  if (guidance) {
4092
- throw new TenderCliAnalyticsApiError(`${error.message}\n\n${guidance}`, error.reasonCode);
4620
+ throw new TenderCliAnalyticsApiError(`${error.message}\n\n${guidance}`, error.reasonCode, error.scope, error.example);
4093
4621
  }
4094
4622
  }
4095
4623
  throw error;
@@ -6127,6 +6655,137 @@ async function resolveAnalyticsCredentials(command, runtime) {
6127
6655
  env: runtime.env ?? process.env,
6128
6656
  });
6129
6657
  }
6658
+ async function resolveDbCredentials(command, runtime) {
6659
+ return await resolveApiCredentials({
6660
+ baseUrl: command.baseUrl,
6661
+ flagToken: command.flagToken,
6662
+ profileName: command.profileName,
6663
+ env: runtime.env ?? process.env,
6664
+ });
6665
+ }
6666
+ function validateDbSql(sql) {
6667
+ if (!sql.trim()) {
6668
+ throw new TenderCliUsageError("SQL statement is required.");
6669
+ }
6670
+ const byteLength = new TextEncoder().encode(sql).byteLength;
6671
+ if (byteLength > DB_SQL_STATEMENT_MAX_BYTES) {
6672
+ throw new TenderCliUsageError(`SQL statement is limited to ${DB_SQL_STATEMENT_MAX_BYTES} bytes.`);
6673
+ }
6674
+ }
6675
+ async function readDbSql(command, runtime) {
6676
+ const sql = command.sqlSource.kind === "inline"
6677
+ ? command.sqlSource.value
6678
+ : command.sqlSource.value === "-"
6679
+ ? runtime.stdin
6680
+ : await readFile(path.resolve(command.sqlSource.value), "utf8");
6681
+ if (!sql) {
6682
+ throw new TenderCliUsageError("--file - requires SQL on stdin.");
6683
+ }
6684
+ validateDbSql(sql);
6685
+ return sql;
6686
+ }
6687
+ function printDbPayload(command, io, payload, fallbackLines) {
6688
+ if (command.json) {
6689
+ io.stdout(JSON.stringify(payload, null, 2));
6690
+ }
6691
+ else {
6692
+ io.stdout(fallbackLines.join("\n"));
6693
+ }
6694
+ }
6695
+ async function runDbCapabilities(command, io, runtime) {
6696
+ const credentials = await resolveDbCredentials(command, runtime);
6697
+ const payload = await requestDbApi({
6698
+ artifactId: command.artifactId,
6699
+ dbPath: "capabilities",
6700
+ method: "GET",
6701
+ baseUrl: credentials.baseUrl,
6702
+ token: credentials.token,
6703
+ fetcher: runtime.fetcher ?? fetch,
6704
+ });
6705
+ const limits = isCliJsonRecord(payload.limits) ? payload.limits : {};
6706
+ const runtimeInfo = isCliJsonRecord(payload.runtime) ? payload.runtime : {};
6707
+ printDbPayload(command, io, payload, [
6708
+ `app_id: ${command.artifactId}`,
6709
+ `backend: ${String(runtimeInfo.backend ?? "app-database")}`,
6710
+ `default_limit: ${String(limits.defaultLimit ?? "")}`,
6711
+ `max_limit: ${String(limits.maxInteractiveLimit ?? "")}`,
6712
+ ]);
6713
+ return 0;
6714
+ }
6715
+ async function runDbTables(command, io, runtime) {
6716
+ const credentials = await resolveDbCredentials(command, runtime);
6717
+ const payload = await requestDbApi({
6718
+ artifactId: command.artifactId,
6719
+ dbPath: "tables",
6720
+ method: "GET",
6721
+ baseUrl: credentials.baseUrl,
6722
+ token: credentials.token,
6723
+ fetcher: runtime.fetcher ?? fetch,
6724
+ environment: command.environment,
6725
+ });
6726
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6727
+ printDbPayload(command, io, payload, [
6728
+ `app_id: ${command.artifactId}`,
6729
+ `environment: ${command.environment}`,
6730
+ `tables: ${rows.length}`,
6731
+ ]);
6732
+ return 0;
6733
+ }
6734
+ async function runDbSchema(command, io, runtime) {
6735
+ const credentials = await resolveDbCredentials(command, runtime);
6736
+ const payload = await requestDbApi({
6737
+ artifactId: command.artifactId,
6738
+ dbPath: "schema",
6739
+ method: "GET",
6740
+ baseUrl: credentials.baseUrl,
6741
+ token: credentials.token,
6742
+ fetcher: runtime.fetcher ?? fetch,
6743
+ environment: command.environment,
6744
+ tableName: command.tableName,
6745
+ });
6746
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6747
+ printDbPayload(command, io, payload, [
6748
+ `app_id: ${command.artifactId}`,
6749
+ `environment: ${command.environment}`,
6750
+ `table: ${command.tableName}`,
6751
+ `schema_rows: ${rows.length}`,
6752
+ ]);
6753
+ return 0;
6754
+ }
6755
+ async function runDbQuery(command, io, runtime) {
6756
+ const credentials = await resolveDbCredentials(command, runtime);
6757
+ const sql = await readDbSql(command, runtime);
6758
+ const body = {
6759
+ environment: command.environment,
6760
+ mode: "read",
6761
+ sql,
6762
+ params: command.params,
6763
+ ...(command.limit === null ? {} : { limit: command.limit }),
6764
+ offset: command.offset,
6765
+ };
6766
+ const payload = await requestDbApi({
6767
+ artifactId: command.artifactId,
6768
+ dbPath: "query",
6769
+ method: "POST",
6770
+ baseUrl: credentials.baseUrl,
6771
+ token: credentials.token,
6772
+ fetcher: runtime.fetcher ?? fetch,
6773
+ body,
6774
+ });
6775
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
6776
+ printDbPayload(command, io, payload, [
6777
+ `app_id: ${command.artifactId}`,
6778
+ `environment: ${command.environment}`,
6779
+ `rows: ${rows.length}`,
6780
+ `offset: ${String(payload.offset ?? command.offset)}`,
6781
+ `applied_limit: ${String(payload.appliedLimit ?? command.limit ?? "")}`,
6782
+ `truncated: ${String(payload.truncated ?? false)}`,
6783
+ ...(typeof payload.nextOffset === "number"
6784
+ ? [`next_offset: ${payload.nextOffset}`]
6785
+ : []),
6786
+ ]);
6787
+ return 0;
6788
+ }
6130
6789
  async function runAnalyticsSummary(command, io, runtime) {
6131
6790
  const credentials = await resolveAnalyticsCredentials(command, runtime);
6132
6791
  const payload = await requestAnalyticsApi({
@@ -6530,12 +7189,106 @@ async function runAnalyticsChartsDelete(command, io, runtime) {
6530
7189
  ]);
6531
7190
  return 0;
6532
7191
  }
7192
+ function cliErrorMessage(error) {
7193
+ if (error instanceof Error) {
7194
+ return error.message.split("\n")[0] ?? error.message;
7195
+ }
7196
+ return String(error);
7197
+ }
7198
+ function cliErrorCode(error) {
7199
+ if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
7200
+ return error.reasonCode;
7201
+ }
7202
+ if (error && typeof error === "object" && "code" in error) {
7203
+ const code = error.code;
7204
+ if (code === "ENOENT") {
7205
+ return "file_not_found";
7206
+ }
7207
+ if (typeof code === "string" && code.length > 0) {
7208
+ return code.toLowerCase();
7209
+ }
7210
+ }
7211
+ return error instanceof TenderCliUsageError ? "cli_usage_error" : "cli_error";
7212
+ }
7213
+ function commandNameForError(args, command) {
7214
+ if (command) {
7215
+ return command.command === "help" ? `help ${command.topic}` : command.command;
7216
+ }
7217
+ if (args[0] === "app" && args[1] === "db" && args[2]) {
7218
+ return `app db ${args[2]}`;
7219
+ }
7220
+ if (args[0] === "app" && args[1]) {
7221
+ return `app ${args[1]}`;
7222
+ }
7223
+ return args[0] ?? "unknown";
7224
+ }
7225
+ function dbPathFromCommandName(commandName) {
7226
+ if (commandName === "app db capabilities") {
7227
+ return "capabilities";
7228
+ }
7229
+ if (commandName === "app db tables") {
7230
+ return "tables";
7231
+ }
7232
+ if (commandName === "app db schema") {
7233
+ return "schema";
7234
+ }
7235
+ if (commandName === "app db query") {
7236
+ return "query";
7237
+ }
7238
+ return null;
7239
+ }
7240
+ function artifactIdForError(args, command) {
7241
+ if (command && "artifactId" in command) {
7242
+ return command.artifactId;
7243
+ }
7244
+ if (args[0] === "app" && args[1] === "db" && args[3] && !args[3].startsWith("-")) {
7245
+ return args[3];
7246
+ }
7247
+ return null;
7248
+ }
7249
+ function scopeForError(error, args, command) {
7250
+ if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
7251
+ return error.scope;
7252
+ }
7253
+ const commandName = commandNameForError(args, command);
7254
+ const dbPath = dbPathFromCommandName(commandName);
7255
+ const artifactId = artifactIdForError(args, command);
7256
+ if (dbPath && artifactId) {
7257
+ return {
7258
+ artifactId,
7259
+ surface: `db/${dbPath}`,
7260
+ };
7261
+ }
7262
+ return null;
7263
+ }
7264
+ function exampleForError(error, args, command) {
7265
+ if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
7266
+ return error.example;
7267
+ }
7268
+ const commandName = commandNameForError(args, command);
7269
+ const dbPath = dbPathFromCommandName(commandName);
7270
+ const artifactId = artifactIdForError(args, command) ?? "artifact_123";
7271
+ return dbPath ? dbCorrectedExample(artifactId, dbPath) : null;
7272
+ }
7273
+ function cliErrorPayload(error, args, command) {
7274
+ const scope = scopeForError(error, args, command);
7275
+ const example = exampleForError(error, args, command);
7276
+ return {
7277
+ ok: false,
7278
+ command: commandNameForError(args, command),
7279
+ reasonCode: cliErrorCode(error),
7280
+ message: cliErrorMessage(error),
7281
+ ...(scope ? { scope } : {}),
7282
+ ...(example ? { example } : {}),
7283
+ };
7284
+ }
6533
7285
  export async function runTenderCli(args, io = {
6534
7286
  stdout: (message) => console.log(message),
6535
7287
  stderr: (message) => console.error(message),
6536
7288
  }, runtime = {}) {
7289
+ let command = null;
6537
7290
  try {
6538
- const command = parseTenderArgs(args);
7291
+ command = parseTenderArgs(args);
6539
7292
  if (command.command === "help") {
6540
7293
  if (command.topic === "capabilities") {
6541
7294
  io.stdout(capabilitiesHelp());
@@ -6660,6 +7413,21 @@ export async function runTenderCli(args, io = {
6660
7413
  else if (command.topic === "app analytics properties activate") {
6661
7414
  io.stdout(analyticsPropertiesActivateHelp());
6662
7415
  }
7416
+ else if (command.topic === "app db") {
7417
+ io.stdout(dbHelp());
7418
+ }
7419
+ else if (command.topic === "app db capabilities") {
7420
+ io.stdout(dbCapabilitiesHelp());
7421
+ }
7422
+ else if (command.topic === "app db tables") {
7423
+ io.stdout(dbTablesHelp());
7424
+ }
7425
+ else if (command.topic === "app db schema") {
7426
+ io.stdout(dbSchemaHelp());
7427
+ }
7428
+ else if (command.topic === "app db query") {
7429
+ io.stdout(dbQueryHelp());
7430
+ }
6663
7431
  else if (command.topic === "app") {
6664
7432
  io.stdout(appHelp());
6665
7433
  }
@@ -6776,22 +7544,44 @@ export async function runTenderCli(args, io = {
6776
7544
  if (command.command === "app analytics properties activate") {
6777
7545
  return await runAnalyticsPropertiesActivate(command, io, runtime);
6778
7546
  }
7547
+ if (command.command === "app db capabilities") {
7548
+ return await runDbCapabilities(command, io, runtime);
7549
+ }
7550
+ if (command.command === "app db tables") {
7551
+ return await runDbTables(command, io, runtime);
7552
+ }
7553
+ if (command.command === "app db schema") {
7554
+ return await runDbSchema(command, io, runtime);
7555
+ }
7556
+ if (command.command === "app db query") {
7557
+ return await runDbQuery(command, io, runtime);
7558
+ }
6779
7559
  if (command.command === "app git remote") {
6780
7560
  return await runGitRemote(command, io, runtime);
6781
7561
  }
6782
7562
  return await runDoctor(command, io);
6783
7563
  }
6784
7564
  catch (error) {
7565
+ if (args.includes("--json") && args[0] === "app" && args[1] === "db") {
7566
+ io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
7567
+ return error instanceof TenderCliUsageError ? 2 : 1;
7568
+ }
6785
7569
  io.stderr(error instanceof Error ? error.message : String(error));
6786
7570
  io.stderr("Run `tender --help` for examples.");
6787
7571
  return error instanceof TenderCliUsageError ? 2 : 1;
6788
7572
  }
6789
7573
  }
6790
7574
  const currentFile = fileURLToPath(import.meta.url);
7575
+ function hasFlagValue(args, flag, value) {
7576
+ return args.some((arg, index) => (arg === flag && args[index + 1] === value) || arg === `${flag}=${value}`);
7577
+ }
6791
7578
  function shouldReadEntrypointStdin(args) {
6792
7579
  return (args[0] === "app" &&
6793
7580
  ((args[1] === "git" && args[2] === "credential") ||
6794
- (args[1] === "analytics" && args.includes("--spec") && args.includes("-"))));
7581
+ (args[1] === "analytics" && hasFlagValue(args, "--spec", "-")) ||
7582
+ (args[1] === "db" &&
7583
+ args[2] === "query" &&
7584
+ hasFlagValue(args, "--file", "-"))));
6795
7585
  }
6796
7586
  async function readEntrypointStdin() {
6797
7587
  if (process.stdin.isTTY) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"