@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.
- package/README.md +29 -0
- package/dist/index.js +904 -22
- 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.
|
|
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
|
-
|
|
755
|
-
|
|
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
|
|
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:
|
|
6415
|
-
|
|
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=${
|
|
6425
|
-
`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
|
-
|
|
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
|
-
|
|
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
|
}
|