@tenderprompt/cli 0.1.15 → 0.1.17
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 +4 -3
- package/dist/index.js +1043 -21
- package/package.json +1 -1
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,6 +82,10 @@ 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
|
|
@@ -134,10 +138,14 @@ Examples:
|
|
|
134
138
|
tender capabilities --json
|
|
135
139
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
136
140
|
tender auth status --json
|
|
141
|
+
tender playbooks list --json
|
|
142
|
+
tender playbooks get recharge-bundle-builder --json
|
|
143
|
+
tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json
|
|
137
144
|
tender app list --json
|
|
138
145
|
tender app create --name "Exit Intent Widget" --json
|
|
146
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
139
147
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
140
|
-
tender app init artifact_123 --dir ./widget --json
|
|
148
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
141
149
|
tender app validate artifact_123 --json
|
|
142
150
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
143
151
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
@@ -202,13 +210,56 @@ Examples:
|
|
|
202
210
|
tender auth status
|
|
203
211
|
tender auth status --profile publish --json`;
|
|
204
212
|
}
|
|
213
|
+
function playbooksHelp() {
|
|
214
|
+
return `Usage: tender playbooks <command> [options]
|
|
215
|
+
|
|
216
|
+
Commands:
|
|
217
|
+
tender playbooks list
|
|
218
|
+
List remote playbook metadata only
|
|
219
|
+
tender playbooks get <playbook-id>
|
|
220
|
+
Fetch one playbook body and reference index
|
|
221
|
+
tender playbooks file <playbook-id> --path <path>
|
|
222
|
+
Fetch one playbook reference file
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
tender playbooks list --json
|
|
226
|
+
tender playbooks get recharge-bundle-builder --json
|
|
227
|
+
tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json`;
|
|
228
|
+
}
|
|
229
|
+
function playbooksListHelp() {
|
|
230
|
+
return `Usage: tender playbooks list [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
231
|
+
|
|
232
|
+
Lists compact metadata for all remote Tender Prompt playbooks. This command
|
|
233
|
+
does not write playbooks to disk.
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
tender playbooks list --json`;
|
|
237
|
+
}
|
|
238
|
+
function playbooksGetHelp() {
|
|
239
|
+
return `Usage: tender playbooks get <playbook-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
240
|
+
|
|
241
|
+
Fetches one remote playbook body plus a reference-file index. This command
|
|
242
|
+
prints session-only context and does not write to the checkout.
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
tender playbooks get recharge-bundle-builder --json`;
|
|
246
|
+
}
|
|
247
|
+
function playbooksFileHelp() {
|
|
248
|
+
return `Usage: tender playbooks file <playbook-id> --path <references/file.md> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
249
|
+
|
|
250
|
+
Fetches one playbook reference file. Paths are limited to references/*.md.
|
|
251
|
+
This command does not write playbooks to disk.
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json`;
|
|
255
|
+
}
|
|
205
256
|
function appHelp() {
|
|
206
257
|
return `Usage: tender app <command> [options]
|
|
207
258
|
|
|
208
259
|
Commands:
|
|
209
260
|
tender app list List Tender App records visible to the current token
|
|
210
261
|
tender app create --name <name>
|
|
211
|
-
Create a new Tender App
|
|
262
|
+
Create a new Tender App, optionally initialize source, and preview
|
|
212
263
|
tender app update <app-id> --name <name>
|
|
213
264
|
Update app metadata
|
|
214
265
|
tender app init <app-id>
|
|
@@ -223,6 +274,8 @@ Commands:
|
|
|
223
274
|
Show preview job status by latest, commit, or job id
|
|
224
275
|
tender app preview watch <app-id>
|
|
225
276
|
Replay lifecycle events by latest, commit, or job id
|
|
277
|
+
tender app agent heartbeat <app-id>
|
|
278
|
+
Send a concise agent status update to the workspace
|
|
226
279
|
tender app publish <app-id>
|
|
227
280
|
Publish after dry-run or explicit confirmation
|
|
228
281
|
tender app publish status <app-id>
|
|
@@ -258,7 +311,7 @@ Commands:
|
|
|
258
311
|
tender app db query <app-id> --env <preview|published> --sql <sql>
|
|
259
312
|
Run one read-only app database query
|
|
260
313
|
tender app context fetch <app-id>
|
|
261
|
-
Fetch AGENTS.md, skills, and Tender
|
|
314
|
+
Fetch AGENTS.md, skills, and Tender project context
|
|
262
315
|
tender app context status <app-id>
|
|
263
316
|
Show managed context freshness for a checkout
|
|
264
317
|
tender app context refresh <app-id>
|
|
@@ -276,13 +329,15 @@ Commands:
|
|
|
276
329
|
Examples:
|
|
277
330
|
tender app list --json
|
|
278
331
|
tender app create --name "Exit Intent Widget" --json
|
|
332
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
279
333
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
280
|
-
tender app init artifact_123 --dir ./widget --json
|
|
334
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
281
335
|
tender app validate artifact_123 --json
|
|
282
336
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
283
337
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
284
338
|
tender app preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
285
339
|
tender app preview watch artifact_123 --job job_123 --stream --json
|
|
340
|
+
tender app agent heartbeat artifact_123 --source codex --status working --phase editing --summary "Updating the result screen" --json
|
|
286
341
|
tender app publish artifact_123 --dry-run --json
|
|
287
342
|
tender app publish status artifact_123 --job job_123 --json
|
|
288
343
|
tender app publish watch artifact_123 --job job_123 --stream --json
|
|
@@ -561,10 +616,18 @@ Examples:
|
|
|
561
616
|
TENDER_API_TOKEN=tpat_... tender app list --base-url https://app.tenderprompt.com --json`;
|
|
562
617
|
}
|
|
563
618
|
function artifactsCreateHelp() {
|
|
564
|
-
return `Usage: tender app create --name <name> [--target-pack <id>] [--target-pack-version <version>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
619
|
+
return `Usage: tender app create --name <name> [--init] [--dir <path>] [--scaffold <server-backed|none>] [--preview] [--target-pack <id>] [--target-pack-version <version>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
565
620
|
|
|
566
621
|
Options:
|
|
567
622
|
--name <name> App name.
|
|
623
|
+
--init Initialize a local checkout after creating the app.
|
|
624
|
+
--dir <path> Local checkout directory for --init. Defaults to current directory.
|
|
625
|
+
--scaffold <value> API-derived scaffold for empty app source: server-backed or none. Defaults to server-backed.
|
|
626
|
+
--no-scaffold Alias for --scaffold none.
|
|
627
|
+
--preview After --init writes and pushes initial source, request a preview build.
|
|
628
|
+
--remote <name> Git remote name for --init. Defaults to tender.
|
|
629
|
+
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
630
|
+
--force Overwrite conflicting managed files during --init.
|
|
568
631
|
--target-pack <id> Target pack id. Defaults to the API default.
|
|
569
632
|
--target-pack-version <value> Target pack version. Defaults to the API default.
|
|
570
633
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
@@ -574,6 +637,7 @@ Options:
|
|
|
574
637
|
|
|
575
638
|
Examples:
|
|
576
639
|
tender app create --name "Exit Intent Widget" --json
|
|
640
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
577
641
|
tender app create --name "Exit Intent Widget" --profile account --json`;
|
|
578
642
|
}
|
|
579
643
|
function artifactsUpdateHelp() {
|
|
@@ -590,11 +654,14 @@ Examples:
|
|
|
590
654
|
tender app update artifact_123 --name "Sale Widget" --json`;
|
|
591
655
|
}
|
|
592
656
|
function artifactsInitHelp() {
|
|
593
|
-
return `Usage: tender app init <app-id> [--dir <path>] [--remote <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--dry-run] [--force] [--json]
|
|
657
|
+
return `Usage: tender app init <app-id> [--dir <path>] [--remote <name>] [--scaffold <server-backed|none>] [--preview] [--base-url <url>] [--token <token>] [--profile <name>] [--ttl <7d|14d|30d>] [--dry-run] [--force] [--json]
|
|
594
658
|
|
|
595
659
|
Options:
|
|
596
660
|
--dir <path> Local checkout directory to create/update. Defaults to current directory.
|
|
597
661
|
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
662
|
+
--scaffold <value> API-derived scaffold for empty app source: server-backed or none. Defaults to server-backed.
|
|
663
|
+
--no-scaffold Alias for --scaffold none.
|
|
664
|
+
--preview Request a preview build for the initialized source commit.
|
|
598
665
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
599
666
|
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
600
667
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
@@ -606,6 +673,7 @@ Options:
|
|
|
606
673
|
Examples:
|
|
607
674
|
tender app init artifact_123 --dir ./widget --dry-run --json
|
|
608
675
|
tender app init artifact_123 --dir ./widget --json
|
|
676
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
609
677
|
tender app init artifact_123 --dir ./widget --force --json`;
|
|
610
678
|
}
|
|
611
679
|
function artifactsContextFetchHelp() {
|
|
@@ -833,6 +901,34 @@ Examples:
|
|
|
833
901
|
tender app publish artifact_123 --dry-run --json
|
|
834
902
|
tender app publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json`;
|
|
835
903
|
}
|
|
904
|
+
function agentHeartbeatHelp() {
|
|
905
|
+
return `Usage: tender app agent heartbeat <app-id> --source <codex|claude-code|tender-cli|git|other> --status <connected|working|waiting_for_user|needs_attention> --summary <text> [options]
|
|
906
|
+
|
|
907
|
+
Options:
|
|
908
|
+
--source <value> Agent identity. Use codex first, claude-code for Claude Code, or other with --source-label.
|
|
909
|
+
--source-label <text> Optional display name when --source other is not specific enough.
|
|
910
|
+
--status <value> connected, working, waiting_for_user, or needs_attention.
|
|
911
|
+
--phase <value> planning, exploring, editing, pushing, validating, building_preview, preview_updating, watching_preview, or waiting.
|
|
912
|
+
--summary <text> Short status line shown in the workspace.
|
|
913
|
+
--detail <text> Optional detail shown in activity history.
|
|
914
|
+
--current-task <text> Optional current task label.
|
|
915
|
+
--commit <sha> Commit SHA related to this update.
|
|
916
|
+
--preview-job <job-id> Preview lifecycle job related to this update.
|
|
917
|
+
--ttl-seconds <seconds> Freshness window. The API clamps this to a bounded range.
|
|
918
|
+
--requested-action <value> none, answer_question, review_preview, or approve_publish. Defaults to none.
|
|
919
|
+
--interruptible Mark the agent as interruptible.
|
|
920
|
+
--input <file|-> Read additional heartbeat JSON fields from a file or stdin. Flags override JSON fields.
|
|
921
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
922
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
923
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
924
|
+
--json Emit stable JSON for coding agents.
|
|
925
|
+
|
|
926
|
+
Examples:
|
|
927
|
+
tender app agent heartbeat artifact_123 --source codex --status connected --summary "Loaded the Tender Prompt skill" --json
|
|
928
|
+
tender app agent heartbeat artifact_123 --source codex --status working --phase exploring --summary "Reading the app structure" --json
|
|
929
|
+
tender app agent heartbeat artifact_123 --source claude-code --status needs_attention --summary "Waiting for an API key decision" --requested-action answer_question --json
|
|
930
|
+
tender app agent heartbeat artifact_123 --input - --json < heartbeat.json`;
|
|
931
|
+
}
|
|
836
932
|
function publishStatusHelp() {
|
|
837
933
|
return `Usage: tender app publish status <app-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
838
934
|
|
|
@@ -870,6 +966,14 @@ function tenderCliCapabilities() {
|
|
|
870
966
|
latestRunner: "npm exec --yes @tenderprompt/cli@latest --",
|
|
871
967
|
},
|
|
872
968
|
recommendedSkill: RECOMMENDED_TENDER_PROMPT_SKILL,
|
|
969
|
+
playbooks: {
|
|
970
|
+
mode: "remote_progressive_disclosure",
|
|
971
|
+
metadataCommand: "tender playbooks list --json",
|
|
972
|
+
entryCommand: "tender playbooks get <playbook-id> --json",
|
|
973
|
+
fileCommand: "tender playbooks file <playbook-id> --path <path> --json",
|
|
974
|
+
persistence: "session_only",
|
|
975
|
+
writesToCheckout: false,
|
|
976
|
+
},
|
|
873
977
|
description: "Agent-safe Tender App source, lifecycle, analytics, and local scaffold controls.",
|
|
874
978
|
discovery: {
|
|
875
979
|
primary: "npm exec --yes @tenderprompt/cli@latest -- capabilities --json",
|
|
@@ -877,7 +981,9 @@ function tenderCliCapabilities() {
|
|
|
877
981
|
"tender --version",
|
|
878
982
|
"tender --help",
|
|
879
983
|
"tender auth --help",
|
|
984
|
+
"tender playbooks --help",
|
|
880
985
|
"tender app --help",
|
|
986
|
+
"tender app agent heartbeat --help",
|
|
881
987
|
"tender app analytics --help",
|
|
882
988
|
"tender app db --help",
|
|
883
989
|
"tender app bindings outbound --help",
|
|
@@ -892,17 +998,42 @@ function tenderCliCapabilities() {
|
|
|
892
998
|
"tender auth login --device --profile edit-preview --artifact <artifact-id> --ttl 7d --json",
|
|
893
999
|
],
|
|
894
1000
|
},
|
|
1001
|
+
{
|
|
1002
|
+
name: "playbook_discovery",
|
|
1003
|
+
when: "Before planning non-trivial Tender App or widget work.",
|
|
1004
|
+
commands: [
|
|
1005
|
+
"tender playbooks list --json",
|
|
1006
|
+
"tender playbooks get <playbook-id> --json",
|
|
1007
|
+
"tender playbooks file <playbook-id> --path references/<file>.md --json",
|
|
1008
|
+
],
|
|
1009
|
+
notes: [
|
|
1010
|
+
"List returns metadata only; use it to decide whether a playbook is relevant.",
|
|
1011
|
+
"Fetch full playbooks and reference files only on demand.",
|
|
1012
|
+
"Do not write fetched playbook contents into the checkout or install them as skills.",
|
|
1013
|
+
],
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: "create_new_app_with_preview",
|
|
1017
|
+
when: "When the user wants a new Tender App that is immediately visible.",
|
|
1018
|
+
commands: [
|
|
1019
|
+
"tender app create --name <name> --init --dir <dir> --preview --json",
|
|
1020
|
+
],
|
|
1021
|
+
notes: [
|
|
1022
|
+
"create --init uses the API-derived scaffold when the new app has no source.",
|
|
1023
|
+
"--preview requests a preview build for the initial source commit after the push.",
|
|
1024
|
+
],
|
|
1025
|
+
},
|
|
895
1026
|
{
|
|
896
1027
|
name: "clone_or_bootstrap_existing_app",
|
|
897
1028
|
when: "When the user wants to download or work on an existing app locally.",
|
|
898
1029
|
commands: [
|
|
899
1030
|
"tender app init <artifact-id> --dir <dir> --dry-run --json",
|
|
900
|
-
"tender app init <artifact-id> --dir <dir> --json",
|
|
1031
|
+
"tender app init <artifact-id> --dir <dir> --preview --json",
|
|
901
1032
|
],
|
|
902
1033
|
notes: [
|
|
903
1034
|
"init is the normal clone/bootstrap command.",
|
|
904
1035
|
"If the app already has artifact Git source, init fetches it and configures the tender remote.",
|
|
905
|
-
"If no artifact Git source exists yet, init writes managed context
|
|
1036
|
+
"If no artifact Git source or workspace source exists yet, init fetches the API-derived scaffold, writes managed context files, and configures the tender remote.",
|
|
906
1037
|
"Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
|
|
907
1038
|
],
|
|
908
1039
|
},
|
|
@@ -910,14 +1041,18 @@ function tenderCliCapabilities() {
|
|
|
910
1041
|
name: "local_edit_preview_publish",
|
|
911
1042
|
when: "When editing app source from a local checkout.",
|
|
912
1043
|
commands: [
|
|
1044
|
+
"tender app agent heartbeat <artifact-id> --source codex --status connected --summary \"Loaded Tender Prompt context\" --json",
|
|
913
1045
|
"tender app doctor --dir <dir> --json",
|
|
1046
|
+
"tender app agent heartbeat <artifact-id> --source codex --status working --phase editing --summary \"Editing app files\" --json",
|
|
914
1047
|
"tender app git setup <artifact-id> --dir <dir> --dry-run --json",
|
|
915
1048
|
"git push tender main",
|
|
1049
|
+
"tender app agent heartbeat <artifact-id> --source codex --status working --phase building_preview --summary \"Waiting for preview build\" --commit $(git rev-parse HEAD) --json",
|
|
916
1050
|
"tender app preview <artifact-id> --commit $(git rev-parse HEAD) --stream --json",
|
|
917
1051
|
"tender app publish <artifact-id> --dry-run --json",
|
|
918
1052
|
"tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json",
|
|
919
1053
|
],
|
|
920
1054
|
notes: [
|
|
1055
|
+
"Send heartbeat updates only at meaningful milestones or when waiting for user input; do not send rapid polling updates.",
|
|
921
1056
|
"Publishing is production-facing and requires explicit user intent.",
|
|
922
1057
|
],
|
|
923
1058
|
},
|
|
@@ -996,12 +1131,43 @@ function tenderCliCapabilities() {
|
|
|
996
1131
|
writes: false,
|
|
997
1132
|
},
|
|
998
1133
|
{
|
|
999
|
-
command: "tender
|
|
1000
|
-
purpose: "
|
|
1134
|
+
command: "tender playbooks list --json",
|
|
1135
|
+
purpose: "List remote Tender Prompt playbook metadata without fetching full bodies.",
|
|
1136
|
+
requiresAuth: true,
|
|
1137
|
+
writes: false,
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
command: "tender playbooks get <playbook-id> --json",
|
|
1141
|
+
purpose: "Fetch one remote playbook body plus its reference index.",
|
|
1142
|
+
requiresAuth: true,
|
|
1143
|
+
writes: false,
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
command: "tender playbooks file <playbook-id> --path references/<file>.md --json",
|
|
1147
|
+
purpose: "Fetch one playbook reference file on demand.",
|
|
1148
|
+
requiresAuth: true,
|
|
1149
|
+
writes: false,
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
command: "tender app create --name <name> --init --dir <dir> --preview --json",
|
|
1153
|
+
purpose: "Create a new Tender App, initialize API-derived source in a local checkout, push it, and request a preview build.",
|
|
1154
|
+
requiresAuth: true,
|
|
1155
|
+
writes: true,
|
|
1156
|
+
dryRun: false,
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
command: "tender app init <artifact-id> --dir <dir> --preview --json",
|
|
1160
|
+
purpose: "Clone/bootstrap an existing Tender App checkout, configure context plus the tender Git remote, and optionally request a preview for the initialized commit.",
|
|
1001
1161
|
requiresAuth: true,
|
|
1002
1162
|
writes: true,
|
|
1003
1163
|
dryRun: true,
|
|
1004
1164
|
},
|
|
1165
|
+
{
|
|
1166
|
+
command: "tender app agent heartbeat <artifact-id> --source codex --status working --summary <text> --json",
|
|
1167
|
+
purpose: "Send a concise workspace feedback update from a coding agent.",
|
|
1168
|
+
requiresAuth: true,
|
|
1169
|
+
writes: true,
|
|
1170
|
+
},
|
|
1005
1171
|
{
|
|
1006
1172
|
command: "tender app context fetch <artifact-id> --dir <dir> --json",
|
|
1007
1173
|
purpose: "Fetch managed AGENTS.md, skills, and Tender context into an existing checkout.",
|
|
@@ -1285,6 +1451,12 @@ function parseTtl(value) {
|
|
|
1285
1451
|
}
|
|
1286
1452
|
return value;
|
|
1287
1453
|
}
|
|
1454
|
+
function parseScaffoldTemplate(value) {
|
|
1455
|
+
if (value !== "server-backed" && value !== "none") {
|
|
1456
|
+
throw new TenderCliUsageError("--scaffold must be one of server-backed or none.");
|
|
1457
|
+
}
|
|
1458
|
+
return value;
|
|
1459
|
+
}
|
|
1288
1460
|
function parseGitRemoteArgs(args) {
|
|
1289
1461
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1290
1462
|
return { command: "help", topic: "app git remote" };
|
|
@@ -2045,6 +2217,13 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2045
2217
|
let name = "";
|
|
2046
2218
|
let targetPackId = null;
|
|
2047
2219
|
let targetPackVersion = null;
|
|
2220
|
+
let init = false;
|
|
2221
|
+
let dir = ".";
|
|
2222
|
+
let remoteName = "tender";
|
|
2223
|
+
let ttl = "7d";
|
|
2224
|
+
let force = false;
|
|
2225
|
+
let scaffold = "server-backed";
|
|
2226
|
+
let preview = false;
|
|
2048
2227
|
let baseUrl = null;
|
|
2049
2228
|
let flagToken = null;
|
|
2050
2229
|
let profileName = null;
|
|
@@ -2055,6 +2234,22 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2055
2234
|
json = true;
|
|
2056
2235
|
continue;
|
|
2057
2236
|
}
|
|
2237
|
+
if (arg === "--init") {
|
|
2238
|
+
init = true;
|
|
2239
|
+
continue;
|
|
2240
|
+
}
|
|
2241
|
+
if (arg === "--preview") {
|
|
2242
|
+
preview = true;
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
if (arg === "--force") {
|
|
2246
|
+
force = true;
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
if (arg === "--no-scaffold") {
|
|
2250
|
+
scaffold = "none";
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2058
2253
|
if (arg === "--name") {
|
|
2059
2254
|
const value = args[index + 1];
|
|
2060
2255
|
if (!value || value.startsWith("-")) {
|
|
@@ -2082,6 +2277,30 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2082
2277
|
index += 1;
|
|
2083
2278
|
continue;
|
|
2084
2279
|
}
|
|
2280
|
+
if (arg === "--dir") {
|
|
2281
|
+
const value = args[index + 1];
|
|
2282
|
+
if (!value || value.startsWith("-")) {
|
|
2283
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
2284
|
+
}
|
|
2285
|
+
dir = value;
|
|
2286
|
+
index += 1;
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
if (arg === "--remote") {
|
|
2290
|
+
remoteName = parseRemoteName(args[index + 1]);
|
|
2291
|
+
index += 1;
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
if (arg === "--ttl") {
|
|
2295
|
+
ttl = parseTtl(args[index + 1]);
|
|
2296
|
+
index += 1;
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
if (arg === "--scaffold") {
|
|
2300
|
+
scaffold = parseScaffoldTemplate(args[index + 1]);
|
|
2301
|
+
index += 1;
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2085
2304
|
if (arg === "--base-url") {
|
|
2086
2305
|
baseUrl = parseBaseUrlFlag(args, index);
|
|
2087
2306
|
index += 1;
|
|
@@ -2102,11 +2321,38 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2102
2321
|
if (!name.trim()) {
|
|
2103
2322
|
throw new TenderCliUsageError("--name is required. Example: tender app create --name \"Exit Intent Widget\" --json");
|
|
2104
2323
|
}
|
|
2324
|
+
if (!init) {
|
|
2325
|
+
if (preview) {
|
|
2326
|
+
throw new TenderCliUsageError("--preview requires --init. Example: tender app create --name \"Exit Intent Widget\" --init --dir ./widget --preview --json");
|
|
2327
|
+
}
|
|
2328
|
+
if (force) {
|
|
2329
|
+
throw new TenderCliUsageError("--force is only valid with --init.");
|
|
2330
|
+
}
|
|
2331
|
+
if (dir !== ".") {
|
|
2332
|
+
throw new TenderCliUsageError("--dir is only valid with --init.");
|
|
2333
|
+
}
|
|
2334
|
+
if (remoteName !== "tender") {
|
|
2335
|
+
throw new TenderCliUsageError("--remote is only valid with --init.");
|
|
2336
|
+
}
|
|
2337
|
+
if (ttl !== "7d") {
|
|
2338
|
+
throw new TenderCliUsageError("--ttl is only valid with --init.");
|
|
2339
|
+
}
|
|
2340
|
+
if (scaffold !== "server-backed") {
|
|
2341
|
+
throw new TenderCliUsageError("--scaffold is only valid with --init.");
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2105
2344
|
return {
|
|
2106
2345
|
command: "app create",
|
|
2107
2346
|
name: name.trim(),
|
|
2108
2347
|
targetPackId,
|
|
2109
2348
|
targetPackVersion,
|
|
2349
|
+
init,
|
|
2350
|
+
dir,
|
|
2351
|
+
remoteName,
|
|
2352
|
+
ttl,
|
|
2353
|
+
force,
|
|
2354
|
+
scaffold,
|
|
2355
|
+
preview,
|
|
2110
2356
|
baseUrl,
|
|
2111
2357
|
flagToken,
|
|
2112
2358
|
profileName,
|
|
@@ -2185,6 +2431,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2185
2431
|
let flagToken = null;
|
|
2186
2432
|
let profileName = null;
|
|
2187
2433
|
let ttl = "7d";
|
|
2434
|
+
let scaffold = "server-backed";
|
|
2435
|
+
let preview = false;
|
|
2188
2436
|
let dryRun = false;
|
|
2189
2437
|
let force = false;
|
|
2190
2438
|
let json = false;
|
|
@@ -2202,6 +2450,14 @@ function parseArtifactsInitArgs(args) {
|
|
|
2202
2450
|
force = true;
|
|
2203
2451
|
continue;
|
|
2204
2452
|
}
|
|
2453
|
+
if (arg === "--preview") {
|
|
2454
|
+
preview = true;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
if (arg === "--no-scaffold") {
|
|
2458
|
+
scaffold = "none";
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2205
2461
|
if (arg === "--dir") {
|
|
2206
2462
|
const value = args[index + 1];
|
|
2207
2463
|
if (!value || value.startsWith("-")) {
|
|
@@ -2236,6 +2492,11 @@ function parseArtifactsInitArgs(args) {
|
|
|
2236
2492
|
index += 1;
|
|
2237
2493
|
continue;
|
|
2238
2494
|
}
|
|
2495
|
+
if (arg === "--scaffold") {
|
|
2496
|
+
scaffold = parseScaffoldTemplate(args[index + 1]);
|
|
2497
|
+
index += 1;
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2239
2500
|
throw new TenderCliUsageError(`Unknown app init option: ${arg}`);
|
|
2240
2501
|
}
|
|
2241
2502
|
return {
|
|
@@ -2247,6 +2508,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2247
2508
|
flagToken,
|
|
2248
2509
|
profileName,
|
|
2249
2510
|
ttl,
|
|
2511
|
+
scaffold,
|
|
2512
|
+
preview,
|
|
2250
2513
|
dryRun,
|
|
2251
2514
|
force,
|
|
2252
2515
|
json,
|
|
@@ -2570,6 +2833,114 @@ function parseAuthStatusArgs(args) {
|
|
|
2570
2833
|
json,
|
|
2571
2834
|
};
|
|
2572
2835
|
}
|
|
2836
|
+
function parsePlaybooksCommonArgs(args, startIndex) {
|
|
2837
|
+
let baseUrl = null;
|
|
2838
|
+
let flagToken = null;
|
|
2839
|
+
let profileName = null;
|
|
2840
|
+
let json = false;
|
|
2841
|
+
const extras = [];
|
|
2842
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
2843
|
+
const arg = args[index];
|
|
2844
|
+
if (arg === "--json") {
|
|
2845
|
+
json = true;
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
if (arg === "--base-url") {
|
|
2849
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
2850
|
+
index += 1;
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
if (arg === "--token") {
|
|
2854
|
+
flagToken = parseTokenFlag(args, index);
|
|
2855
|
+
index += 1;
|
|
2856
|
+
continue;
|
|
2857
|
+
}
|
|
2858
|
+
if (arg === "--profile") {
|
|
2859
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
2860
|
+
index += 1;
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2863
|
+
extras.push(arg);
|
|
2864
|
+
}
|
|
2865
|
+
return { baseUrl, flagToken, profileName, json, extras };
|
|
2866
|
+
}
|
|
2867
|
+
function parsePlaybooksListArgs(args) {
|
|
2868
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2869
|
+
return { command: "help", topic: "playbooks list" };
|
|
2870
|
+
}
|
|
2871
|
+
const parsed = parsePlaybooksCommonArgs(args, 0);
|
|
2872
|
+
if (parsed.extras.length > 0) {
|
|
2873
|
+
throw new TenderCliUsageError(`Unknown playbooks list option: ${parsed.extras[0]}`);
|
|
2874
|
+
}
|
|
2875
|
+
return {
|
|
2876
|
+
command: "playbooks list",
|
|
2877
|
+
baseUrl: parsed.baseUrl,
|
|
2878
|
+
flagToken: parsed.flagToken,
|
|
2879
|
+
profileName: parsed.profileName,
|
|
2880
|
+
json: parsed.json,
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function parsePlaybooksGetArgs(args) {
|
|
2884
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2885
|
+
return { command: "help", topic: "playbooks get" };
|
|
2886
|
+
}
|
|
2887
|
+
const playbookId = args[0];
|
|
2888
|
+
if (!playbookId || playbookId.startsWith("-")) {
|
|
2889
|
+
throw new TenderCliUsageError("playbook id is required. Example: tender playbooks get recharge-bundle-builder --json");
|
|
2890
|
+
}
|
|
2891
|
+
const parsed = parsePlaybooksCommonArgs(args, 1);
|
|
2892
|
+
if (parsed.extras.length > 0) {
|
|
2893
|
+
throw new TenderCliUsageError(`Unknown playbooks get option: ${parsed.extras[0]}`);
|
|
2894
|
+
}
|
|
2895
|
+
return {
|
|
2896
|
+
command: "playbooks get",
|
|
2897
|
+
playbookId,
|
|
2898
|
+
baseUrl: parsed.baseUrl,
|
|
2899
|
+
flagToken: parsed.flagToken,
|
|
2900
|
+
profileName: parsed.profileName,
|
|
2901
|
+
json: parsed.json,
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
function parsePlaybooksFileArgs(args) {
|
|
2905
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2906
|
+
return { command: "help", topic: "playbooks file" };
|
|
2907
|
+
}
|
|
2908
|
+
const playbookId = args[0];
|
|
2909
|
+
if (!playbookId || playbookId.startsWith("-")) {
|
|
2910
|
+
throw new TenderCliUsageError("playbook id is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json");
|
|
2911
|
+
}
|
|
2912
|
+
let filePath = null;
|
|
2913
|
+
const rest = [];
|
|
2914
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
2915
|
+
const arg = args[index];
|
|
2916
|
+
if (arg === "--path") {
|
|
2917
|
+
const value = args[index + 1];
|
|
2918
|
+
if (!value || value.startsWith("-")) {
|
|
2919
|
+
throw new TenderCliUsageError("--path requires a playbook reference path.");
|
|
2920
|
+
}
|
|
2921
|
+
filePath = value;
|
|
2922
|
+
index += 1;
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
rest.push(arg);
|
|
2926
|
+
}
|
|
2927
|
+
const parsed = parsePlaybooksCommonArgs(rest, 0);
|
|
2928
|
+
if (parsed.extras.length > 0) {
|
|
2929
|
+
throw new TenderCliUsageError(`Unknown playbooks file option: ${parsed.extras[0]}`);
|
|
2930
|
+
}
|
|
2931
|
+
if (!filePath) {
|
|
2932
|
+
throw new TenderCliUsageError("--path is required. Example: tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json");
|
|
2933
|
+
}
|
|
2934
|
+
return {
|
|
2935
|
+
command: "playbooks file",
|
|
2936
|
+
playbookId,
|
|
2937
|
+
filePath,
|
|
2938
|
+
baseUrl: parsed.baseUrl,
|
|
2939
|
+
flagToken: parsed.flagToken,
|
|
2940
|
+
profileName: parsed.profileName,
|
|
2941
|
+
json: parsed.json,
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2573
2944
|
function parseGenerateEnvTypesArgs(args) {
|
|
2574
2945
|
if (args.includes("--help") || args.includes("-h")) {
|
|
2575
2946
|
return { command: "help", topic: "app generate-env-types" };
|
|
@@ -3798,6 +4169,204 @@ function parseDbQueryArgs(args) {
|
|
|
3798
4169
|
...parsed,
|
|
3799
4170
|
};
|
|
3800
4171
|
}
|
|
4172
|
+
const AGENT_HEARTBEAT_SOURCE_VALUES = new Set([
|
|
4173
|
+
"codex",
|
|
4174
|
+
"claude-code",
|
|
4175
|
+
"tender-cli",
|
|
4176
|
+
"git",
|
|
4177
|
+
"other",
|
|
4178
|
+
]);
|
|
4179
|
+
const AGENT_HEARTBEAT_STATUS_VALUES = new Set([
|
|
4180
|
+
"connected",
|
|
4181
|
+
"working",
|
|
4182
|
+
"waiting_for_user",
|
|
4183
|
+
"needs_attention",
|
|
4184
|
+
]);
|
|
4185
|
+
const AGENT_HEARTBEAT_PHASE_VALUES = new Set([
|
|
4186
|
+
"planning",
|
|
4187
|
+
"exploring",
|
|
4188
|
+
"editing",
|
|
4189
|
+
"pushing",
|
|
4190
|
+
"validating",
|
|
4191
|
+
"building_preview",
|
|
4192
|
+
"preview_updating",
|
|
4193
|
+
"watching_preview",
|
|
4194
|
+
"waiting",
|
|
4195
|
+
]);
|
|
4196
|
+
const AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES = new Set([
|
|
4197
|
+
"answer_question",
|
|
4198
|
+
"review_preview",
|
|
4199
|
+
"approve_publish",
|
|
4200
|
+
"none",
|
|
4201
|
+
]);
|
|
4202
|
+
function parseAgentHeartbeatEnum(value, flag, allowed) {
|
|
4203
|
+
const parsed = parseRequiredFlagValue(value, flag);
|
|
4204
|
+
if (!allowed.has(parsed)) {
|
|
4205
|
+
throw new TenderCliUsageError(`${flag} must be one of ${Array.from(allowed).join(", ")}.`);
|
|
4206
|
+
}
|
|
4207
|
+
return parsed;
|
|
4208
|
+
}
|
|
4209
|
+
function parseAgentHeartbeatInputPath(value) {
|
|
4210
|
+
if (!value || (value !== "-" && value.startsWith("-"))) {
|
|
4211
|
+
throw new TenderCliUsageError("--input requires a JSON file path or - for stdin.");
|
|
4212
|
+
}
|
|
4213
|
+
return value;
|
|
4214
|
+
}
|
|
4215
|
+
function parseAgentHeartbeatArgs(args) {
|
|
4216
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
4217
|
+
return { command: "help", topic: "app agent heartbeat" };
|
|
4218
|
+
}
|
|
4219
|
+
const artifactId = args[0];
|
|
4220
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
4221
|
+
throw new TenderCliUsageError("app id is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
4222
|
+
}
|
|
4223
|
+
let baseUrl = null;
|
|
4224
|
+
let flagToken = null;
|
|
4225
|
+
let profileName = null;
|
|
4226
|
+
let inputPath = null;
|
|
4227
|
+
let source = null;
|
|
4228
|
+
let sourceLabel = null;
|
|
4229
|
+
let status = null;
|
|
4230
|
+
let phase = null;
|
|
4231
|
+
let summary = null;
|
|
4232
|
+
let detail = null;
|
|
4233
|
+
let currentTask = null;
|
|
4234
|
+
let commitSha = null;
|
|
4235
|
+
let previewJobId = null;
|
|
4236
|
+
let ttlSeconds = null;
|
|
4237
|
+
let interruptible = false;
|
|
4238
|
+
let requestedAction = null;
|
|
4239
|
+
let json = false;
|
|
4240
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
4241
|
+
const arg = args[index];
|
|
4242
|
+
const sourceAssignment = parseFlagAssignment(arg, "--source");
|
|
4243
|
+
if (sourceAssignment !== null) {
|
|
4244
|
+
source = parseAgentHeartbeatEnum(sourceAssignment, "--source", AGENT_HEARTBEAT_SOURCE_VALUES);
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
const statusAssignment = parseFlagAssignment(arg, "--status");
|
|
4248
|
+
if (statusAssignment !== null) {
|
|
4249
|
+
status = parseAgentHeartbeatEnum(statusAssignment, "--status", AGENT_HEARTBEAT_STATUS_VALUES);
|
|
4250
|
+
continue;
|
|
4251
|
+
}
|
|
4252
|
+
const phaseAssignment = parseFlagAssignment(arg, "--phase");
|
|
4253
|
+
if (phaseAssignment !== null) {
|
|
4254
|
+
phase = parseAgentHeartbeatEnum(phaseAssignment, "--phase", AGENT_HEARTBEAT_PHASE_VALUES);
|
|
4255
|
+
continue;
|
|
4256
|
+
}
|
|
4257
|
+
const requestedActionAssignment = parseFlagAssignment(arg, "--requested-action");
|
|
4258
|
+
if (requestedActionAssignment !== null) {
|
|
4259
|
+
requestedAction = parseAgentHeartbeatEnum(requestedActionAssignment, "--requested-action", AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES);
|
|
4260
|
+
continue;
|
|
4261
|
+
}
|
|
4262
|
+
const ttlAssignment = parseFlagAssignment(arg, "--ttl-seconds");
|
|
4263
|
+
if (ttlAssignment !== null) {
|
|
4264
|
+
ttlSeconds = parsePositiveIntegerFlag(ttlAssignment, "--ttl-seconds");
|
|
4265
|
+
continue;
|
|
4266
|
+
}
|
|
4267
|
+
const inputAssignment = parseFlagAssignment(arg, "--input");
|
|
4268
|
+
if (inputAssignment !== null) {
|
|
4269
|
+
inputPath = parseAgentHeartbeatInputPath(inputAssignment);
|
|
4270
|
+
continue;
|
|
4271
|
+
}
|
|
4272
|
+
const textAssignments = [
|
|
4273
|
+
["--source-label", (value) => { sourceLabel = value; }],
|
|
4274
|
+
["--summary", (value) => { summary = value; }],
|
|
4275
|
+
["--detail", (value) => { detail = value; }],
|
|
4276
|
+
["--current-task", (value) => { currentTask = value; }],
|
|
4277
|
+
["--commit", (value) => { commitSha = value; }],
|
|
4278
|
+
["--preview-job", (value) => { previewJobId = value; }],
|
|
4279
|
+
];
|
|
4280
|
+
const matchedAssignment = textAssignments.find(([flag]) => arg.startsWith(`${flag}=`));
|
|
4281
|
+
if (matchedAssignment) {
|
|
4282
|
+
const [flag, assign] = matchedAssignment;
|
|
4283
|
+
assign(parseRequiredFlagValue(parseFlagAssignment(arg, flag) ?? undefined, flag));
|
|
4284
|
+
continue;
|
|
4285
|
+
}
|
|
4286
|
+
if (arg === "--json") {
|
|
4287
|
+
json = true;
|
|
4288
|
+
continue;
|
|
4289
|
+
}
|
|
4290
|
+
if (arg === "--interruptible") {
|
|
4291
|
+
interruptible = true;
|
|
4292
|
+
continue;
|
|
4293
|
+
}
|
|
4294
|
+
if (arg === "--base-url") {
|
|
4295
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
4296
|
+
index += 1;
|
|
4297
|
+
continue;
|
|
4298
|
+
}
|
|
4299
|
+
if (arg === "--token") {
|
|
4300
|
+
flagToken = parseTokenFlag(args, index);
|
|
4301
|
+
index += 1;
|
|
4302
|
+
continue;
|
|
4303
|
+
}
|
|
4304
|
+
if (arg === "--profile") {
|
|
4305
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
4306
|
+
index += 1;
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
if (arg === "--source") {
|
|
4310
|
+
source = parseAgentHeartbeatEnum(args[index + 1], "--source", AGENT_HEARTBEAT_SOURCE_VALUES);
|
|
4311
|
+
index += 1;
|
|
4312
|
+
continue;
|
|
4313
|
+
}
|
|
4314
|
+
if (arg === "--status") {
|
|
4315
|
+
status = parseAgentHeartbeatEnum(args[index + 1], "--status", AGENT_HEARTBEAT_STATUS_VALUES);
|
|
4316
|
+
index += 1;
|
|
4317
|
+
continue;
|
|
4318
|
+
}
|
|
4319
|
+
if (arg === "--phase") {
|
|
4320
|
+
phase = parseAgentHeartbeatEnum(args[index + 1], "--phase", AGENT_HEARTBEAT_PHASE_VALUES);
|
|
4321
|
+
index += 1;
|
|
4322
|
+
continue;
|
|
4323
|
+
}
|
|
4324
|
+
if (arg === "--requested-action") {
|
|
4325
|
+
requestedAction = parseAgentHeartbeatEnum(args[index + 1], "--requested-action", AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES);
|
|
4326
|
+
index += 1;
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
4329
|
+
if (arg === "--ttl-seconds") {
|
|
4330
|
+
ttlSeconds = parsePositiveIntegerFlag(args[index + 1], "--ttl-seconds");
|
|
4331
|
+
index += 1;
|
|
4332
|
+
continue;
|
|
4333
|
+
}
|
|
4334
|
+
if (arg === "--input") {
|
|
4335
|
+
inputPath = parseAgentHeartbeatInputPath(args[index + 1]);
|
|
4336
|
+
index += 1;
|
|
4337
|
+
continue;
|
|
4338
|
+
}
|
|
4339
|
+
const matchedFlag = textAssignments.find(([flag]) => arg === flag);
|
|
4340
|
+
if (matchedFlag) {
|
|
4341
|
+
const [flag, assign] = matchedFlag;
|
|
4342
|
+
assign(parseRequiredFlagValue(args[index + 1], flag));
|
|
4343
|
+
index += 1;
|
|
4344
|
+
continue;
|
|
4345
|
+
}
|
|
4346
|
+
throw new TenderCliUsageError(`Unknown app agent heartbeat option: ${arg}`);
|
|
4347
|
+
}
|
|
4348
|
+
return {
|
|
4349
|
+
command: "app agent heartbeat",
|
|
4350
|
+
artifactId,
|
|
4351
|
+
baseUrl,
|
|
4352
|
+
flagToken,
|
|
4353
|
+
profileName,
|
|
4354
|
+
inputPath,
|
|
4355
|
+
source,
|
|
4356
|
+
sourceLabel,
|
|
4357
|
+
status,
|
|
4358
|
+
phase,
|
|
4359
|
+
summary,
|
|
4360
|
+
detail,
|
|
4361
|
+
currentTask,
|
|
4362
|
+
commitSha,
|
|
4363
|
+
previewJobId,
|
|
4364
|
+
ttlSeconds,
|
|
4365
|
+
interruptible,
|
|
4366
|
+
requestedAction,
|
|
4367
|
+
json,
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
3801
4370
|
function parseTenderArgs(args) {
|
|
3802
4371
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
3803
4372
|
return { command: "help", topic: "root" };
|
|
@@ -3823,6 +4392,21 @@ function parseTenderArgs(args) {
|
|
|
3823
4392
|
}
|
|
3824
4393
|
throw new TenderCliUsageError(`Unknown auth command: ${args[1]}`);
|
|
3825
4394
|
}
|
|
4395
|
+
if (args[0] === "playbooks") {
|
|
4396
|
+
if (args[1] === undefined || args[1] === "--help" || args[1] === "-h") {
|
|
4397
|
+
return { command: "help", topic: "playbooks" };
|
|
4398
|
+
}
|
|
4399
|
+
if (args[1] === "list") {
|
|
4400
|
+
return parsePlaybooksListArgs(args.slice(2));
|
|
4401
|
+
}
|
|
4402
|
+
if (args[1] === "get") {
|
|
4403
|
+
return parsePlaybooksGetArgs(args.slice(2));
|
|
4404
|
+
}
|
|
4405
|
+
if (args[1] === "file") {
|
|
4406
|
+
return parsePlaybooksFileArgs(args.slice(2));
|
|
4407
|
+
}
|
|
4408
|
+
throw new TenderCliUsageError(`Unknown playbooks command: ${args[1]}`);
|
|
4409
|
+
}
|
|
3826
4410
|
if (args[0] === "apps") {
|
|
3827
4411
|
throw new TenderCliUsageError("Unknown command: apps. Use `tender app ...` for Tender App workflows.");
|
|
3828
4412
|
}
|
|
@@ -3857,6 +4441,18 @@ function parseTenderArgs(args) {
|
|
|
3857
4441
|
if (resourceArgs[1] === "context" && resourceArgs[2] === "refresh") {
|
|
3858
4442
|
return parseContextRefreshArgs(resourceArgs.slice(3));
|
|
3859
4443
|
}
|
|
4444
|
+
if (resourceArgs[1] === "agent" &&
|
|
4445
|
+
(resourceArgs[2] === undefined ||
|
|
4446
|
+
resourceArgs[2] === "--help" ||
|
|
4447
|
+
resourceArgs[2] === "-h")) {
|
|
4448
|
+
return { command: "help", topic: "app agent heartbeat" };
|
|
4449
|
+
}
|
|
4450
|
+
if (resourceArgs[1] === "agent" && resourceArgs[2] === "heartbeat") {
|
|
4451
|
+
return parseAgentHeartbeatArgs(resourceArgs.slice(3));
|
|
4452
|
+
}
|
|
4453
|
+
if (resourceArgs[1] === "agent") {
|
|
4454
|
+
throw new TenderCliUsageError(`Unknown app agent command: ${resourceArgs[2]}`);
|
|
4455
|
+
}
|
|
3860
4456
|
if (resourceArgs[1] === "bindings" && resourceArgs[2] === "outbound") {
|
|
3861
4457
|
return parseBindingsOutboundArgs(resourceArgs.slice(3));
|
|
3862
4458
|
}
|
|
@@ -4426,6 +5022,40 @@ async function requestDbApi(input) {
|
|
|
4426
5022
|
}
|
|
4427
5023
|
return payload;
|
|
4428
5024
|
}
|
|
5025
|
+
function encodePathSegments(filePath) {
|
|
5026
|
+
return filePath.split("/").map(encodeURIComponent).join("/");
|
|
5027
|
+
}
|
|
5028
|
+
async function requestPlaybooksApi(input) {
|
|
5029
|
+
const suffix = input.playbooksPath === "list"
|
|
5030
|
+
? ""
|
|
5031
|
+
: input.playbooksPath === "get"
|
|
5032
|
+
? `/${encodeURIComponent(input.playbookId)}`
|
|
5033
|
+
: `/${encodeURIComponent(input.playbookId)}/files/${encodePathSegments(input.filePath)}`;
|
|
5034
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/playbooks${suffix}`, {
|
|
5035
|
+
method: "GET",
|
|
5036
|
+
headers: {
|
|
5037
|
+
authorization: `Bearer ${input.token}`,
|
|
5038
|
+
accept: "application/json",
|
|
5039
|
+
},
|
|
5040
|
+
});
|
|
5041
|
+
const payload = await parseJsonResponse(response);
|
|
5042
|
+
if (!response.ok || !payload) {
|
|
5043
|
+
const reasonCode = (typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
5044
|
+
`http_${response.status}`;
|
|
5045
|
+
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
5046
|
+
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
5047
|
+
`Playbooks request failed with HTTP ${response.status}.`;
|
|
5048
|
+
const authHint = response.status === 401
|
|
5049
|
+
? "\nAuthenticate first: tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json"
|
|
5050
|
+
: "";
|
|
5051
|
+
throw new Error([
|
|
5052
|
+
message,
|
|
5053
|
+
`Reason: ${reasonCode}.`,
|
|
5054
|
+
"Example: tender playbooks list --profile agent --json.",
|
|
5055
|
+
].join("\n") + authHint);
|
|
5056
|
+
}
|
|
5057
|
+
return payload;
|
|
5058
|
+
}
|
|
4429
5059
|
function dbCorrectedExample(artifactId, dbPath) {
|
|
4430
5060
|
if (dbPath === "capabilities") {
|
|
4431
5061
|
return `tender app db capabilities ${artifactId} --json`;
|
|
@@ -5246,6 +5876,31 @@ async function requestAgentContext(input) {
|
|
|
5246
5876
|
files: payload.files,
|
|
5247
5877
|
};
|
|
5248
5878
|
}
|
|
5879
|
+
async function requestArtifactScaffold(input) {
|
|
5880
|
+
const params = new URLSearchParams({ template: input.template });
|
|
5881
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/scaffold?${params.toString()}`, {
|
|
5882
|
+
headers: {
|
|
5883
|
+
authorization: `Bearer ${input.token}`,
|
|
5884
|
+
},
|
|
5885
|
+
});
|
|
5886
|
+
const payload = await parseJsonResponse(response);
|
|
5887
|
+
if (!response.ok ||
|
|
5888
|
+
!payload?.ok ||
|
|
5889
|
+
!payload.files ||
|
|
5890
|
+
!payload.revision ||
|
|
5891
|
+
payload.template !== input.template) {
|
|
5892
|
+
throw new Error(payload?.message ??
|
|
5893
|
+
payload?.reasonCode ??
|
|
5894
|
+
`Artifact scaffold request failed with HTTP ${response.status}.`);
|
|
5895
|
+
}
|
|
5896
|
+
return {
|
|
5897
|
+
...payload,
|
|
5898
|
+
artifactId: payload.artifactId ?? input.artifactId,
|
|
5899
|
+
template: payload.template,
|
|
5900
|
+
revision: payload.revision,
|
|
5901
|
+
files: payload.files,
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5249
5904
|
async function runContextFetch(command, io, runtime) {
|
|
5250
5905
|
const credentials = await resolveApiCredentials({
|
|
5251
5906
|
baseUrl: command.baseUrl,
|
|
@@ -5454,8 +6109,33 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5454
6109
|
payload?.reasonCode ??
|
|
5455
6110
|
`Artifact create request failed with HTTP ${response.status}.`);
|
|
5456
6111
|
}
|
|
6112
|
+
let initResult = null;
|
|
6113
|
+
let initExitCode = null;
|
|
6114
|
+
if (command.init) {
|
|
6115
|
+
const initStdout = [];
|
|
6116
|
+
initExitCode = await runArtifactsInit({
|
|
6117
|
+
command: "app init",
|
|
6118
|
+
artifactId: payload.artifact.artifactId,
|
|
6119
|
+
dir: command.dir,
|
|
6120
|
+
remoteName: command.remoteName,
|
|
6121
|
+
baseUrl: credentials.baseUrl,
|
|
6122
|
+
flagToken: command.flagToken,
|
|
6123
|
+
profileName: credentials.profileName,
|
|
6124
|
+
ttl: command.ttl,
|
|
6125
|
+
scaffold: command.scaffold,
|
|
6126
|
+
preview: command.preview,
|
|
6127
|
+
dryRun: false,
|
|
6128
|
+
force: command.force,
|
|
6129
|
+
json: true,
|
|
6130
|
+
}, {
|
|
6131
|
+
stdout: (message) => initStdout.push(message),
|
|
6132
|
+
stderr: io.stderr,
|
|
6133
|
+
}, runtime);
|
|
6134
|
+
const initPayload = initStdout.join("\n").trim();
|
|
6135
|
+
initResult = initPayload ? JSON.parse(initPayload) : null;
|
|
6136
|
+
}
|
|
5457
6137
|
const result = {
|
|
5458
|
-
ok: true,
|
|
6138
|
+
ok: initExitCode === null ? true : initExitCode === 0,
|
|
5459
6139
|
command: command.command,
|
|
5460
6140
|
baseUrl: credentials.baseUrl,
|
|
5461
6141
|
authSource: credentials.source,
|
|
@@ -5463,7 +6143,9 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5463
6143
|
tenantId: payload.tenantId ?? null,
|
|
5464
6144
|
appId: payload.artifact.artifactId,
|
|
5465
6145
|
artifact: payload.artifact,
|
|
6146
|
+
init: initResult,
|
|
5466
6147
|
next: {
|
|
6148
|
+
init: `tender app init ${payload.artifact.artifactId} --dir . --preview`,
|
|
5467
6149
|
contextFetch: `tender app context fetch ${payload.artifact.artifactId} --dir .`,
|
|
5468
6150
|
gitSetup: `tender app git setup ${payload.artifact.artifactId} --dir .`,
|
|
5469
6151
|
},
|
|
@@ -5476,10 +6158,13 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5476
6158
|
`app_id: ${payload.artifact.artifactId}`,
|
|
5477
6159
|
`name: ${payload.artifact.name}`,
|
|
5478
6160
|
`preview_url: ${payload.artifact.previewUrl ?? ""}`,
|
|
5479
|
-
`next: ${result.next.
|
|
5480
|
-
|
|
6161
|
+
initExitCode === null ? `next: ${result.next.init}` : null,
|
|
6162
|
+
initExitCode !== null ? `init_exit_code: ${initExitCode}` : null,
|
|
6163
|
+
]
|
|
6164
|
+
.filter((line) => Boolean(line))
|
|
6165
|
+
.join("\n"));
|
|
5481
6166
|
}
|
|
5482
|
-
return 0;
|
|
6167
|
+
return initExitCode ?? 0;
|
|
5483
6168
|
}
|
|
5484
6169
|
async function runArtifactsUpdate(command, io, runtime) {
|
|
5485
6170
|
const credentials = await resolveApiCredentials({
|
|
@@ -5563,7 +6248,7 @@ function shellQuote(value) {
|
|
|
5563
6248
|
function createCredentialHelperCommand(input) {
|
|
5564
6249
|
const parts = [
|
|
5565
6250
|
"tender",
|
|
5566
|
-
"
|
|
6251
|
+
"app",
|
|
5567
6252
|
"git",
|
|
5568
6253
|
"credential",
|
|
5569
6254
|
shellQuote(input.artifactId),
|
|
@@ -5736,6 +6421,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5736
6421
|
skippedReason: "local_head_exists",
|
|
5737
6422
|
commitExitCode: null,
|
|
5738
6423
|
pushExitCode: null,
|
|
6424
|
+
commitSha: null,
|
|
5739
6425
|
};
|
|
5740
6426
|
}
|
|
5741
6427
|
if (input.sourceCheckout.checkedOut) {
|
|
@@ -5746,6 +6432,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5746
6432
|
skippedReason: "source_checked_out",
|
|
5747
6433
|
commitExitCode: null,
|
|
5748
6434
|
pushExitCode: null,
|
|
6435
|
+
commitSha: null,
|
|
5749
6436
|
};
|
|
5750
6437
|
}
|
|
5751
6438
|
if (input.sourceCheckout.skippedReason !== "remote_default_branch_missing") {
|
|
@@ -5756,6 +6443,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5756
6443
|
skippedReason: "remote_unavailable",
|
|
5757
6444
|
commitExitCode: null,
|
|
5758
6445
|
pushExitCode: null,
|
|
6446
|
+
commitSha: null,
|
|
5759
6447
|
};
|
|
5760
6448
|
}
|
|
5761
6449
|
const paths = [...new Set(input.paths)].sort();
|
|
@@ -5767,6 +6455,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5767
6455
|
skippedReason: "no_files_to_seed",
|
|
5768
6456
|
commitExitCode: null,
|
|
5769
6457
|
pushExitCode: null,
|
|
6458
|
+
commitSha: null,
|
|
5770
6459
|
};
|
|
5771
6460
|
}
|
|
5772
6461
|
await runGitCommand({
|
|
@@ -5779,6 +6468,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5779
6468
|
cwd: input.root,
|
|
5780
6469
|
args: ["commit", "-m", "Seed Tender App scaffold"],
|
|
5781
6470
|
});
|
|
6471
|
+
const commitShaResult = await runGitCommand({
|
|
6472
|
+
runtime: input.runtime,
|
|
6473
|
+
cwd: input.root,
|
|
6474
|
+
args: ["rev-parse", "HEAD"],
|
|
6475
|
+
});
|
|
5782
6476
|
const pushResult = await runGitCommand({
|
|
5783
6477
|
runtime: input.runtime,
|
|
5784
6478
|
cwd: input.root,
|
|
@@ -5791,6 +6485,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5791
6485
|
skippedReason: null,
|
|
5792
6486
|
commitExitCode: commitResult.exitCode,
|
|
5793
6487
|
pushExitCode: pushResult.exitCode,
|
|
6488
|
+
commitSha: commitShaResult.stdout.trim() || null,
|
|
5794
6489
|
};
|
|
5795
6490
|
}
|
|
5796
6491
|
async function requestGitRemoteLease(input) {
|
|
@@ -5808,6 +6503,26 @@ async function requestGitRemoteLease(input) {
|
|
|
5808
6503
|
}
|
|
5809
6504
|
return payload.git;
|
|
5810
6505
|
}
|
|
6506
|
+
async function requestPreviewRebuildJob(input) {
|
|
6507
|
+
const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/preview/rebuild`, {
|
|
6508
|
+
method: "POST",
|
|
6509
|
+
headers: {
|
|
6510
|
+
authorization: `Bearer ${input.credentials.token}`,
|
|
6511
|
+
"content-type": "application/json",
|
|
6512
|
+
},
|
|
6513
|
+
body: JSON.stringify({ commitSha: input.commitSha }),
|
|
6514
|
+
});
|
|
6515
|
+
const payload = await parseJsonResponse(response);
|
|
6516
|
+
if (!response.ok || !payload?.ok) {
|
|
6517
|
+
throw new Error(payload?.message ??
|
|
6518
|
+
payload?.reasonCode ??
|
|
6519
|
+
`Artifact preview request failed with HTTP ${response.status}.`);
|
|
6520
|
+
}
|
|
6521
|
+
return {
|
|
6522
|
+
job: payload.job ?? null,
|
|
6523
|
+
result: payload.result ?? null,
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
5811
6526
|
function parseCredentialInput(stdin) {
|
|
5812
6527
|
const values = new Map();
|
|
5813
6528
|
for (const line of stdin.split(/\r?\n/)) {
|
|
@@ -6010,13 +6725,28 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6010
6725
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
6011
6726
|
commitExitCode: null,
|
|
6012
6727
|
pushExitCode: null,
|
|
6728
|
+
commitSha: null,
|
|
6013
6729
|
};
|
|
6014
6730
|
let sourceExport = {
|
|
6015
6731
|
attempted: false,
|
|
6732
|
+
source: null,
|
|
6733
|
+
template: null,
|
|
6734
|
+
revision: null,
|
|
6016
6735
|
filesWritten: 0,
|
|
6017
6736
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
6018
6737
|
files: [],
|
|
6019
6738
|
};
|
|
6739
|
+
let preview = {
|
|
6740
|
+
attempted: false,
|
|
6741
|
+
commitSha: null,
|
|
6742
|
+
skippedReason: command.preview
|
|
6743
|
+
? command.dryRun
|
|
6744
|
+
? "dry_run"
|
|
6745
|
+
: null
|
|
6746
|
+
: "not_requested",
|
|
6747
|
+
job: null,
|
|
6748
|
+
result: null,
|
|
6749
|
+
};
|
|
6020
6750
|
let seedPaths = [];
|
|
6021
6751
|
let plannedFiles = await planContextFiles({
|
|
6022
6752
|
root,
|
|
@@ -6052,12 +6782,31 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6052
6782
|
defaultBranch: git.defaultBranch,
|
|
6053
6783
|
});
|
|
6054
6784
|
if (sourceCheckout.skippedReason === "remote_default_branch_missing") {
|
|
6055
|
-
|
|
6785
|
+
let sourceFiles = await collectArtifactWorkspaceSourceFiles({
|
|
6056
6786
|
artifactId: command.artifactId,
|
|
6057
6787
|
baseUrl: credentials.baseUrl,
|
|
6058
6788
|
token: credentials.token,
|
|
6059
6789
|
fetcher,
|
|
6060
6790
|
});
|
|
6791
|
+
let source = sourceFiles.length > 0 ? "workspace" : null;
|
|
6792
|
+
let scaffoldRevision = null;
|
|
6793
|
+
let scaffoldTemplate = null;
|
|
6794
|
+
if (sourceFiles.length === 0 && command.scaffold !== "none") {
|
|
6795
|
+
const scaffold = await requestArtifactScaffold({
|
|
6796
|
+
artifactId: command.artifactId,
|
|
6797
|
+
template: command.scaffold,
|
|
6798
|
+
baseUrl: credentials.baseUrl,
|
|
6799
|
+
token: credentials.token,
|
|
6800
|
+
fetcher,
|
|
6801
|
+
});
|
|
6802
|
+
sourceFiles = scaffold.files.map((file) => ({
|
|
6803
|
+
path: file.path,
|
|
6804
|
+
content: file.content,
|
|
6805
|
+
}));
|
|
6806
|
+
source = "api_scaffold";
|
|
6807
|
+
scaffoldRevision = scaffold.revision;
|
|
6808
|
+
scaffoldTemplate = scaffold.template;
|
|
6809
|
+
}
|
|
6061
6810
|
const plannedSourceFiles = await planSourceFiles({
|
|
6062
6811
|
root,
|
|
6063
6812
|
files: sourceFiles,
|
|
@@ -6066,8 +6815,15 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6066
6815
|
const sourceConflicts = plannedSourceFiles.filter((file) => file.action === "conflict");
|
|
6067
6816
|
sourceExport = {
|
|
6068
6817
|
attempted: true,
|
|
6818
|
+
source,
|
|
6819
|
+
template: scaffoldTemplate,
|
|
6820
|
+
revision: scaffoldRevision,
|
|
6069
6821
|
filesWritten: 0,
|
|
6070
|
-
skippedReason: plannedSourceFiles.length === 0
|
|
6822
|
+
skippedReason: plannedSourceFiles.length === 0
|
|
6823
|
+
? command.scaffold === "none"
|
|
6824
|
+
? "scaffold_disabled"
|
|
6825
|
+
: "empty_workspace"
|
|
6826
|
+
: null,
|
|
6071
6827
|
files: plannedSourceFiles.map((file) => ({
|
|
6072
6828
|
path: file.path,
|
|
6073
6829
|
action: file.action,
|
|
@@ -6133,6 +6889,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6133
6889
|
else if (sourceCheckout.checkedOut) {
|
|
6134
6890
|
sourceExport = {
|
|
6135
6891
|
attempted: false,
|
|
6892
|
+
source: null,
|
|
6893
|
+
template: null,
|
|
6894
|
+
revision: null,
|
|
6136
6895
|
filesWritten: 0,
|
|
6137
6896
|
skippedReason: "source_checked_out",
|
|
6138
6897
|
files: [],
|
|
@@ -6141,6 +6900,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6141
6900
|
else if (sourceCheckout.skippedReason === "local_head_exists") {
|
|
6142
6901
|
sourceExport = {
|
|
6143
6902
|
attempted: false,
|
|
6903
|
+
source: null,
|
|
6904
|
+
template: null,
|
|
6905
|
+
revision: null,
|
|
6144
6906
|
filesWritten: 0,
|
|
6145
6907
|
skippedReason: "local_head_exists",
|
|
6146
6908
|
files: [],
|
|
@@ -6149,6 +6911,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6149
6911
|
else if (sourceCheckout.skippedReason === "remote_unavailable") {
|
|
6150
6912
|
sourceExport = {
|
|
6151
6913
|
attempted: false,
|
|
6914
|
+
source: null,
|
|
6915
|
+
template: null,
|
|
6916
|
+
revision: null,
|
|
6152
6917
|
filesWritten: 0,
|
|
6153
6918
|
skippedReason: "remote_unavailable",
|
|
6154
6919
|
files: [],
|
|
@@ -6220,6 +6985,45 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6220
6985
|
sourceCheckout,
|
|
6221
6986
|
paths: seedPaths,
|
|
6222
6987
|
});
|
|
6988
|
+
if (command.preview) {
|
|
6989
|
+
let commitSha = seedCommit.commitSha;
|
|
6990
|
+
if (!commitSha && (seedCommit.pushed || sourceCheckout.checkedOut)) {
|
|
6991
|
+
const headResult = await runGitCommand({
|
|
6992
|
+
runtime,
|
|
6993
|
+
cwd: root,
|
|
6994
|
+
args: ["rev-parse", "HEAD"],
|
|
6995
|
+
allowFailure: true,
|
|
6996
|
+
});
|
|
6997
|
+
commitSha =
|
|
6998
|
+
headResult.exitCode === 0 ? headResult.stdout.trim() || null : null;
|
|
6999
|
+
}
|
|
7000
|
+
if (!commitSha) {
|
|
7001
|
+
preview = {
|
|
7002
|
+
attempted: false,
|
|
7003
|
+
commitSha: null,
|
|
7004
|
+
skippedReason: seedCommit.pushed || sourceCheckout.checkedOut
|
|
7005
|
+
? "commit_unavailable"
|
|
7006
|
+
: "seed_not_pushed",
|
|
7007
|
+
job: null,
|
|
7008
|
+
result: null,
|
|
7009
|
+
};
|
|
7010
|
+
}
|
|
7011
|
+
else {
|
|
7012
|
+
const previewJob = await requestPreviewRebuildJob({
|
|
7013
|
+
artifactId: command.artifactId,
|
|
7014
|
+
commitSha,
|
|
7015
|
+
credentials,
|
|
7016
|
+
fetcher,
|
|
7017
|
+
});
|
|
7018
|
+
preview = {
|
|
7019
|
+
attempted: true,
|
|
7020
|
+
commitSha,
|
|
7021
|
+
skippedReason: null,
|
|
7022
|
+
job: previewJob.job,
|
|
7023
|
+
result: previewJob.result,
|
|
7024
|
+
};
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
6223
7027
|
}
|
|
6224
7028
|
const result = {
|
|
6225
7029
|
ok: true,
|
|
@@ -6233,11 +7037,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6233
7037
|
defaultBranch: git.defaultBranch,
|
|
6234
7038
|
dryRun: command.dryRun,
|
|
6235
7039
|
force: command.force,
|
|
7040
|
+
scaffold: command.scaffold,
|
|
6236
7041
|
authSource: credentials.source,
|
|
6237
7042
|
profile: credentials.profileName,
|
|
6238
7043
|
sourceCheckout,
|
|
6239
7044
|
sourceExport,
|
|
6240
7045
|
seedCommit,
|
|
7046
|
+
preview,
|
|
6241
7047
|
summary: {
|
|
6242
7048
|
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
6243
7049
|
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
@@ -6267,8 +7073,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6267
7073
|
`context_revision: ${context.revision}`,
|
|
6268
7074
|
`dir: ${root}`,
|
|
6269
7075
|
`remote: ${command.remoteName}`,
|
|
7076
|
+
preview.attempted && preview.job?.jobId
|
|
7077
|
+
? `preview_job: ${preview.job.jobId}`
|
|
7078
|
+
: null,
|
|
6270
7079
|
`next: ${result.next}`,
|
|
6271
|
-
]
|
|
7080
|
+
]
|
|
7081
|
+
.filter((line) => Boolean(line))
|
|
7082
|
+
.join("\n"));
|
|
6272
7083
|
}
|
|
6273
7084
|
return 0;
|
|
6274
7085
|
}
|
|
@@ -6666,6 +7477,109 @@ async function runPublishWatch(command, io, runtime) {
|
|
|
6666
7477
|
}
|
|
6667
7478
|
return 0;
|
|
6668
7479
|
}
|
|
7480
|
+
async function readAgentHeartbeatInputObject(input) {
|
|
7481
|
+
if (!input.command.inputPath) {
|
|
7482
|
+
return {};
|
|
7483
|
+
}
|
|
7484
|
+
const raw = input.command.inputPath === "-"
|
|
7485
|
+
? input.stdin
|
|
7486
|
+
: await readFile(path.resolve(input.command.inputPath), "utf8");
|
|
7487
|
+
if (!raw) {
|
|
7488
|
+
throw new TenderCliUsageError("--input - requires heartbeat JSON on stdin.");
|
|
7489
|
+
}
|
|
7490
|
+
let parsed;
|
|
7491
|
+
try {
|
|
7492
|
+
parsed = JSON.parse(raw);
|
|
7493
|
+
}
|
|
7494
|
+
catch {
|
|
7495
|
+
throw new TenderCliUsageError("--input must contain valid JSON.");
|
|
7496
|
+
}
|
|
7497
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7498
|
+
throw new TenderCliUsageError("--input must contain a JSON object.");
|
|
7499
|
+
}
|
|
7500
|
+
return parsed;
|
|
7501
|
+
}
|
|
7502
|
+
function assignAgentHeartbeatField(body, field, value) {
|
|
7503
|
+
if (value !== null) {
|
|
7504
|
+
body[field] = value;
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
async function buildAgentHeartbeatBody(command, runtime) {
|
|
7508
|
+
const body = await readAgentHeartbeatInputObject({
|
|
7509
|
+
command,
|
|
7510
|
+
stdin: runtime.stdin,
|
|
7511
|
+
});
|
|
7512
|
+
assignAgentHeartbeatField(body, "source", command.source);
|
|
7513
|
+
assignAgentHeartbeatField(body, "sourceLabel", command.sourceLabel);
|
|
7514
|
+
assignAgentHeartbeatField(body, "status", command.status);
|
|
7515
|
+
assignAgentHeartbeatField(body, "phase", command.phase);
|
|
7516
|
+
assignAgentHeartbeatField(body, "summary", command.summary);
|
|
7517
|
+
assignAgentHeartbeatField(body, "detail", command.detail);
|
|
7518
|
+
assignAgentHeartbeatField(body, "currentTask", command.currentTask);
|
|
7519
|
+
assignAgentHeartbeatField(body, "commitSha", command.commitSha);
|
|
7520
|
+
assignAgentHeartbeatField(body, "previewJobId", command.previewJobId);
|
|
7521
|
+
assignAgentHeartbeatField(body, "ttlSeconds", command.ttlSeconds);
|
|
7522
|
+
assignAgentHeartbeatField(body, "requestedAction", command.requestedAction);
|
|
7523
|
+
if (command.interruptible) {
|
|
7524
|
+
body.interruptible = true;
|
|
7525
|
+
}
|
|
7526
|
+
if (typeof body.source !== "string" || body.source.trim().length === 0) {
|
|
7527
|
+
throw new TenderCliUsageError("--source is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7528
|
+
}
|
|
7529
|
+
if (typeof body.status !== "string" || body.status.trim().length === 0) {
|
|
7530
|
+
throw new TenderCliUsageError("--status is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7531
|
+
}
|
|
7532
|
+
if (typeof body.summary !== "string" || body.summary.trim().length === 0) {
|
|
7533
|
+
throw new TenderCliUsageError("--summary is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7534
|
+
}
|
|
7535
|
+
return body;
|
|
7536
|
+
}
|
|
7537
|
+
async function runAgentHeartbeat(command, io, runtime) {
|
|
7538
|
+
const credentials = await resolveApiCredentials({
|
|
7539
|
+
baseUrl: command.baseUrl,
|
|
7540
|
+
flagToken: command.flagToken,
|
|
7541
|
+
profileName: command.profileName,
|
|
7542
|
+
env: runtime.env ?? process.env,
|
|
7543
|
+
});
|
|
7544
|
+
const body = await buildAgentHeartbeatBody(command, runtime);
|
|
7545
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/agent/heartbeat`, {
|
|
7546
|
+
method: "POST",
|
|
7547
|
+
headers: {
|
|
7548
|
+
authorization: `Bearer ${credentials.token}`,
|
|
7549
|
+
accept: "application/json",
|
|
7550
|
+
"content-type": "application/json",
|
|
7551
|
+
},
|
|
7552
|
+
body: JSON.stringify(body),
|
|
7553
|
+
});
|
|
7554
|
+
const payload = await parseJsonResponse(response);
|
|
7555
|
+
if (!response.ok || !payload?.ok) {
|
|
7556
|
+
throw new Error((typeof payload?.message === "string" && payload.message) ||
|
|
7557
|
+
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
7558
|
+
`Agent heartbeat failed with HTTP ${response.status}.`);
|
|
7559
|
+
}
|
|
7560
|
+
const result = {
|
|
7561
|
+
ok: true,
|
|
7562
|
+
command: command.command,
|
|
7563
|
+
baseUrl: credentials.baseUrl,
|
|
7564
|
+
authSource: credentials.source,
|
|
7565
|
+
profile: credentials.profileName,
|
|
7566
|
+
tenantId: payload.tenantId ?? null,
|
|
7567
|
+
artifactId: payload.artifactId ?? command.artifactId,
|
|
7568
|
+
activity: payload.activity ?? null,
|
|
7569
|
+
};
|
|
7570
|
+
if (command.json) {
|
|
7571
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
7572
|
+
}
|
|
7573
|
+
else {
|
|
7574
|
+
io.stdout([
|
|
7575
|
+
`app_id: ${result.artifactId}`,
|
|
7576
|
+
`heartbeat: accepted`,
|
|
7577
|
+
`status: ${String(body.status)}`,
|
|
7578
|
+
`summary: ${String(body.summary)}`,
|
|
7579
|
+
].join("\n"));
|
|
7580
|
+
}
|
|
7581
|
+
return 0;
|
|
7582
|
+
}
|
|
6669
7583
|
async function runGitRemote(command, io, runtime) {
|
|
6670
7584
|
const credentials = await resolveApiCredentials({
|
|
6671
7585
|
baseUrl: command.baseUrl,
|
|
@@ -6723,6 +7637,25 @@ async function resolveAnalyticsCredentials(command, runtime) {
|
|
|
6723
7637
|
env: runtime.env ?? process.env,
|
|
6724
7638
|
});
|
|
6725
7639
|
}
|
|
7640
|
+
async function resolvePlaybooksCredentials(command, runtime) {
|
|
7641
|
+
try {
|
|
7642
|
+
return await resolveApiCredentials({
|
|
7643
|
+
baseUrl: command.baseUrl,
|
|
7644
|
+
flagToken: command.flagToken,
|
|
7645
|
+
profileName: command.profileName,
|
|
7646
|
+
env: runtime.env ?? process.env,
|
|
7647
|
+
});
|
|
7648
|
+
}
|
|
7649
|
+
catch (error) {
|
|
7650
|
+
if (error instanceof TenderCliUsageError) {
|
|
7651
|
+
throw new TenderCliUsageError([
|
|
7652
|
+
"Playbooks require Tender authentication.",
|
|
7653
|
+
"Run `tender auth login --device --profile edit-preview --save-profile agent --ttl 7d --json`, set TENDER_API_TOKEN, or pass --token <token>.",
|
|
7654
|
+
].join("\n"));
|
|
7655
|
+
}
|
|
7656
|
+
throw error;
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
6726
7659
|
async function resolveDbCredentials(command, runtime) {
|
|
6727
7660
|
return await resolveApiCredentials({
|
|
6728
7661
|
baseUrl: command.baseUrl,
|
|
@@ -6760,6 +7693,58 @@ function printDbPayload(command, io, payload, fallbackLines) {
|
|
|
6760
7693
|
io.stdout(fallbackLines.join("\n"));
|
|
6761
7694
|
}
|
|
6762
7695
|
}
|
|
7696
|
+
function printPlaybooksPayload(command, io, payload, fallback) {
|
|
7697
|
+
if (command.json) {
|
|
7698
|
+
io.stdout(JSON.stringify(payload, null, 2));
|
|
7699
|
+
}
|
|
7700
|
+
else {
|
|
7701
|
+
io.stdout(fallback);
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
async function runPlaybooksList(command, io, runtime) {
|
|
7705
|
+
const credentials = await resolvePlaybooksCredentials(command, runtime);
|
|
7706
|
+
const payload = await requestPlaybooksApi({
|
|
7707
|
+
playbooksPath: "list",
|
|
7708
|
+
baseUrl: credentials.baseUrl,
|
|
7709
|
+
token: credentials.token,
|
|
7710
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
7711
|
+
});
|
|
7712
|
+
const playbooks = Array.isArray(payload.playbooks) ? payload.playbooks : [];
|
|
7713
|
+
printPlaybooksPayload(command, io, payload, playbooks
|
|
7714
|
+
.map((playbook) => isCliJsonRecord(playbook)
|
|
7715
|
+
? `${String(playbook.id ?? "")}\t${String(playbook.description ?? "")}`
|
|
7716
|
+
: "")
|
|
7717
|
+
.filter(Boolean)
|
|
7718
|
+
.join("\n") || "No Tender Prompt playbooks found.");
|
|
7719
|
+
return 0;
|
|
7720
|
+
}
|
|
7721
|
+
async function runPlaybooksGet(command, io, runtime) {
|
|
7722
|
+
const credentials = await resolvePlaybooksCredentials(command, runtime);
|
|
7723
|
+
const payload = await requestPlaybooksApi({
|
|
7724
|
+
playbooksPath: "get",
|
|
7725
|
+
playbookId: command.playbookId,
|
|
7726
|
+
baseUrl: credentials.baseUrl,
|
|
7727
|
+
token: credentials.token,
|
|
7728
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
7729
|
+
});
|
|
7730
|
+
printPlaybooksPayload(command, io, payload, typeof payload.body === "string" ? payload.body : JSON.stringify(payload, null, 2));
|
|
7731
|
+
return 0;
|
|
7732
|
+
}
|
|
7733
|
+
async function runPlaybooksFile(command, io, runtime) {
|
|
7734
|
+
const credentials = await resolvePlaybooksCredentials(command, runtime);
|
|
7735
|
+
const payload = await requestPlaybooksApi({
|
|
7736
|
+
playbooksPath: "file",
|
|
7737
|
+
playbookId: command.playbookId,
|
|
7738
|
+
filePath: command.filePath,
|
|
7739
|
+
baseUrl: credentials.baseUrl,
|
|
7740
|
+
token: credentials.token,
|
|
7741
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
7742
|
+
});
|
|
7743
|
+
printPlaybooksPayload(command, io, payload, typeof payload.content === "string"
|
|
7744
|
+
? payload.content
|
|
7745
|
+
: JSON.stringify(payload, null, 2));
|
|
7746
|
+
return 0;
|
|
7747
|
+
}
|
|
6763
7748
|
async function runDbCapabilities(command, io, runtime) {
|
|
6764
7749
|
const credentials = await resolveDbCredentials(command, runtime);
|
|
6765
7750
|
const payload = await requestDbApi({
|
|
@@ -7376,6 +8361,18 @@ export async function runTenderCli(args, io = {
|
|
|
7376
8361
|
else if (command.topic === "auth status") {
|
|
7377
8362
|
io.stdout(authStatusHelp());
|
|
7378
8363
|
}
|
|
8364
|
+
else if (command.topic === "playbooks") {
|
|
8365
|
+
io.stdout(playbooksHelp());
|
|
8366
|
+
}
|
|
8367
|
+
else if (command.topic === "playbooks list") {
|
|
8368
|
+
io.stdout(playbooksListHelp());
|
|
8369
|
+
}
|
|
8370
|
+
else if (command.topic === "playbooks get") {
|
|
8371
|
+
io.stdout(playbooksGetHelp());
|
|
8372
|
+
}
|
|
8373
|
+
else if (command.topic === "playbooks file") {
|
|
8374
|
+
io.stdout(playbooksFileHelp());
|
|
8375
|
+
}
|
|
7379
8376
|
else if (command.topic === "auth") {
|
|
7380
8377
|
io.stdout(authHelp());
|
|
7381
8378
|
}
|
|
@@ -7433,6 +8430,9 @@ export async function runTenderCli(args, io = {
|
|
|
7433
8430
|
else if (command.topic === "app preview watch") {
|
|
7434
8431
|
io.stdout(previewWatchHelp());
|
|
7435
8432
|
}
|
|
8433
|
+
else if (command.topic === "app agent heartbeat") {
|
|
8434
|
+
io.stdout(agentHeartbeatHelp());
|
|
8435
|
+
}
|
|
7436
8436
|
else if (command.topic === "app publish") {
|
|
7437
8437
|
io.stdout(publishHelp());
|
|
7438
8438
|
}
|
|
@@ -7525,6 +8525,15 @@ export async function runTenderCli(args, io = {
|
|
|
7525
8525
|
if (command.command === "auth status") {
|
|
7526
8526
|
return await runAuthStatus(command, io, runtime);
|
|
7527
8527
|
}
|
|
8528
|
+
if (command.command === "playbooks list") {
|
|
8529
|
+
return await runPlaybooksList(command, io, runtime);
|
|
8530
|
+
}
|
|
8531
|
+
if (command.command === "playbooks get") {
|
|
8532
|
+
return await runPlaybooksGet(command, io, runtime);
|
|
8533
|
+
}
|
|
8534
|
+
if (command.command === "playbooks file") {
|
|
8535
|
+
return await runPlaybooksFile(command, io, runtime);
|
|
8536
|
+
}
|
|
7528
8537
|
if (command.command === "app list") {
|
|
7529
8538
|
return await runArtifactsList(command, io, runtime);
|
|
7530
8539
|
}
|
|
@@ -7573,6 +8582,9 @@ export async function runTenderCli(args, io = {
|
|
|
7573
8582
|
if (command.command === "app preview watch") {
|
|
7574
8583
|
return await runPreviewWatch(command, io, runtime);
|
|
7575
8584
|
}
|
|
8585
|
+
if (command.command === "app agent heartbeat") {
|
|
8586
|
+
return await runAgentHeartbeat(command, io, runtime);
|
|
8587
|
+
}
|
|
7576
8588
|
if (command.command === "app publish") {
|
|
7577
8589
|
return await runPublish(command, io, runtime);
|
|
7578
8590
|
}
|
|
@@ -7639,10 +8651,17 @@ export async function runTenderCli(args, io = {
|
|
|
7639
8651
|
if (command.command === "app git remote") {
|
|
7640
8652
|
return await runGitRemote(command, io, runtime);
|
|
7641
8653
|
}
|
|
7642
|
-
|
|
8654
|
+
if (command.command === "app doctor") {
|
|
8655
|
+
return await runDoctor(command, io);
|
|
8656
|
+
}
|
|
8657
|
+
throw new TenderCliUsageError("Unhandled command.");
|
|
7643
8658
|
}
|
|
7644
8659
|
catch (error) {
|
|
7645
|
-
if (args.includes("--json") &&
|
|
8660
|
+
if (args.includes("--json") &&
|
|
8661
|
+
((args[0] === "app" &&
|
|
8662
|
+
(args[1] === "db" ||
|
|
8663
|
+
(args[1] === "agent" && args[2] === "heartbeat"))) ||
|
|
8664
|
+
args[0] === "playbooks")) {
|
|
7646
8665
|
io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
|
|
7647
8666
|
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
7648
8667
|
}
|
|
@@ -7659,6 +8678,9 @@ export function shouldReadEntrypointStdin(args) {
|
|
|
7659
8678
|
return (args[0] === "app" &&
|
|
7660
8679
|
((args[1] === "git" && args[2] === "credential") ||
|
|
7661
8680
|
(args[1] === "analytics" && hasFlagValue(args, "--spec", "-")) ||
|
|
8681
|
+
(args[1] === "agent" &&
|
|
8682
|
+
args[2] === "heartbeat" &&
|
|
8683
|
+
hasFlagValue(args, "--input", "-")) ||
|
|
7662
8684
|
(args[1] === "db" &&
|
|
7663
8685
|
args[2] === "query" &&
|
|
7664
8686
|
hasFlagValue(args, "--file", "-"))));
|