@tenderprompt/cli 0.1.16 → 0.1.18

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 +29 -0
  2. package/dist/index.js +904 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -39,6 +39,8 @@ session:
39
39
  - inspect artifact-scoped analytics without SQL or platform credentials
40
40
  - inspect artifact-scoped app database tables, schema, and bounded read-only
41
41
  query output
42
+ - share app setup between Tender accounts without copying customer data,
43
+ analytics history, app database rows, secrets, or release history
42
44
  - run server-validated chart specs and export aggregate rows
43
45
  - generate starter analytics suggestions
44
46
  - create, list, update, and delete saved analytics dashboards and charts, with
@@ -129,6 +131,9 @@ tender capabilities --json
129
131
  tender app list --json
130
132
  tender app create --name "Exit Intent Widget" --json
131
133
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
134
+ tender app share create artifact_123 --expires 7d --claims 1 --json
135
+ tender app share inspect tok_share_123 --json
136
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
132
137
  tender app update artifact_123 --name "Sale Widget" --json
133
138
  tender app init artifact_123 --dir ./widget --preview --json
134
139
  tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
@@ -145,6 +150,30 @@ tender app db query artifact_123 --env published --sql "select * from votes limi
145
150
  tender app db query artifact_123 --env published --sql "select * from votes order by created_at" --limit 50 --offset 50 --json
146
151
  ```
147
152
 
153
+ ## Shared app setup
154
+
155
+ Use this for agency-to-customer or collaborator handoffs where the recipient
156
+ should get the source and reusable setup, not the source account's data.
157
+
158
+ Agency side:
159
+
160
+ ```bash
161
+ tender app share create artifact_123 --expires 7d --claims 1 --json
162
+ ```
163
+
164
+ Recipient side:
165
+
166
+ ```bash
167
+ tender app share inspect tok_share_123 --json
168
+ tender app claim tok_share_123 --dir ./exit-intent-widget
169
+ tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json
170
+ ```
171
+
172
+ Claim copies source code, app setup, and analytics dashboard/chart definitions.
173
+ It does not copy analytics events, aggregates, exports, app database rows,
174
+ secrets, environment values, release history, or ongoing access to the source
175
+ artifact.
176
+
148
177
  ## Analytics
149
178
 
150
179
  Analytics commands are artifact-scoped and go through Tender's server-side
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const CLI_PACKAGE_NAME = "@tenderprompt/cli";
11
11
  const UNKNOWN_CLI_VERSION = "0.0.0";
12
12
  const RECOMMENDED_TENDER_PROMPT_SKILL = {
13
13
  name: "tender-prompt",
14
- version: "0.1.2",
14
+ version: "0.1.4",
15
15
  source: "git@github.com:tenderprompt/skills.git",
16
16
  path: "skills/tender-prompt/SKILL.md",
17
17
  updateHint: "Refresh the tender-prompt skill from git@github.com:tenderprompt/skills.git when the local skill version is older.",
@@ -82,11 +82,19 @@ Commands:
82
82
  tender auth login --device
83
83
  Authorize this machine with a browser-approved device flow
84
84
  tender auth status Show whether a Tender API token is available
85
+ tender playbooks list List remote Tender Prompt playbook metadata
86
+ tender playbooks get <id> Fetch one remote playbook body on demand
87
+ tender playbooks file <id> --path <path>
88
+ Fetch one playbook reference file on demand
85
89
  tender app list List Tender App records visible to the current token
86
90
  tender app create --name <name>
87
91
  Create a new Tender App in the current account
88
92
  tender app update <app-id> --name <name>
89
93
  Update app metadata
94
+ tender app share create <app-id>
95
+ Create a claimable app setup share token
96
+ tender app claim <token>
97
+ Claim shared app setup into the current account
90
98
  tender app init <app-id>
91
99
  Bootstrap a checkout for local agent work
92
100
  tender app validate <app-id>
@@ -134,10 +142,15 @@ Examples:
134
142
  tender capabilities --json
135
143
  tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
136
144
  tender auth status --json
145
+ tender playbooks list --json
146
+ tender playbooks get recharge-bundle-builder --json
147
+ tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json
137
148
  tender app list --json
138
149
  tender app create --name "Exit Intent Widget" --json
139
150
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
140
151
  tender app update artifact_123 --name "Sale Widget" --json
152
+ tender app share create artifact_123 --expires 7d --claims 1 --json
153
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
141
154
  tender app init artifact_123 --dir ./widget --preview --json
142
155
  tender app validate artifact_123 --json
143
156
  tender app build artifact_123 --commit $(git rev-parse HEAD) --json
@@ -203,6 +216,49 @@ Examples:
203
216
  tender auth status
204
217
  tender auth status --profile publish --json`;
205
218
  }
219
+ function playbooksHelp() {
220
+ return `Usage: tender playbooks <command> [options]
221
+
222
+ Commands:
223
+ tender playbooks list
224
+ List remote playbook metadata only
225
+ tender playbooks get <playbook-id>
226
+ Fetch one playbook body and reference index
227
+ tender playbooks file <playbook-id> --path <path>
228
+ Fetch one playbook reference file
229
+
230
+ Examples:
231
+ tender playbooks list --json
232
+ tender playbooks get recharge-bundle-builder --json
233
+ tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json`;
234
+ }
235
+ function playbooksListHelp() {
236
+ return `Usage: tender playbooks list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
237
+
238
+ Lists compact metadata for all remote Tender Prompt playbooks. This command
239
+ does not write playbooks to disk.
240
+
241
+ Examples:
242
+ tender playbooks list --json`;
243
+ }
244
+ function playbooksGetHelp() {
245
+ return `Usage: tender playbooks get <playbook-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
246
+
247
+ Fetches one remote playbook body plus a reference-file index. This command
248
+ prints session-only context and does not write to the checkout.
249
+
250
+ Examples:
251
+ tender playbooks get recharge-bundle-builder --json`;
252
+ }
253
+ function playbooksFileHelp() {
254
+ return `Usage: tender playbooks file <playbook-id> --path <references/file.md> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
255
+
256
+ Fetches one playbook reference file. Paths are limited to references/*.md.
257
+ This command does not write playbooks to disk.
258
+
259
+ Examples:
260
+ tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json`;
261
+ }
206
262
  function appHelp() {
207
263
  return `Usage: tender app <command> [options]
208
264
 
@@ -212,6 +268,14 @@ Commands:
212
268
  Create a new Tender App, optionally initialize source, and preview
213
269
  tender app update <app-id> --name <name>
214
270
  Update app metadata
271
+ tender app share create <app-id>
272
+ Create a claimable app setup share token
273
+ tender app share inspect <token>
274
+ Inspect a shared app setup token without claiming it
275
+ tender app share revoke <share-id>
276
+ Revoke a shared app setup token
277
+ tender app claim <token>
278
+ Claim shared app setup into the current account
215
279
  tender app init <app-id>
216
280
  Bootstrap context files and the tender Git remote
217
281
  tender app validate <app-id>
@@ -281,6 +345,9 @@ Examples:
281
345
  tender app create --name "Exit Intent Widget" --json
282
346
  tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
283
347
  tender app update artifact_123 --name "Sale Widget" --json
348
+ tender app share create artifact_123 --expires 7d --claims 1 --json
349
+ tender app share inspect tok_share_123 --json
350
+ tender app claim tok_share_123 --dir ./widget --confirm source-and-setup --json
284
351
  tender app init artifact_123 --dir ./widget --preview --json
285
352
  tender app validate artifact_123 --json
286
353
  tender app build artifact_123 --commit $(git rev-parse HEAD) --json
@@ -316,6 +383,53 @@ Examples:
316
383
  tender app doctor --json
317
384
  tender app git remote artifact_123 --ttl 7d --json`;
318
385
  }
386
+ function appShareHelp() {
387
+ return `Usage: tender app share <command> [options]
388
+
389
+ Commands:
390
+ tender app share create <app-id> Create a claimable app setup token
391
+ tender app share inspect <token> Inspect token metadata before claiming
392
+ tender app share revoke <share-id> Revoke a share token
393
+
394
+ Examples:
395
+ tender app share create artifact_123 --expires 7d --claims 1 --json
396
+ tender app share inspect tok_share_123 --json
397
+ tender app share revoke app_setup_share_123 --json`;
398
+ }
399
+ function appShareCreateHelp() {
400
+ return `Usage: tender app share create <app-id> [--expires <7d|24h>] [--claims <n>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
401
+
402
+ Creates a claimable app setup token. The share includes source code, app setup,
403
+ and analytics dashboard/chart definitions. It does not include customer data,
404
+ analytics history, app database rows, secrets, or release history.
405
+
406
+ Examples:
407
+ tender app share create artifact_123 --expires 7d --claims 1 --json`;
408
+ }
409
+ function appShareInspectHelp() {
410
+ return `Usage: tender app share inspect <token> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
411
+
412
+ Examples:
413
+ tender app share inspect tok_share_123 --json`;
414
+ }
415
+ function appShareRevokeHelp() {
416
+ return `Usage: tender app share revoke <share-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
417
+
418
+ Examples:
419
+ tender app share revoke app_setup_share_123 --json`;
420
+ }
421
+ function appClaimHelp() {
422
+ return `Usage: tender app claim <token> --dir <path> [--name <name>] [--confirm source-and-setup] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
423
+
424
+ Claims shared app setup into the authenticated Tender account, creates a new app,
425
+ materializes source files into the local directory, and configures the Tender Git
426
+ remote. Without --confirm source-and-setup, the command only previews what will
427
+ happen.
428
+
429
+ Examples:
430
+ tender app claim tok_share_123 --dir ./exit-intent-widget
431
+ tender app claim tok_share_123 --dir ./exit-intent-widget --confirm source-and-setup --json`;
432
+ }
319
433
  function capabilitiesHelp() {
320
434
  return `Usage: tender capabilities [--json]
321
435
 
@@ -722,7 +836,7 @@ Options:
722
836
  --token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
723
837
  --profile <name> Local config profile name. Defaults to the saved default profile.
724
838
  --ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
725
- --json Emit stable JSON for coding agents.
839
+ --json Emit stable JSON for coding agents without printing Git passwords.
726
840
 
727
841
  Examples:
728
842
  tender app git remote artifact_123 --ttl 7d --json
@@ -748,11 +862,12 @@ Examples:
748
862
  tender app git setup artifact_123 --dir ./widget --dry-run --json`;
749
863
  }
750
864
  function gitCredentialHelp() {
751
- return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>]
865
+ return `Usage: tender app git credential <app-id> [get|store|erase] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--remote-url <url>] [--json]
752
866
 
753
867
  This command is intended for Git's credential.helper protocol. It prints a
754
- Tender API-token username/password pair only for matching get requests. Do not
755
- run it manually unless you are debugging Git credential-helper behavior.
868
+ username/password pair only for matching get requests. Do not run it manually
869
+ unless you are debugging Git credential-helper behavior.
870
+ JSON output reports whether a password is available without printing it.
756
871
 
757
872
  Examples:
758
873
  tender app git credential artifact_123 get
@@ -916,6 +1031,14 @@ function tenderCliCapabilities() {
916
1031
  latestRunner: "npm exec --yes @tenderprompt/cli@latest --",
917
1032
  },
918
1033
  recommendedSkill: RECOMMENDED_TENDER_PROMPT_SKILL,
1034
+ playbooks: {
1035
+ mode: "remote_progressive_disclosure",
1036
+ metadataCommand: "tender playbooks list --json",
1037
+ entryCommand: "tender playbooks get <playbook-id> --json",
1038
+ fileCommand: "tender playbooks file <playbook-id> --path <path> --json",
1039
+ persistence: "session_only",
1040
+ writesToCheckout: false,
1041
+ },
919
1042
  description: "Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.",
920
1043
  discovery: {
921
1044
  primary: "npm exec --yes @tenderprompt/cli@latest -- capabilities --json",
@@ -923,8 +1046,11 @@ function tenderCliCapabilities() {
923
1046
  "tender --version",
924
1047
  "tender --help",
925
1048
  "tender auth --help",
1049
+ "tender playbooks --help",
926
1050
  "tender app --help",
927
1051
  "tender app agent heartbeat --help",
1052
+ "tender app share --help",
1053
+ "tender app claim --help",
928
1054
  "tender app analytics --help",
929
1055
  "tender app db --help",
930
1056
  "tender app bindings outbound --help",
@@ -939,6 +1065,20 @@ function tenderCliCapabilities() {
939
1065
  "tender auth login --device --profile edit-preview --artifact <artifact-id> --ttl 7d --json",
940
1066
  ],
941
1067
  },
1068
+ {
1069
+ name: "playbook_discovery",
1070
+ when: "Before planning non-trivial Tender App or widget work.",
1071
+ commands: [
1072
+ "tender playbooks list --json",
1073
+ "tender playbooks get <playbook-id> --json",
1074
+ "tender playbooks file <playbook-id> --path references/<file>.md --json",
1075
+ ],
1076
+ notes: [
1077
+ "List returns metadata only; use it to decide whether a playbook is relevant.",
1078
+ "Fetch full playbooks and reference files only on demand.",
1079
+ "Do not write fetched playbook contents into the checkout or install them as skills.",
1080
+ ],
1081
+ },
942
1082
  {
943
1083
  name: "create_new_app_with_preview",
944
1084
  when: "When the user wants a new Tender App that is immediately visible.",
@@ -964,6 +1104,20 @@ function tenderCliCapabilities() {
964
1104
  "Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
965
1105
  ],
966
1106
  },
1107
+ {
1108
+ name: "share_app_setup_handoff",
1109
+ when: "When handing off source code and reusable app setup between Tender accounts without copying data.",
1110
+ commands: [
1111
+ "tender app share create <artifact-id> --expires 7d --claims 1 --json",
1112
+ "tender app share inspect <token> --json",
1113
+ "tender app claim <token> --dir <dir> --confirm source-and-setup --json",
1114
+ ],
1115
+ notes: [
1116
+ "Share app setup copies source files, app setup, and analytics dashboard/chart definitions.",
1117
+ "It never copies customer data, analytics history, app database rows, secrets, release history, or source account access.",
1118
+ "Run claim without --confirm first when the user wants a preview of what will happen.",
1119
+ ],
1120
+ },
967
1121
  {
968
1122
  name: "local_edit_preview_publish",
969
1123
  when: "When editing app source from a local checkout.",
@@ -1057,6 +1211,24 @@ function tenderCliCapabilities() {
1057
1211
  requiresAuth: false,
1058
1212
  writes: false,
1059
1213
  },
1214
+ {
1215
+ command: "tender playbooks list --json",
1216
+ purpose: "List remote Tender Prompt playbook metadata without fetching full bodies.",
1217
+ requiresAuth: true,
1218
+ writes: false,
1219
+ },
1220
+ {
1221
+ command: "tender playbooks get <playbook-id> --json",
1222
+ purpose: "Fetch one remote playbook body plus its reference index.",
1223
+ requiresAuth: true,
1224
+ writes: false,
1225
+ },
1226
+ {
1227
+ command: "tender playbooks file <playbook-id> --path references/<file>.md --json",
1228
+ purpose: "Fetch one playbook reference file on demand.",
1229
+ requiresAuth: true,
1230
+ writes: false,
1231
+ },
1060
1232
  {
1061
1233
  command: "tender app create --name <name> --init --dir <dir> --preview --json",
1062
1234
  purpose: "Create a new Tender App, initialize API-derived source in a local checkout, push it, and request a preview build.",
@@ -1071,6 +1243,24 @@ function tenderCliCapabilities() {
1071
1243
  writes: true,
1072
1244
  dryRun: true,
1073
1245
  },
1246
+ {
1247
+ command: "tender app share create <artifact-id> --expires 7d --claims 1 --json",
1248
+ purpose: "Create a claimable app setup token that includes source/setup definitions but no app data.",
1249
+ requiresAuth: true,
1250
+ writes: true,
1251
+ },
1252
+ {
1253
+ command: "tender app share inspect <token> --json",
1254
+ purpose: "Inspect a shared app setup token before claiming it.",
1255
+ requiresAuth: true,
1256
+ writes: false,
1257
+ },
1258
+ {
1259
+ command: "tender app claim <token> --dir <dir> --confirm source-and-setup --json",
1260
+ purpose: "Create a new account-owned app from shared source/setup and materialize its source locally.",
1261
+ requiresAuth: true,
1262
+ writes: true,
1263
+ },
1074
1264
  {
1075
1265
  command: "tender app agent heartbeat <artifact-id> --source codex --status working --summary <text> --json",
1076
1266
  purpose: "Send a concise workspace feedback update from a coding agent.",
@@ -2326,6 +2516,128 @@ function parseArtifactsUpdateArgs(args) {
2326
2516
  json,
2327
2517
  };
2328
2518
  }
2519
+ function parseShareCommonArgs(args, startIndex) {
2520
+ let baseUrl = null;
2521
+ let flagToken = null;
2522
+ let profileName = null;
2523
+ let json = false;
2524
+ const remaining = [];
2525
+ for (let index = startIndex; index < args.length; index += 1) {
2526
+ const arg = args[index];
2527
+ if (arg === "--json") {
2528
+ json = true;
2529
+ continue;
2530
+ }
2531
+ if (arg === "--base-url") {
2532
+ baseUrl = parseBaseUrlFlag(args, index);
2533
+ index += 1;
2534
+ continue;
2535
+ }
2536
+ if (arg === "--token") {
2537
+ flagToken = parseTokenFlag(args, index);
2538
+ index += 1;
2539
+ continue;
2540
+ }
2541
+ if (arg === "--profile") {
2542
+ profileName = parseProfileName(args[index + 1], "--profile");
2543
+ index += 1;
2544
+ continue;
2545
+ }
2546
+ remaining.push(arg);
2547
+ }
2548
+ return { baseUrl, flagToken, profileName, json, remaining };
2549
+ }
2550
+ function parseShareCreateArgs(args) {
2551
+ if (args.includes("--help") || args.includes("-h")) {
2552
+ return { command: "help", topic: "app share create" };
2553
+ }
2554
+ const artifactId = args[0];
2555
+ if (!artifactId || artifactId.startsWith("-")) {
2556
+ throw new TenderCliUsageError("app id is required. Example: tender app share create <app-id> --json");
2557
+ }
2558
+ const parsed = parseShareCommonArgs(args, 1);
2559
+ let expires = "7d";
2560
+ let claims = 1;
2561
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
2562
+ const arg = parsed.remaining[index];
2563
+ if (arg === "--expires") {
2564
+ expires = parseRequiredFlagValue(parsed.remaining[index + 1], "--expires");
2565
+ index += 1;
2566
+ continue;
2567
+ }
2568
+ if (arg === "--claims") {
2569
+ claims = parsePositiveIntegerFlag(parsed.remaining[index + 1], "--claims");
2570
+ index += 1;
2571
+ continue;
2572
+ }
2573
+ throw new TenderCliUsageError(`Unknown app share create option: ${arg}`);
2574
+ }
2575
+ return { command: "app share create", artifactId, expires, claims, ...parsed };
2576
+ }
2577
+ function parseShareInspectArgs(args) {
2578
+ if (args.includes("--help") || args.includes("-h")) {
2579
+ return { command: "help", topic: "app share inspect" };
2580
+ }
2581
+ const token = args[0];
2582
+ if (!token || token.startsWith("-")) {
2583
+ throw new TenderCliUsageError("share token is required. Example: tender app share inspect <token> --json");
2584
+ }
2585
+ const parsed = parseShareCommonArgs(args, 1);
2586
+ if (parsed.remaining.length > 0) {
2587
+ throw new TenderCliUsageError(`Unknown app share inspect option: ${parsed.remaining[0]}`);
2588
+ }
2589
+ return { command: "app share inspect", token, ...parsed };
2590
+ }
2591
+ function parseShareRevokeArgs(args) {
2592
+ if (args.includes("--help") || args.includes("-h")) {
2593
+ return { command: "help", topic: "app share revoke" };
2594
+ }
2595
+ const shareId = args[0];
2596
+ if (!shareId || shareId.startsWith("-")) {
2597
+ throw new TenderCliUsageError("share id is required. Example: tender app share revoke <share-id> --json");
2598
+ }
2599
+ const parsed = parseShareCommonArgs(args, 1);
2600
+ if (parsed.remaining.length > 0) {
2601
+ throw new TenderCliUsageError(`Unknown app share revoke option: ${parsed.remaining[0]}`);
2602
+ }
2603
+ return { command: "app share revoke", shareId, ...parsed };
2604
+ }
2605
+ function parseClaimArgs(args) {
2606
+ if (args.includes("--help") || args.includes("-h")) {
2607
+ return { command: "help", topic: "app claim" };
2608
+ }
2609
+ const token = args[0];
2610
+ if (!token || token.startsWith("-")) {
2611
+ throw new TenderCliUsageError("share token is required. Example: tender app claim <token> --dir ./widget");
2612
+ }
2613
+ const parsed = parseShareCommonArgs(args, 1);
2614
+ let dir = null;
2615
+ let name = null;
2616
+ let confirm = null;
2617
+ for (let index = 0; index < parsed.remaining.length; index += 1) {
2618
+ const arg = parsed.remaining[index];
2619
+ if (arg === "--dir") {
2620
+ dir = parseRequiredFlagValue(parsed.remaining[index + 1], "--dir");
2621
+ index += 1;
2622
+ continue;
2623
+ }
2624
+ if (arg === "--name") {
2625
+ name = parseRequiredFlagValue(parsed.remaining[index + 1], "--name");
2626
+ index += 1;
2627
+ continue;
2628
+ }
2629
+ if (arg === "--confirm") {
2630
+ confirm = parseRequiredFlagValue(parsed.remaining[index + 1], "--confirm");
2631
+ index += 1;
2632
+ continue;
2633
+ }
2634
+ throw new TenderCliUsageError(`Unknown app claim option: ${arg}`);
2635
+ }
2636
+ if (!dir) {
2637
+ throw new TenderCliUsageError("--dir is required. Example: tender app claim tok_share_123 --dir ./widget");
2638
+ }
2639
+ return { command: "app claim", token, dir, name, confirm, ...parsed };
2640
+ }
2329
2641
  function parseArtifactsInitArgs(args) {
2330
2642
  if (args.includes("--help") || args.includes("-h")) {
2331
2643
  return { command: "help", topic: "app init" };
@@ -2742,6 +3054,114 @@ function parseAuthStatusArgs(args) {
2742
3054
  json,
2743
3055
  };
2744
3056
  }
3057
+ function parsePlaybooksCommonArgs(args, startIndex) {
3058
+ let baseUrl = null;
3059
+ let flagToken = null;
3060
+ let profileName = null;
3061
+ let json = false;
3062
+ const extras = [];
3063
+ for (let index = startIndex; index < args.length; index += 1) {
3064
+ const arg = args[index];
3065
+ if (arg === "--json") {
3066
+ json = true;
3067
+ continue;
3068
+ }
3069
+ if (arg === "--base-url") {
3070
+ baseUrl = parseBaseUrlFlag(args, index);
3071
+ index += 1;
3072
+ continue;
3073
+ }
3074
+ if (arg === "--token") {
3075
+ flagToken = parseTokenFlag(args, index);
3076
+ index += 1;
3077
+ continue;
3078
+ }
3079
+ if (arg === "--profile") {
3080
+ profileName = parseProfileName(args[index + 1], "--profile");
3081
+ index += 1;
3082
+ continue;
3083
+ }
3084
+ extras.push(arg);
3085
+ }
3086
+ return { baseUrl, flagToken, profileName, json, extras };
3087
+ }
3088
+ function parsePlaybooksListArgs(args) {
3089
+ if (args.includes("--help") || args.includes("-h")) {
3090
+ return { command: "help", topic: "playbooks list" };
3091
+ }
3092
+ const parsed = parsePlaybooksCommonArgs(args, 0);
3093
+ if (parsed.extras.length > 0) {
3094
+ throw new TenderCliUsageError(`Unknown playbooks list option: ${parsed.extras[0]}`);
3095
+ }
3096
+ return {
3097
+ command: "playbooks list",
3098
+ baseUrl: parsed.baseUrl,
3099
+ flagToken: parsed.flagToken,
3100
+ profileName: parsed.profileName,
3101
+ json: parsed.json,
3102
+ };
3103
+ }
3104
+ function parsePlaybooksGetArgs(args) {
3105
+ if (args.includes("--help") || args.includes("-h")) {
3106
+ return { command: "help", topic: "playbooks get" };
3107
+ }
3108
+ const playbookId = args[0];
3109
+ if (!playbookId || playbookId.startsWith("-")) {
3110
+ throw new TenderCliUsageError("playbook id is required. Example: tender playbooks get recharge-bundle-builder --json");
3111
+ }
3112
+ const parsed = parsePlaybooksCommonArgs(args, 1);
3113
+ if (parsed.extras.length > 0) {
3114
+ throw new TenderCliUsageError(`Unknown playbooks get option: ${parsed.extras[0]}`);
3115
+ }
3116
+ return {
3117
+ command: "playbooks get",
3118
+ playbookId,
3119
+ baseUrl: parsed.baseUrl,
3120
+ flagToken: parsed.flagToken,
3121
+ profileName: parsed.profileName,
3122
+ json: parsed.json,
3123
+ };
3124
+ }
3125
+ function parsePlaybooksFileArgs(args) {
3126
+ if (args.includes("--help") || args.includes("-h")) {
3127
+ return { command: "help", topic: "playbooks file" };
3128
+ }
3129
+ const playbookId = args[0];
3130
+ if (!playbookId || playbookId.startsWith("-")) {
3131
+ throw new TenderCliUsageError("playbook id is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json");
3132
+ }
3133
+ let filePath = null;
3134
+ const rest = [];
3135
+ for (let index = 1; index < args.length; index += 1) {
3136
+ const arg = args[index];
3137
+ if (arg === "--path") {
3138
+ const value = args[index + 1];
3139
+ if (!value || value.startsWith("-")) {
3140
+ throw new TenderCliUsageError("--path requires a playbook reference path.");
3141
+ }
3142
+ filePath = value;
3143
+ index += 1;
3144
+ continue;
3145
+ }
3146
+ rest.push(arg);
3147
+ }
3148
+ const parsed = parsePlaybooksCommonArgs(rest, 0);
3149
+ if (parsed.extras.length > 0) {
3150
+ throw new TenderCliUsageError(`Unknown playbooks file option: ${parsed.extras[0]}`);
3151
+ }
3152
+ if (!filePath) {
3153
+ throw new TenderCliUsageError("--path is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json");
3154
+ }
3155
+ return {
3156
+ command: "playbooks file",
3157
+ playbookId,
3158
+ filePath,
3159
+ baseUrl: parsed.baseUrl,
3160
+ flagToken: parsed.flagToken,
3161
+ profileName: parsed.profileName,
3162
+ json: parsed.json,
3163
+ };
3164
+ }
2745
3165
  function parseGenerateEnvTypesArgs(args) {
2746
3166
  if (args.includes("--help") || args.includes("-h")) {
2747
3167
  return { command: "help", topic: "app generate-env-types" };
@@ -4193,6 +4613,21 @@ function parseTenderArgs(args) {
4193
4613
  }
4194
4614
  throw new TenderCliUsageError(`Unknown auth command: ${args[1]}`);
4195
4615
  }
4616
+ if (args[0] === "playbooks") {
4617
+ if (args[1] === undefined || args[1] === "--help" || args[1] === "-h") {
4618
+ return { command: "help", topic: "playbooks" };
4619
+ }
4620
+ if (args[1] === "list") {
4621
+ return parsePlaybooksListArgs(args.slice(2));
4622
+ }
4623
+ if (args[1] === "get") {
4624
+ return parsePlaybooksGetArgs(args.slice(2));
4625
+ }
4626
+ if (args[1] === "file") {
4627
+ return parsePlaybooksFileArgs(args.slice(2));
4628
+ }
4629
+ throw new TenderCliUsageError(`Unknown playbooks command: ${args[1]}`);
4630
+ }
4196
4631
  if (args[0] === "apps") {
4197
4632
  throw new TenderCliUsageError("Unknown command: apps. Use `tender app ...` for Tender App workflows.");
4198
4633
  }
@@ -4215,6 +4650,27 @@ function parseTenderArgs(args) {
4215
4650
  if (resourceArgs[1] === "update") {
4216
4651
  return parseArtifactsUpdateArgs(resourceArgs.slice(2));
4217
4652
  }
4653
+ if (resourceArgs[1] === "share" &&
4654
+ (resourceArgs[2] === undefined ||
4655
+ resourceArgs[2] === "--help" ||
4656
+ resourceArgs[2] === "-h")) {
4657
+ return { command: "help", topic: "app share" };
4658
+ }
4659
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "create") {
4660
+ return parseShareCreateArgs(resourceArgs.slice(3));
4661
+ }
4662
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "inspect") {
4663
+ return parseShareInspectArgs(resourceArgs.slice(3));
4664
+ }
4665
+ if (resourceArgs[1] === "share" && resourceArgs[2] === "revoke") {
4666
+ return parseShareRevokeArgs(resourceArgs.slice(3));
4667
+ }
4668
+ if (resourceArgs[1] === "share") {
4669
+ throw new TenderCliUsageError(`Unknown app share command: ${resourceArgs[2]}`);
4670
+ }
4671
+ if (resourceArgs[1] === "claim") {
4672
+ return parseClaimArgs(resourceArgs.slice(2));
4673
+ }
4218
4674
  if (resourceArgs[1] === "init") {
4219
4675
  return parseArtifactsInitArgs(resourceArgs.slice(2));
4220
4676
  }
@@ -4808,6 +5264,40 @@ async function requestDbApi(input) {
4808
5264
  }
4809
5265
  return payload;
4810
5266
  }
5267
+ function encodePathSegments(filePath) {
5268
+ return filePath.split("/").map(encodeURIComponent).join("/");
5269
+ }
5270
+ async function requestPlaybooksApi(input) {
5271
+ const suffix = input.playbooksPath === "list"
5272
+ ? ""
5273
+ : input.playbooksPath === "get"
5274
+ ? `/${encodeURIComponent(input.playbookId)}`
5275
+ : `/${encodeURIComponent(input.playbookId)}/files/${encodePathSegments(input.filePath)}`;
5276
+ const response = await input.fetcher(`${input.baseUrl}/api/v1/playbooks${suffix}`, {
5277
+ method: "GET",
5278
+ headers: {
5279
+ authorization: `Bearer ${input.token}`,
5280
+ accept: "application/json",
5281
+ },
5282
+ });
5283
+ const payload = await parseJsonResponse(response);
5284
+ if (!response.ok || !payload) {
5285
+ const reasonCode = (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
5286
+ `http_${response.status}`;
5287
+ const message = (typeof payload?.message === "string" && payload.message) ||
5288
+ (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
5289
+ `Playbooks request failed with HTTP ${response.status}.`;
5290
+ const authHint = response.status === 401
5291
+ ? "\nAuthenticate first: tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json"
5292
+ : "";
5293
+ throw new Error([
5294
+ message,
5295
+ `Reason: ${reasonCode}.`,
5296
+ "Example: tender playbooks list --profile agent --json.",
5297
+ ].join("\n") + authHint);
5298
+ }
5299
+ return payload;
5300
+ }
4811
5301
  function dbCorrectedExample(artifactId, dbPath) {
4812
5302
  if (dbPath === "capabilities") {
4813
5303
  return `tender app db capabilities ${artifactId} --json`;
@@ -5961,6 +6451,262 @@ async function runArtifactsUpdate(command, io, runtime) {
5961
6451
  }
5962
6452
  return 0;
5963
6453
  }
6454
+ async function runShareCreate(command, io, runtime) {
6455
+ const credentials = await resolveApiCredentials({
6456
+ baseUrl: command.baseUrl,
6457
+ flagToken: command.flagToken,
6458
+ profileName: command.profileName,
6459
+ env: runtime.env ?? process.env,
6460
+ });
6461
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/share`, {
6462
+ method: "POST",
6463
+ headers: {
6464
+ authorization: `Bearer ${credentials.token}`,
6465
+ "content-type": "application/json",
6466
+ },
6467
+ body: JSON.stringify({
6468
+ expiresIn: command.expires,
6469
+ claims: command.claims,
6470
+ }),
6471
+ });
6472
+ const payload = await parseJsonResponse(response);
6473
+ if (!response.ok || !payload?.ok || !payload.share || !payload.token) {
6474
+ throw new Error(payload?.message ??
6475
+ payload?.reasonCode ??
6476
+ `App setup share request failed with HTTP ${response.status}.`);
6477
+ }
6478
+ const result = {
6479
+ ok: true,
6480
+ command: command.command,
6481
+ baseUrl: credentials.baseUrl,
6482
+ authSource: credentials.source,
6483
+ profile: credentials.profileName,
6484
+ share: payload.share,
6485
+ token: payload.token,
6486
+ claimCommand: payload.claimCommand,
6487
+ };
6488
+ if (command.json) {
6489
+ io.stdout(JSON.stringify(result, null, 2));
6490
+ }
6491
+ else {
6492
+ io.stdout([
6493
+ `Created share token for "${payload.share.sourceAppName ?? command.artifactId}".`,
6494
+ "",
6495
+ "Includes:",
6496
+ "- Source code",
6497
+ "- App setup",
6498
+ "- Analytics dashboard and chart definitions",
6499
+ "",
6500
+ "Excludes:",
6501
+ "- Customer data",
6502
+ "- Analytics history",
6503
+ "- App database data",
6504
+ "- Secrets",
6505
+ "- Release history",
6506
+ "",
6507
+ "Share this command:",
6508
+ "",
6509
+ ` ${payload.claimCommand ?? `tender app claim ${payload.token} --dir .`}`,
6510
+ "",
6511
+ `Expires: ${payload.share.expiresAt ?? ""}`,
6512
+ `Claims remaining: ${payload.share.claimsRemaining ?? command.claims}`,
6513
+ ].join("\n"));
6514
+ }
6515
+ return 0;
6516
+ }
6517
+ async function runShareInspect(command, io, runtime) {
6518
+ const credentials = await resolveApiCredentials({
6519
+ baseUrl: command.baseUrl,
6520
+ flagToken: command.flagToken,
6521
+ profileName: command.profileName,
6522
+ env: runtime.env ?? process.env,
6523
+ });
6524
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}`, {
6525
+ headers: {
6526
+ authorization: `Bearer ${credentials.token}`,
6527
+ },
6528
+ });
6529
+ const payload = await parseJsonResponse(response);
6530
+ if (!response.ok || !payload?.ok || !payload.share) {
6531
+ throw new Error(payload?.message ??
6532
+ payload?.reasonCode ??
6533
+ `App setup share inspect failed with HTTP ${response.status}.`);
6534
+ }
6535
+ if (command.json) {
6536
+ io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
6537
+ }
6538
+ else {
6539
+ io.stdout([
6540
+ `App: ${payload.share.sourceAppName ?? payload.share.sourceArtifactId ?? "Shared app"}`,
6541
+ `Expires: ${payload.share.expiresAt ?? ""}`,
6542
+ `Claims remaining: ${payload.share.claimsRemaining ?? 0}`,
6543
+ `Claimable: ${payload.claimable ? "yes" : "no"}`,
6544
+ ].join("\n"));
6545
+ }
6546
+ return 0;
6547
+ }
6548
+ async function runShareRevoke(command, io, runtime) {
6549
+ const credentials = await resolveApiCredentials({
6550
+ baseUrl: command.baseUrl,
6551
+ flagToken: command.flagToken,
6552
+ profileName: command.profileName,
6553
+ env: runtime.env ?? process.env,
6554
+ });
6555
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.shareId)}/revoke`, {
6556
+ method: "POST",
6557
+ headers: {
6558
+ authorization: `Bearer ${credentials.token}`,
6559
+ },
6560
+ });
6561
+ const payload = await parseJsonResponse(response);
6562
+ if (!response.ok || !payload?.ok || !payload.share) {
6563
+ throw new Error(payload?.message ??
6564
+ payload?.reasonCode ??
6565
+ `App setup share revoke failed with HTTP ${response.status}.`);
6566
+ }
6567
+ if (command.json) {
6568
+ io.stdout(JSON.stringify({ ok: true, command: command.command, share: payload.share }, null, 2));
6569
+ }
6570
+ else {
6571
+ io.stdout(`revoked: ${payload.share.id}`);
6572
+ }
6573
+ return 0;
6574
+ }
6575
+ async function runClaim(command, io, runtime) {
6576
+ const credentials = await resolveApiCredentials({
6577
+ baseUrl: command.baseUrl,
6578
+ flagToken: command.flagToken,
6579
+ profileName: command.profileName,
6580
+ env: runtime.env ?? process.env,
6581
+ });
6582
+ const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/app-shares/${encodeURIComponent(command.token)}/claim`, {
6583
+ method: "POST",
6584
+ headers: {
6585
+ authorization: `Bearer ${credentials.token}`,
6586
+ "content-type": "application/json",
6587
+ },
6588
+ body: JSON.stringify({
6589
+ name: command.name,
6590
+ confirm: command.confirm,
6591
+ }),
6592
+ });
6593
+ const payload = await parseJsonResponse(response);
6594
+ if (!response.ok || !payload?.ok) {
6595
+ throw new Error(payload?.message ??
6596
+ payload?.reasonCode ??
6597
+ `App setup claim failed with HTTP ${response.status}.`);
6598
+ }
6599
+ if (payload.requiresConfirmation) {
6600
+ if (command.json) {
6601
+ io.stdout(JSON.stringify({ command: command.command, ...payload }, null, 2));
6602
+ }
6603
+ else {
6604
+ io.stdout([
6605
+ `Claim "${payload.share?.sourceAppName ?? "shared app setup"}"?`,
6606
+ "",
6607
+ "This creates a new app owned by the current Tender account.",
6608
+ "",
6609
+ "Will copy:",
6610
+ "- Source code",
6611
+ "- App setup",
6612
+ "- Analytics dashboard and chart definitions",
6613
+ "",
6614
+ "Will not copy:",
6615
+ "- Customer data",
6616
+ "- Analytics history",
6617
+ "- App database data",
6618
+ "- Secrets",
6619
+ "- Release history",
6620
+ "",
6621
+ `Continue with: tender app claim ${command.token} --dir ${command.dir} --confirm source-and-setup`,
6622
+ ].join("\n"));
6623
+ }
6624
+ return 0;
6625
+ }
6626
+ if (!payload.artifact) {
6627
+ throw new Error("App setup claim response did not include a destination app.");
6628
+ }
6629
+ const root = path.resolve(command.dir);
6630
+ await mkdir(root, { recursive: true });
6631
+ const plannedFiles = await planSourceFiles({
6632
+ root,
6633
+ files: payload.sourceFiles ?? [],
6634
+ force: false,
6635
+ });
6636
+ const conflicts = plannedFiles.filter((file) => file.action === "conflict");
6637
+ if (conflicts.length > 0) {
6638
+ throw new TenderCliUsageError(`Claim target has ${conflicts.length} conflicting files. Choose an empty --dir or resolve: ${conflicts.map((file) => file.path).join(", ")}`);
6639
+ }
6640
+ for (const file of plannedFiles) {
6641
+ if (file.action !== "create" && file.action !== "overwrite") {
6642
+ continue;
6643
+ }
6644
+ await mkdir(path.dirname(file.absolutePath), { recursive: true });
6645
+ await writeFile(file.absolutePath, file.content);
6646
+ }
6647
+ const gitStdout = [];
6648
+ await runGitSetup({
6649
+ command: "app git setup",
6650
+ artifactId: payload.artifact.artifactId,
6651
+ dir: root,
6652
+ remoteName: "tender",
6653
+ baseUrl: credentials.baseUrl,
6654
+ flagToken: command.flagToken,
6655
+ profileName: credentials.profileName,
6656
+ ttl: "7d",
6657
+ dryRun: false,
6658
+ json: true,
6659
+ }, {
6660
+ stdout: (message) => gitStdout.push(message),
6661
+ stderr: io.stderr,
6662
+ }, runtime);
6663
+ const result = {
6664
+ ok: true,
6665
+ command: command.command,
6666
+ baseUrl: credentials.baseUrl,
6667
+ authSource: credentials.source,
6668
+ profile: credentials.profileName,
6669
+ appId: payload.artifact.artifactId,
6670
+ artifact: payload.artifact,
6671
+ dir: root,
6672
+ copied: payload.copied,
6673
+ notCopied: payload.notCopied,
6674
+ files: plannedFiles.map((file) => ({ path: file.path, action: file.action })),
6675
+ git: gitStdout.join("\n").trim() ? JSON.parse(gitStdout.join("\n")) : null,
6676
+ nextCommands: payload.nextCommands,
6677
+ };
6678
+ if (command.json) {
6679
+ io.stdout(JSON.stringify(result, null, 2));
6680
+ }
6681
+ else {
6682
+ io.stdout([
6683
+ "Claimed shared app setup.",
6684
+ "",
6685
+ "Created:",
6686
+ ` App: ${payload.artifact.name}`,
6687
+ ` ID: ${payload.artifact.artifactId}`,
6688
+ ` Local checkout: ${root}`,
6689
+ "",
6690
+ "Copied:",
6691
+ ` Source files: ${payload.copied?.sourceFiles ?? plannedFiles.length}`,
6692
+ ` Analytics dashboards: ${payload.copied?.analyticsDashboards ?? 0}`,
6693
+ ` Analytics charts: ${payload.copied?.analyticsCharts ?? 0}`,
6694
+ "",
6695
+ "Not copied:",
6696
+ " Usage data",
6697
+ " App database rows",
6698
+ " Secrets",
6699
+ " Release history",
6700
+ "",
6701
+ "Next:",
6702
+ ` cd ${command.dir}`,
6703
+ ` tender app doctor --dir .`,
6704
+ ` git push tender main`,
6705
+ ` tender app preview ${payload.artifact.artifactId} --stream`,
6706
+ ].join("\n"));
6707
+ }
6708
+ return 0;
6709
+ }
5964
6710
  function defaultCommandRunner(command, args, options) {
5965
6711
  return new Promise((resolve, reject) => {
5966
6712
  const child = spawn(command, args, {
@@ -6241,7 +6987,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
6241
6987
  };
6242
6988
  }
6243
6989
  async function requestGitRemoteLease(input) {
6244
- const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?ttl=${encodeURIComponent(input.ttl)}`;
6990
+ const params = new URLSearchParams({ ttl: input.ttl });
6991
+ if (input.includeCredential) {
6992
+ params.set("credential", "1");
6993
+ }
6994
+ const url = `${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/git/remote?${params.toString()}`;
6245
6995
  const response = await input.fetcher(url, {
6246
6996
  headers: {
6247
6997
  authorization: `Bearer ${input.token}`,
@@ -6404,28 +7154,41 @@ async function runGitCredential(command, io, runtime) {
6404
7154
  baseUrl: credentials.baseUrl,
6405
7155
  token: credentials.token,
6406
7156
  fetcher: runtime.fetcher ?? fetch,
7157
+ includeCredential: true,
6407
7158
  });
7159
+ const credentialPassword = git.token?.password ?? null;
7160
+ const credentialUsername = git.token?.username ?? git.authentication?.username ?? "tender";
7161
+ if (!credentialPassword) {
7162
+ if (command.json) {
7163
+ io.stdout(JSON.stringify({
7164
+ ok: false,
7165
+ command: command.command,
7166
+ artifactId: command.artifactId,
7167
+ remoteUrl: git.remoteUrl,
7168
+ reasonCode: "git_credential_unavailable",
7169
+ passwordAvailable: false,
7170
+ }, null, 2));
7171
+ }
7172
+ else {
7173
+ io.stderr("Tender Git credential is unavailable. Refresh the Git setup so the helper can request a scoped Git credential.");
7174
+ }
7175
+ return 1;
7176
+ }
6408
7177
  if (command.json) {
6409
7178
  io.stdout(JSON.stringify({
6410
7179
  ok: true,
6411
7180
  command: command.command,
6412
7181
  artifactId: command.artifactId,
6413
7182
  remoteUrl: git.remoteUrl,
6414
- username: git.token?.username ?? git.authentication?.username ?? "tender",
6415
- password: git.token?.password ??
6416
- (git.authentication?.mode === "tender-api-token"
6417
- ? credentials.token
6418
- : ""),
7183
+ username: credentialUsername,
7184
+ passwordAvailable: true,
6419
7185
  expiresAt: git.token?.expiresAt ?? null,
6420
7186
  }, null, 2));
6421
7187
  }
6422
7188
  else {
6423
7189
  io.stdout([
6424
- `username=${git.token?.username ?? git.authentication?.username ?? "tender"}`,
6425
- `password=${git.token?.password ??
6426
- (git.authentication?.mode === "tender-api-token"
6427
- ? credentials.token
6428
- : "")}`,
7190
+ `username=${credentialUsername}`,
7191
+ `password=${credentialPassword}`,
6429
7192
  ].join("\n"));
6430
7193
  }
6431
7194
  return 0;
@@ -7358,7 +8121,7 @@ async function runGitRemote(command, io, runtime) {
7358
8121
  scope: git.scope,
7359
8122
  authentication: git.authentication?.mode ?? "artifacts-token",
7360
8123
  username: git.token?.username ?? git.authentication?.username ?? "tender",
7361
- password: git.token?.password ?? null,
8124
+ passwordAvailable: Boolean(git.token?.password),
7362
8125
  expiresAt: git.token?.expiresAt ?? null,
7363
8126
  };
7364
8127
  if (command.json) {
@@ -7370,10 +8133,8 @@ async function runGitRemote(command, io, runtime) {
7370
8133
  `default_branch: ${result.defaultBranch}`,
7371
8134
  `authentication: ${result.authentication}`,
7372
8135
  `username: ${result.username}`,
8136
+ `password_available: ${result.passwordAvailable ? "true" : "false"}`,
7373
8137
  ];
7374
- if (result.password) {
7375
- lines.push(`password: ${result.password}`);
7376
- }
7377
8138
  if (result.expiresAt) {
7378
8139
  lines.push(`expires_at: ${result.expiresAt}`);
7379
8140
  }
@@ -7389,6 +8150,25 @@ async function resolveAnalyticsCredentials(command, runtime) {
7389
8150
  env: runtime.env ?? process.env,
7390
8151
  });
7391
8152
  }
8153
+ async function resolvePlaybooksCredentials(command, runtime) {
8154
+ try {
8155
+ return await resolveApiCredentials({
8156
+ baseUrl: command.baseUrl,
8157
+ flagToken: command.flagToken,
8158
+ profileName: command.profileName,
8159
+ env: runtime.env ?? process.env,
8160
+ });
8161
+ }
8162
+ catch (error) {
8163
+ if (error instanceof TenderCliUsageError) {
8164
+ throw new TenderCliUsageError([
8165
+ "Playbooks require Tender authentication.",
8166
+ "Run `tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json`, set TENDER_API_TOKEN, or pass --token <token>.",
8167
+ ].join("\n"));
8168
+ }
8169
+ throw error;
8170
+ }
8171
+ }
7392
8172
  async function resolveDbCredentials(command, runtime) {
7393
8173
  return await resolveApiCredentials({
7394
8174
  baseUrl: command.baseUrl,
@@ -7426,6 +8206,58 @@ function printDbPayload(command, io, payload, fallbackLines) {
7426
8206
  io.stdout(fallbackLines.join("\n"));
7427
8207
  }
7428
8208
  }
8209
+ function printPlaybooksPayload(command, io, payload, fallback) {
8210
+ if (command.json) {
8211
+ io.stdout(JSON.stringify(payload, null, 2));
8212
+ }
8213
+ else {
8214
+ io.stdout(fallback);
8215
+ }
8216
+ }
8217
+ async function runPlaybooksList(command, io, runtime) {
8218
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
8219
+ const payload = await requestPlaybooksApi({
8220
+ playbooksPath: "list",
8221
+ baseUrl: credentials.baseUrl,
8222
+ token: credentials.token,
8223
+ fetcher: runtime.fetcher ?? fetch,
8224
+ });
8225
+ const playbooks = Array.isArray(payload.playbooks) ? payload.playbooks : [];
8226
+ printPlaybooksPayload(command, io, payload, playbooks
8227
+ .map((playbook) => isCliJsonRecord(playbook)
8228
+ ? `${String(playbook.id ?? "")}\t${String(playbook.description ?? "")}`
8229
+ : "")
8230
+ .filter(Boolean)
8231
+ .join("\n") || "No Tender Prompt playbooks found.");
8232
+ return 0;
8233
+ }
8234
+ async function runPlaybooksGet(command, io, runtime) {
8235
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
8236
+ const payload = await requestPlaybooksApi({
8237
+ playbooksPath: "get",
8238
+ playbookId: command.playbookId,
8239
+ baseUrl: credentials.baseUrl,
8240
+ token: credentials.token,
8241
+ fetcher: runtime.fetcher ?? fetch,
8242
+ });
8243
+ printPlaybooksPayload(command, io, payload, typeof payload.body === "string" ? payload.body : JSON.stringify(payload, null, 2));
8244
+ return 0;
8245
+ }
8246
+ async function runPlaybooksFile(command, io, runtime) {
8247
+ const credentials = await resolvePlaybooksCredentials(command, runtime);
8248
+ const payload = await requestPlaybooksApi({
8249
+ playbooksPath: "file",
8250
+ playbookId: command.playbookId,
8251
+ filePath: command.filePath,
8252
+ baseUrl: credentials.baseUrl,
8253
+ token: credentials.token,
8254
+ fetcher: runtime.fetcher ?? fetch,
8255
+ });
8256
+ printPlaybooksPayload(command, io, payload, typeof payload.content === "string"
8257
+ ? payload.content
8258
+ : JSON.stringify(payload, null, 2));
8259
+ return 0;
8260
+ }
7429
8261
  async function runDbCapabilities(command, io, runtime) {
7430
8262
  const credentials = await resolveDbCredentials(command, runtime);
7431
8263
  const payload = await requestDbApi({
@@ -8042,6 +8874,18 @@ export async function runTenderCli(args, io = {
8042
8874
  else if (command.topic === "auth status") {
8043
8875
  io.stdout(authStatusHelp());
8044
8876
  }
8877
+ else if (command.topic === "playbooks") {
8878
+ io.stdout(playbooksHelp());
8879
+ }
8880
+ else if (command.topic === "playbooks list") {
8881
+ io.stdout(playbooksListHelp());
8882
+ }
8883
+ else if (command.topic === "playbooks get") {
8884
+ io.stdout(playbooksGetHelp());
8885
+ }
8886
+ else if (command.topic === "playbooks file") {
8887
+ io.stdout(playbooksFileHelp());
8888
+ }
8045
8889
  else if (command.topic === "auth") {
8046
8890
  io.stdout(authHelp());
8047
8891
  }
@@ -8051,6 +8895,21 @@ export async function runTenderCli(args, io = {
8051
8895
  else if (command.topic === "app update") {
8052
8896
  io.stdout(artifactsUpdateHelp());
8053
8897
  }
8898
+ else if (command.topic === "app share") {
8899
+ io.stdout(appShareHelp());
8900
+ }
8901
+ else if (command.topic === "app share create") {
8902
+ io.stdout(appShareCreateHelp());
8903
+ }
8904
+ else if (command.topic === "app share inspect") {
8905
+ io.stdout(appShareInspectHelp());
8906
+ }
8907
+ else if (command.topic === "app share revoke") {
8908
+ io.stdout(appShareRevokeHelp());
8909
+ }
8910
+ else if (command.topic === "app claim") {
8911
+ io.stdout(appClaimHelp());
8912
+ }
8054
8913
  else if (command.topic === "app init") {
8055
8914
  io.stdout(artifactsInitHelp());
8056
8915
  }
@@ -8194,6 +9053,15 @@ export async function runTenderCli(args, io = {
8194
9053
  if (command.command === "auth status") {
8195
9054
  return await runAuthStatus(command, io, runtime);
8196
9055
  }
9056
+ if (command.command === "playbooks list") {
9057
+ return await runPlaybooksList(command, io, runtime);
9058
+ }
9059
+ if (command.command === "playbooks get") {
9060
+ return await runPlaybooksGet(command, io, runtime);
9061
+ }
9062
+ if (command.command === "playbooks file") {
9063
+ return await runPlaybooksFile(command, io, runtime);
9064
+ }
8197
9065
  if (command.command === "app list") {
8198
9066
  return await runArtifactsList(command, io, runtime);
8199
9067
  }
@@ -8260,6 +9128,18 @@ export async function runTenderCli(args, io = {
8260
9128
  if (command.command === "app analytics query") {
8261
9129
  return await runAnalyticsQuery(command, io, runtime);
8262
9130
  }
9131
+ if (command.command === "app share create") {
9132
+ return await runShareCreate(command, io, runtime);
9133
+ }
9134
+ if (command.command === "app share inspect") {
9135
+ return await runShareInspect(command, io, runtime);
9136
+ }
9137
+ if (command.command === "app share revoke") {
9138
+ return await runShareRevoke(command, io, runtime);
9139
+ }
9140
+ if (command.command === "app claim") {
9141
+ return await runClaim(command, io, runtime);
9142
+ }
8263
9143
  if (command.command === "app analytics capabilities") {
8264
9144
  return await runAnalyticsCapabilities(command, io, runtime);
8265
9145
  }
@@ -8318,8 +9198,10 @@ export async function runTenderCli(args, io = {
8318
9198
  }
8319
9199
  catch (error) {
8320
9200
  if (args.includes("--json") &&
8321
- args[0] === "app" &&
8322
- (args[1] === "db" || (args[1] === "agent" && args[2] === "heartbeat"))) {
9201
+ ((args[0] === "app" &&
9202
+ (args[1] === "db" ||
9203
+ (args[1] === "agent" && args[2] === "heartbeat"))) ||
9204
+ args[0] === "playbooks")) {
8323
9205
  io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
8324
9206
  return error instanceof TenderCliUsageError ? 2 : 1;
8325
9207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"