@tenderprompt/cli 0.1.13 → 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 +16 -10
  2. package/dist/index.js +282 -58
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -133,9 +133,10 @@ tender app build artifact_123 --commit "$(git rev-parse HEAD)" --json
133
133
  tender app preview artifact_123 --commit "$(git rev-parse HEAD)" --stream --json
134
134
  tender app publish artifact_123 --dry-run --json
135
135
  tender app db capabilities artifact_123 --json
136
- tender app db tables artifact_123 --json
137
- tender app db schema artifact_123 --table votes --json
138
- tender app db query artifact_123 --sql "select * from votes limit 20" --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
139
140
  ```
140
141
 
141
142
  ## Analytics
@@ -249,7 +250,8 @@ ids, URLs, or high-cardinality values unless the chart has a clear purpose.
249
250
  App database commands are artifact-scoped and go through Tender's server-side
250
251
  authorization and read-only query checks. The CLI only accepts an app id; it
251
252
  does not accept tenant ids, runner names, provider account ids, or runtime
252
- storage identifiers.
253
+ storage identifiers. Commands that read data require an explicit `--env preview`
254
+ or `--env published`; there is no default environment.
253
255
 
254
256
  Start with capabilities:
255
257
 
@@ -262,24 +264,28 @@ tender app db capabilities artifact_123 --profile db --json
262
264
  Common read-only commands:
263
265
 
264
266
  ```bash
265
- tender app db tables artifact_123 --profile db --json
267
+ tender app db tables artifact_123 --env published --profile db --json
266
268
 
267
- tender app db schema artifact_123 --table votes --profile db --json
269
+ tender app db schema artifact_123 --env published --table votes --profile db --json
268
270
 
269
- tender app db query artifact_123 --sql "select * from votes limit 20" --profile db --json
271
+ tender app db query artifact_123 --env published --sql "select * from votes limit 20" --profile db --json
270
272
 
271
273
  tender app db query artifact_123 \
274
+ --env published \
272
275
  --sql "select * from votes where choice = ?" \
273
276
  --param coffee \
274
277
  --profile db \
275
278
  --json
276
279
 
277
- tender app db query artifact_123 --file ./debug.sql --limit 50 --profile db --json
280
+ tender app db query artifact_123 --env published --file ./debug.sql --limit 50 --offset 100 --profile db --json
278
281
 
279
- tender app db query artifact_123 --file - --profile db --json < debug.sql
282
+ tender app db query artifact_123 --env published --file - --offset 100 --profile db --json < debug.sql
280
283
  ```
281
284
 
282
- The query command sends one read-only statement with positional bindings.
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.
283
289
  Client and server limits include a 100 KB statement cap and at most 100 bound
284
290
  parameters. The server enforces the final policy, row limits, response limits,
285
291
  artifact scope, and audit logging.
package/dist/index.js CHANGED
@@ -41,18 +41,26 @@ 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
48
  this.name = "TenderCliAnalyticsApiError";
47
49
  this.reasonCode = reasonCode;
50
+ this.scope = scope;
51
+ this.example = example;
48
52
  }
49
53
  }
50
54
  class TenderCliDbApiError extends Error {
51
55
  reasonCode;
52
- constructor(message, reasonCode) {
56
+ scope;
57
+ example;
58
+ constructor(message, reasonCode, scope, example) {
53
59
  super(message);
54
60
  this.name = "TenderCliDbApiError";
55
61
  this.reasonCode = reasonCode;
62
+ this.scope = scope;
63
+ this.example = example;
56
64
  }
57
65
  }
58
66
  const DEFAULT_PROFILE_NAME = "default";
@@ -101,7 +109,7 @@ Commands:
101
109
  Generate useful starter chart suggestions
102
110
  tender app db capabilities <app-id>
103
111
  Describe app database query limits and policy
104
- tender app db query <app-id> --sql <sql>
112
+ tender app db query <app-id> --env <preview|published> --sql <sql>
105
113
  Run one read-only app database query
106
114
  tender app context fetch <app-id>
107
115
  Fetch managed agent context into a checkout
@@ -135,9 +143,9 @@ Examples:
135
143
  tender app analytics query artifact_123 --spec chart.json --json
136
144
  tender app analytics suggestions artifact_123 --range 30d --json
137
145
  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
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
141
149
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
142
150
  tender app context status artifact_123 --dir ./widget --json
143
151
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -238,11 +246,11 @@ Commands:
238
246
  Make a custom event property queryable
239
247
  tender app db capabilities <app-id>
240
248
  Describe app database query limits and policy
241
- tender app db tables <app-id>
249
+ tender app db tables <app-id> --env <preview|published>
242
250
  List app database tables and views
243
- tender app db schema <app-id> --table <name>
251
+ tender app db schema <app-id> --env <preview|published> --table <name>
244
252
  Show table and index schema for one table
245
- tender app db query <app-id> --sql <sql>
253
+ tender app db query <app-id> --env <preview|published> --sql <sql>
246
254
  Run one read-only app database query
247
255
  tender app context fetch <app-id>
248
256
  Fetch AGENTS.md, skills, and Tender App scaffold files
@@ -285,9 +293,9 @@ Examples:
285
293
  tender app analytics properties list artifact_123 --json
286
294
  tender app analytics properties activate artifact_123 --event signup_started --property plan --type dimension --dry-run --json
287
295
  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
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
291
299
  tender app context fetch artifact_123 --dir ./widget --dry-run --json
292
300
  tender app context status artifact_123 --dir ./widget --json
293
301
  tender app context refresh artifact_123 --dir ./widget --dry-run --json
@@ -471,16 +479,19 @@ function dbHelp() {
471
479
 
472
480
  Commands:
473
481
  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
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
477
488
 
478
489
  Examples:
479
490
  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`;
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`;
484
495
  }
485
496
  function dbCapabilitiesHelp() {
486
497
  return `Usage: tender app db capabilities <app-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
@@ -489,33 +500,35 @@ Examples:
489
500
  tender app db capabilities artifact_123 --json`;
490
501
  }
491
502
  function dbTablesHelp() {
492
- return `Usage: tender app db tables <app-id> [--env preview] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
503
+ return `Usage: tender app db tables <app-id> --env <preview|published> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
493
504
 
494
505
  Examples:
495
- tender app db tables artifact_123 --json
506
+ tender app db tables artifact_123 --env published --json
496
507
  tender app db tables artifact_123 --env preview --json`;
497
508
  }
498
509
  function dbSchemaHelp() {
499
- return `Usage: tender app db schema <app-id> --table <name> [--env preview] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
510
+ return `Usage: tender app db schema <app-id> --env <preview|published> --table <name> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
500
511
 
501
512
  Examples:
502
- tender app db schema artifact_123 --table votes --json
513
+ tender app db schema artifact_123 --env published --table votes --json
503
514
  tender app db schema artifact_123 --table preference_votes --env preview --json`;
504
515
  }
505
516
  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]
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]
507
518
 
508
519
  Options:
520
+ --env <value> Target app environment: preview or published. Required.
509
521
  --sql <sql> SQL statement. Must be one read-only statement.
510
522
  --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.
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.
513
526
 
514
527
  Examples:
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`;
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`;
519
532
  }
520
533
  function artifactsListHelp() {
521
534
  return `Usage: tender app list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
@@ -921,10 +934,10 @@ function tenderCliCapabilities() {
921
934
  commands: [
922
935
  "tender auth login --device --profile db-read --artifact <artifact-id> --ttl 7d --save-profile db --json",
923
936
  "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",
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",
928
941
  ],
929
942
  notes: [
930
943
  "The CLI only accepts artifact ids. It never accepts tenant ids, runner names, provider account ids, or runtime storage identifiers.",
@@ -1032,19 +1045,19 @@ function tenderCliCapabilities() {
1032
1045
  writes: false,
1033
1046
  },
1034
1047
  {
1035
- command: "tender app db tables <artifact-id> --json",
1048
+ command: "tender app db tables <artifact-id> --env published --json",
1036
1049
  purpose: "List app database tables and views for one artifact.",
1037
1050
  requiresAuth: true,
1038
1051
  writes: false,
1039
1052
  },
1040
1053
  {
1041
- command: "tender app db schema <artifact-id> --table <table-name> --json",
1054
+ command: "tender app db schema <artifact-id> --env published --table <table-name> --json",
1042
1055
  purpose: "Inspect table, index, and trigger schema for one app database table.",
1043
1056
  requiresAuth: true,
1044
1057
  writes: false,
1045
1058
  },
1046
1059
  {
1047
- command: "tender app db query <artifact-id> --sql \"select * from <table-name> limit 20\" --json",
1060
+ command: "tender app db query <artifact-id> --env published --sql \"select * from <table-name> limit 20\" --json",
1048
1061
  purpose: "Run one bounded read-only app database query.",
1049
1062
  requiresAuth: true,
1050
1063
  writes: false,
@@ -2402,6 +2415,13 @@ function parsePositiveIntegerFlag(value, flag) {
2402
2415
  }
2403
2416
  return parsed;
2404
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
+ }
2405
2425
  function parseAuthLoginArgs(args) {
2406
2426
  if (args.includes("--help") || args.includes("-h")) {
2407
2427
  return { command: "help", topic: "auth login" };
@@ -3457,10 +3477,23 @@ function parseDbCommonArgs(args, startIndex) {
3457
3477
  return { baseUrl, flagToken, profileName, json, remaining };
3458
3478
  }
3459
3479
  function parseDbEnvironment(value) {
3460
- if (value === undefined || value === "preview") {
3480
+ if (!value || value.startsWith("--")) {
3481
+ throw new TenderCliUsageError("--env requires a value. Use preview or published.");
3482
+ }
3483
+ if (value === "preview") {
3461
3484
  return "preview";
3462
3485
  }
3463
- throw new TenderCliUsageError("--env must be preview.");
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;
3464
3497
  }
3465
3498
  function parseDbFlagValue(value, flag) {
3466
3499
  if (!value || value.startsWith("--")) {
@@ -3468,6 +3501,12 @@ function parseDbFlagValue(value, flag) {
3468
3501
  }
3469
3502
  return value;
3470
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
+ }
3471
3510
  function parseDbFilePath(value) {
3472
3511
  if (!value || (value !== "-" && value.startsWith("-"))) {
3473
3512
  throw new TenderCliUsageError("--file requires a SQL file path or - for stdin.");
@@ -3509,12 +3548,17 @@ function parseDbTablesArgs(args) {
3509
3548
  }
3510
3549
  const artifactId = args[0];
3511
3550
  if (!artifactId || artifactId.startsWith("-")) {
3512
- throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --json");
3551
+ throw new TenderCliUsageError("app id is required. Example: tender app db tables <app-id> --env published --json");
3513
3552
  }
3514
3553
  const parsed = parseDbCommonArgs(args, 1);
3515
- let environment = "preview";
3554
+ let environment = null;
3516
3555
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3517
3556
  const arg = parsed.remaining[index];
3557
+ const envAssignment = parseFlagAssignment(arg, "--env");
3558
+ if (envAssignment !== null) {
3559
+ environment = parseDbEnvironment(envAssignment);
3560
+ continue;
3561
+ }
3518
3562
  if (arg === "--env") {
3519
3563
  environment = parseDbEnvironment(parsed.remaining[index + 1]);
3520
3564
  index += 1;
@@ -3522,6 +3566,9 @@ function parseDbTablesArgs(args) {
3522
3566
  }
3523
3567
  throw new TenderCliUsageError(`Unknown app db tables option: ${arg}`);
3524
3568
  }
3569
+ if (!environment) {
3570
+ throw new TenderCliUsageError("--env is required. Example: tender app db tables artifact_123 --env published --json");
3571
+ }
3525
3572
  return { command: "app db tables", artifactId, environment, ...parsed };
3526
3573
  }
3527
3574
  function parseDbSchemaArgs(args) {
@@ -3530,13 +3577,23 @@ function parseDbSchemaArgs(args) {
3530
3577
  }
3531
3578
  const artifactId = args[0];
3532
3579
  if (!artifactId || artifactId.startsWith("-")) {
3533
- throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --table votes --json");
3580
+ throw new TenderCliUsageError("app id is required. Example: tender app db schema <app-id> --env published --table votes --json");
3534
3581
  }
3535
3582
  const parsed = parseDbCommonArgs(args, 1);
3536
- let environment = "preview";
3583
+ let environment = null;
3537
3584
  let tableName = null;
3538
3585
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3539
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
+ }
3540
3597
  if (arg === "--env") {
3541
3598
  environment = parseDbEnvironment(parsed.remaining[index + 1]);
3542
3599
  index += 1;
@@ -3549,8 +3606,11 @@ function parseDbSchemaArgs(args) {
3549
3606
  }
3550
3607
  throw new TenderCliUsageError(`Unknown app db schema option: ${arg}`);
3551
3608
  }
3609
+ if (!environment) {
3610
+ throw new TenderCliUsageError("--env is required. Example: tender app db schema artifact_123 --env published --table votes --json");
3611
+ }
3552
3612
  if (!tableName) {
3553
- throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --table votes --json");
3613
+ throw new TenderCliUsageError("--table is required. Example: tender app db schema artifact_123 --env published --table votes --json");
3554
3614
  }
3555
3615
  return {
3556
3616
  command: "app db schema",
@@ -3566,15 +3626,58 @@ function parseDbQueryArgs(args) {
3566
3626
  }
3567
3627
  const artifactId = args[0];
3568
3628
  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");
3629
+ throw new TenderCliUsageError("app id is required. Example: tender app db query <app-id> --env published --sql \"select * from votes limit 20\" --json");
3570
3630
  }
3571
3631
  const parsed = parseDbCommonArgs(args, 1);
3572
- let environment = "preview";
3632
+ let environment = null;
3573
3633
  let sqlSource = null;
3574
3634
  const params = [];
3575
3635
  let limit = null;
3636
+ let offset = 0;
3576
3637
  for (let index = 0; index < parsed.remaining.length; index += 1) {
3577
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
+ }
3578
3681
  if (arg === "--env") {
3579
3682
  environment = parseDbEnvironment(parsed.remaining[index + 1]);
3580
3683
  index += 1;
@@ -3586,7 +3689,7 @@ function parseDbQueryArgs(args) {
3586
3689
  }
3587
3690
  sqlSource = {
3588
3691
  kind: "inline",
3589
- value: parseDbFlagValue(parsed.remaining[index + 1], "--sql"),
3692
+ value: parseDbSqlFlagValue(parsed.remaining[index + 1]),
3590
3693
  };
3591
3694
  index += 1;
3592
3695
  continue;
@@ -3612,10 +3715,18 @@ function parseDbQueryArgs(args) {
3612
3715
  index += 1;
3613
3716
  continue;
3614
3717
  }
3718
+ if (arg === "--offset") {
3719
+ offset = parseNonNegativeIntegerFlag(parsed.remaining[index + 1], "--offset");
3720
+ index += 1;
3721
+ continue;
3722
+ }
3615
3723
  throw new TenderCliUsageError(`Unknown app db query option: ${arg}`);
3616
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
+ }
3617
3728
  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");
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");
3619
3730
  }
3620
3731
  if (params.length > DB_MAX_BOUND_PARAMETERS) {
3621
3732
  throw new TenderCliUsageError(`App database queries are limited to ${DB_MAX_BOUND_PARAMETERS} bound parameters.`);
@@ -3627,6 +3738,7 @@ function parseDbQueryArgs(args) {
3627
3738
  sqlSource,
3628
3739
  params,
3629
3740
  limit,
3741
+ offset,
3630
3742
  ...parsed,
3631
3743
  };
3632
3744
  }
@@ -4192,12 +4304,16 @@ async function requestAnalyticsApi(input) {
4192
4304
  const message = (typeof payload?.message === "string" && payload.message) ||
4193
4305
  (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4194
4306
  `Analytics request failed with HTTP ${response.status}.`;
4307
+ const example = analyticsCorrectedExample(input.artifactId, input.analyticsPath);
4195
4308
  throw new TenderCliAnalyticsApiError([
4196
4309
  message,
4197
4310
  `Scope: artifact ${input.artifactId}, analytics/${input.analyticsPath || "summary"}.`,
4198
4311
  `Reason: ${reasonCode}.`,
4199
- `Example: ${analyticsCorrectedExample(input.artifactId, input.analyticsPath)}.`,
4200
- ].join("\n"), reasonCode);
4312
+ `Example: ${example}.`,
4313
+ ].join("\n"), reasonCode, {
4314
+ artifactId: input.artifactId,
4315
+ surface: `analytics/${input.analyticsPath || "summary"}`,
4316
+ }, example);
4201
4317
  }
4202
4318
  return payload;
4203
4319
  }
@@ -4229,12 +4345,16 @@ async function requestDbApi(input) {
4229
4345
  const message = (typeof payload?.message === "string" && payload.message) ||
4230
4346
  (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
4231
4347
  `App database request failed with HTTP ${response.status}.`;
4348
+ const example = dbCorrectedExample(input.artifactId, input.dbPath);
4232
4349
  throw new TenderCliDbApiError([
4233
4350
  message,
4234
4351
  `Scope: artifact ${input.artifactId}, db/${input.dbPath}.`,
4235
4352
  `Reason: ${reasonCode}.`,
4236
- `Example: ${dbCorrectedExample(input.artifactId, input.dbPath)}.`,
4237
- ].join("\n"), reasonCode);
4353
+ `Example: ${example}.`,
4354
+ ].join("\n"), reasonCode, {
4355
+ artifactId: input.artifactId,
4356
+ surface: `db/${input.dbPath}`,
4357
+ }, example);
4238
4358
  }
4239
4359
  return payload;
4240
4360
  }
@@ -4243,12 +4363,12 @@ function dbCorrectedExample(artifactId, dbPath) {
4243
4363
  return `tender app db capabilities ${artifactId} --json`;
4244
4364
  }
4245
4365
  if (dbPath === "tables") {
4246
- return `tender app db tables ${artifactId} --json`;
4366
+ return `tender app db tables ${artifactId} --env published --json`;
4247
4367
  }
4248
4368
  if (dbPath === "schema") {
4249
- return `tender app db schema ${artifactId} --table <table-name> --json`;
4369
+ return `tender app db schema ${artifactId} --env published --table <table-name> --json`;
4250
4370
  }
4251
- return `tender app db query ${artifactId} --sql "select * from <table-name> limit 20" --json`;
4371
+ return `tender app db query ${artifactId} --env published --sql "select * from <table-name> limit 20" --json`;
4252
4372
  }
4253
4373
  function analyticsCorrectedExample(artifactId, analyticsPath) {
4254
4374
  if (analyticsPath === "capabilities") {
@@ -4497,7 +4617,7 @@ async function requestAnalyticsQueryWithPropertyGuidance(input) {
4497
4617
  spec: input.spec,
4498
4618
  });
4499
4619
  if (guidance) {
4500
- 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);
4501
4621
  }
4502
4622
  }
4503
4623
  throw error;
@@ -6641,6 +6761,7 @@ async function runDbQuery(command, io, runtime) {
6641
6761
  sql,
6642
6762
  params: command.params,
6643
6763
  ...(command.limit === null ? {} : { limit: command.limit }),
6764
+ offset: command.offset,
6644
6765
  };
6645
6766
  const payload = await requestDbApi({
6646
6767
  artifactId: command.artifactId,
@@ -6656,7 +6777,12 @@ async function runDbQuery(command, io, runtime) {
6656
6777
  `app_id: ${command.artifactId}`,
6657
6778
  `environment: ${command.environment}`,
6658
6779
  `rows: ${rows.length}`,
6780
+ `offset: ${String(payload.offset ?? command.offset)}`,
6781
+ `applied_limit: ${String(payload.appliedLimit ?? command.limit ?? "")}`,
6659
6782
  `truncated: ${String(payload.truncated ?? false)}`,
6783
+ ...(typeof payload.nextOffset === "number"
6784
+ ? [`next_offset: ${payload.nextOffset}`]
6785
+ : []),
6660
6786
  ]);
6661
6787
  return 0;
6662
6788
  }
@@ -7063,12 +7189,106 @@ async function runAnalyticsChartsDelete(command, io, runtime) {
7063
7189
  ]);
7064
7190
  return 0;
7065
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
+ }
7066
7285
  export async function runTenderCli(args, io = {
7067
7286
  stdout: (message) => console.log(message),
7068
7287
  stderr: (message) => console.error(message),
7069
7288
  }, runtime = {}) {
7289
+ let command = null;
7070
7290
  try {
7071
- const command = parseTenderArgs(args);
7291
+ command = parseTenderArgs(args);
7072
7292
  if (command.command === "help") {
7073
7293
  if (command.topic === "capabilities") {
7074
7294
  io.stdout(capabilitiesHelp());
@@ -7342,6 +7562,10 @@ export async function runTenderCli(args, io = {
7342
7562
  return await runDoctor(command, io);
7343
7563
  }
7344
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
+ }
7345
7569
  io.stderr(error instanceof Error ? error.message : String(error));
7346
7570
  io.stderr("Run `tender --help` for examples.");
7347
7571
  return error instanceof TenderCliUsageError ? 2 : 1;
@@ -7349,7 +7573,7 @@ export async function runTenderCli(args, io = {
7349
7573
  }
7350
7574
  const currentFile = fileURLToPath(import.meta.url);
7351
7575
  function hasFlagValue(args, flag, value) {
7352
- return args.some((arg, index) => arg === flag && args[index + 1] === value);
7576
+ return args.some((arg, index) => (arg === flag && args[index + 1] === value) || arg === `${flag}=${value}`);
7353
7577
  }
7354
7578
  function shouldReadEntrypointStdin(args) {
7355
7579
  return (args[0] === "app" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"