@tenderprompt/cli 0.1.15 → 0.1.16
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 +701 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ tender capabilities --json
|
|
|
56
56
|
|
|
57
57
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
58
58
|
|
|
59
|
-
tender app init artifact_123 --dir ./widget --json
|
|
59
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
60
60
|
|
|
61
61
|
cd ./widget
|
|
62
62
|
npm run check
|
|
@@ -70,7 +70,7 @@ tender app publish artifact_123 --commit "$(git rev-parse HEAD)" --confirm publi
|
|
|
70
70
|
For agents, keep the pattern simple:
|
|
71
71
|
|
|
72
72
|
1. start with `tender capabilities --json`
|
|
73
|
-
2. use `tender app init` to clone/bootstrap an existing app checkout
|
|
73
|
+
2. use `tender app init --preview` to clone/bootstrap an existing app checkout
|
|
74
74
|
3. inspect and edit files locally
|
|
75
75
|
4. run `tender app doctor` and local project checks
|
|
76
76
|
5. push source with Git
|
|
@@ -128,8 +128,9 @@ tender --version
|
|
|
128
128
|
tender capabilities --json
|
|
129
129
|
tender app list --json
|
|
130
130
|
tender app create --name "Exit Intent Widget" --json
|
|
131
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
131
132
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
132
|
-
tender app init artifact_123 --dir ./widget --json
|
|
133
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
133
134
|
tender app bindings outbound --id shopify --host admin.shopify.com --dry-run --json
|
|
134
135
|
tender app context status artifact_123 --dir ./widget --json
|
|
135
136
|
tender app doctor --dir ./widget --json
|
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.2",
|
|
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.",
|
|
@@ -136,8 +136,9 @@ Examples:
|
|
|
136
136
|
tender auth status --json
|
|
137
137
|
tender app list --json
|
|
138
138
|
tender app create --name "Exit Intent Widget" --json
|
|
139
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
139
140
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
140
|
-
tender app init artifact_123 --dir ./widget --json
|
|
141
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
141
142
|
tender app validate artifact_123 --json
|
|
142
143
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
143
144
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
@@ -208,7 +209,7 @@ function appHelp() {
|
|
|
208
209
|
Commands:
|
|
209
210
|
tender app list List Tender App records visible to the current token
|
|
210
211
|
tender app create --name <name>
|
|
211
|
-
Create a new Tender App
|
|
212
|
+
Create a new Tender App, optionally initialize source, and preview
|
|
212
213
|
tender app update <app-id> --name <name>
|
|
213
214
|
Update app metadata
|
|
214
215
|
tender app init <app-id>
|
|
@@ -223,6 +224,8 @@ Commands:
|
|
|
223
224
|
Show preview job status by latest, commit, or job id
|
|
224
225
|
tender app preview watch <app-id>
|
|
225
226
|
Replay lifecycle events by latest, commit, or job id
|
|
227
|
+
tender app agent heartbeat <app-id>
|
|
228
|
+
Send a concise agent status update to the workspace
|
|
226
229
|
tender app publish <app-id>
|
|
227
230
|
Publish after dry-run or explicit confirmation
|
|
228
231
|
tender app publish status <app-id>
|
|
@@ -258,7 +261,7 @@ Commands:
|
|
|
258
261
|
tender app db query <app-id> --env <preview|published> --sql <sql>
|
|
259
262
|
Run one read-only app database query
|
|
260
263
|
tender app context fetch <app-id>
|
|
261
|
-
Fetch AGENTS.md, skills, and Tender
|
|
264
|
+
Fetch AGENTS.md, skills, and Tender project context
|
|
262
265
|
tender app context status <app-id>
|
|
263
266
|
Show managed context freshness for a checkout
|
|
264
267
|
tender app context refresh <app-id>
|
|
@@ -276,13 +279,15 @@ Commands:
|
|
|
276
279
|
Examples:
|
|
277
280
|
tender app list --json
|
|
278
281
|
tender app create --name "Exit Intent Widget" --json
|
|
282
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
279
283
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
280
|
-
tender app init artifact_123 --dir ./widget --json
|
|
284
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
281
285
|
tender app validate artifact_123 --json
|
|
282
286
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
283
287
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
284
288
|
tender app preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
285
289
|
tender app preview watch artifact_123 --job job_123 --stream --json
|
|
290
|
+
tender app agent heartbeat artifact_123 --source codex --status working --phase editing --summary "Updating the result screen" --json
|
|
286
291
|
tender app publish artifact_123 --dry-run --json
|
|
287
292
|
tender app publish status artifact_123 --job job_123 --json
|
|
288
293
|
tender app publish watch artifact_123 --job job_123 --stream --json
|
|
@@ -561,10 +566,18 @@ Examples:
|
|
|
561
566
|
TENDER_API_TOKEN=tpat_... tender app list --base-url https://app.tenderprompt.com --json`;
|
|
562
567
|
}
|
|
563
568
|
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]
|
|
569
|
+
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
570
|
|
|
566
571
|
Options:
|
|
567
572
|
--name <name> App name.
|
|
573
|
+
--init Initialize a local checkout after creating the app.
|
|
574
|
+
--dir <path> Local checkout directory for --init. Defaults to current directory.
|
|
575
|
+
--scaffold <value> API-derived scaffold for empty app source: server-backed or none. Defaults to server-backed.
|
|
576
|
+
--no-scaffold Alias for --scaffold none.
|
|
577
|
+
--preview After --init writes and pushes initial source, request a preview build.
|
|
578
|
+
--remote <name> Git remote name for --init. Defaults to tender.
|
|
579
|
+
--ttl <value> Deprecated for Tender-hosted Git remotes; accepted for compatibility.
|
|
580
|
+
--force Overwrite conflicting managed files during --init.
|
|
568
581
|
--target-pack <id> Target pack id. Defaults to the API default.
|
|
569
582
|
--target-pack-version <value> Target pack version. Defaults to the API default.
|
|
570
583
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
@@ -574,6 +587,7 @@ Options:
|
|
|
574
587
|
|
|
575
588
|
Examples:
|
|
576
589
|
tender app create --name "Exit Intent Widget" --json
|
|
590
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
577
591
|
tender app create --name "Exit Intent Widget" --profile account --json`;
|
|
578
592
|
}
|
|
579
593
|
function artifactsUpdateHelp() {
|
|
@@ -590,11 +604,14 @@ Examples:
|
|
|
590
604
|
tender app update artifact_123 --name "Sale Widget" --json`;
|
|
591
605
|
}
|
|
592
606
|
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]
|
|
607
|
+
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
608
|
|
|
595
609
|
Options:
|
|
596
610
|
--dir <path> Local checkout directory to create/update. Defaults to current directory.
|
|
597
611
|
--remote <name> Git remote name to add or update. Defaults to tender.
|
|
612
|
+
--scaffold <value> API-derived scaffold for empty app source: server-backed or none. Defaults to server-backed.
|
|
613
|
+
--no-scaffold Alias for --scaffold none.
|
|
614
|
+
--preview Request a preview build for the initialized source commit.
|
|
598
615
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
599
616
|
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
600
617
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
@@ -606,6 +623,7 @@ Options:
|
|
|
606
623
|
Examples:
|
|
607
624
|
tender app init artifact_123 --dir ./widget --dry-run --json
|
|
608
625
|
tender app init artifact_123 --dir ./widget --json
|
|
626
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
609
627
|
tender app init artifact_123 --dir ./widget --force --json`;
|
|
610
628
|
}
|
|
611
629
|
function artifactsContextFetchHelp() {
|
|
@@ -833,6 +851,34 @@ Examples:
|
|
|
833
851
|
tender app publish artifact_123 --dry-run --json
|
|
834
852
|
tender app publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json`;
|
|
835
853
|
}
|
|
854
|
+
function agentHeartbeatHelp() {
|
|
855
|
+
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]
|
|
856
|
+
|
|
857
|
+
Options:
|
|
858
|
+
--source <value> Agent identity. Use codex first, claude-code for Claude Code, or other with --source-label.
|
|
859
|
+
--source-label <text> Optional display name when --source other is not specific enough.
|
|
860
|
+
--status <value> connected, working, waiting_for_user, or needs_attention.
|
|
861
|
+
--phase <value> planning, exploring, editing, pushing, validating, building_preview, preview_updating, watching_preview, or waiting.
|
|
862
|
+
--summary <text> Short status line shown in the workspace.
|
|
863
|
+
--detail <text> Optional detail shown in activity history.
|
|
864
|
+
--current-task <text> Optional current task label.
|
|
865
|
+
--commit <sha> Commit SHA related to this update.
|
|
866
|
+
--preview-job <job-id> Preview lifecycle job related to this update.
|
|
867
|
+
--ttl-seconds <seconds> Freshness window. The API clamps this to a bounded range.
|
|
868
|
+
--requested-action <value> none, answer_question, review_preview, or approve_publish. Defaults to none.
|
|
869
|
+
--interruptible Mark the agent as interruptible.
|
|
870
|
+
--input <file|-> Read additional heartbeat JSON fields from a file or stdin. Flags override JSON fields.
|
|
871
|
+
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
872
|
+
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
873
|
+
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
874
|
+
--json Emit stable JSON for coding agents.
|
|
875
|
+
|
|
876
|
+
Examples:
|
|
877
|
+
tender app agent heartbeat artifact_123 --source codex --status connected --summary "Loaded the Tender Prompt skill" --json
|
|
878
|
+
tender app agent heartbeat artifact_123 --source codex --status working --phase exploring --summary "Reading the app structure" --json
|
|
879
|
+
tender app agent heartbeat artifact_123 --source claude-code --status needs_attention --summary "Waiting for an API key decision" --requested-action answer_question --json
|
|
880
|
+
tender app agent heartbeat artifact_123 --input - --json < heartbeat.json`;
|
|
881
|
+
}
|
|
836
882
|
function publishStatusHelp() {
|
|
837
883
|
return `Usage: tender app publish status <app-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
838
884
|
|
|
@@ -878,6 +924,7 @@ function tenderCliCapabilities() {
|
|
|
878
924
|
"tender --help",
|
|
879
925
|
"tender auth --help",
|
|
880
926
|
"tender app --help",
|
|
927
|
+
"tender app agent heartbeat --help",
|
|
881
928
|
"tender app analytics --help",
|
|
882
929
|
"tender app db --help",
|
|
883
930
|
"tender app bindings outbound --help",
|
|
@@ -892,17 +939,28 @@ function tenderCliCapabilities() {
|
|
|
892
939
|
"tender auth login --device --profile edit-preview --artifact <artifact-id> --ttl 7d --json",
|
|
893
940
|
],
|
|
894
941
|
},
|
|
942
|
+
{
|
|
943
|
+
name: "create_new_app_with_preview",
|
|
944
|
+
when: "When the user wants a new Tender App that is immediately visible.",
|
|
945
|
+
commands: [
|
|
946
|
+
"tender app create --name <name> --init --dir <dir> --preview --json",
|
|
947
|
+
],
|
|
948
|
+
notes: [
|
|
949
|
+
"create --init uses the API-derived scaffold when the new app has no source.",
|
|
950
|
+
"--preview requests a preview build for the initial source commit after the push.",
|
|
951
|
+
],
|
|
952
|
+
},
|
|
895
953
|
{
|
|
896
954
|
name: "clone_or_bootstrap_existing_app",
|
|
897
955
|
when: "When the user wants to download or work on an existing app locally.",
|
|
898
956
|
commands: [
|
|
899
957
|
"tender app init <artifact-id> --dir <dir> --dry-run --json",
|
|
900
|
-
"tender app init <artifact-id> --dir <dir> --json",
|
|
958
|
+
"tender app init <artifact-id> --dir <dir> --preview --json",
|
|
901
959
|
],
|
|
902
960
|
notes: [
|
|
903
961
|
"init is the normal clone/bootstrap command.",
|
|
904
962
|
"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
|
|
963
|
+
"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
964
|
"Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
|
|
907
965
|
],
|
|
908
966
|
},
|
|
@@ -910,14 +968,18 @@ function tenderCliCapabilities() {
|
|
|
910
968
|
name: "local_edit_preview_publish",
|
|
911
969
|
when: "When editing app source from a local checkout.",
|
|
912
970
|
commands: [
|
|
971
|
+
"tender app agent heartbeat <artifact-id> --source codex --status connected --summary \"Loaded Tender Prompt context\" --json",
|
|
913
972
|
"tender app doctor --dir <dir> --json",
|
|
973
|
+
"tender app agent heartbeat <artifact-id> --source codex --status working --phase editing --summary \"Editing app files\" --json",
|
|
914
974
|
"tender app git setup <artifact-id> --dir <dir> --dry-run --json",
|
|
915
975
|
"git push tender main",
|
|
976
|
+
"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
977
|
"tender app preview <artifact-id> --commit $(git rev-parse HEAD) --stream --json",
|
|
917
978
|
"tender app publish <artifact-id> --dry-run --json",
|
|
918
979
|
"tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json",
|
|
919
980
|
],
|
|
920
981
|
notes: [
|
|
982
|
+
"Send heartbeat updates only at meaningful milestones or when waiting for user input; do not send rapid polling updates.",
|
|
921
983
|
"Publishing is production-facing and requires explicit user intent.",
|
|
922
984
|
],
|
|
923
985
|
},
|
|
@@ -996,12 +1058,25 @@ function tenderCliCapabilities() {
|
|
|
996
1058
|
writes: false,
|
|
997
1059
|
},
|
|
998
1060
|
{
|
|
999
|
-
command: "tender app
|
|
1000
|
-
purpose: "
|
|
1061
|
+
command: "tender app create --name <name> --init --dir <dir> --preview --json",
|
|
1062
|
+
purpose: "Create a new Tender App, initialize API-derived source in a local checkout, push it, and request a preview build.",
|
|
1063
|
+
requiresAuth: true,
|
|
1064
|
+
writes: true,
|
|
1065
|
+
dryRun: false,
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
command: "tender app init <artifact-id> --dir <dir> --preview --json",
|
|
1069
|
+
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
1070
|
requiresAuth: true,
|
|
1002
1071
|
writes: true,
|
|
1003
1072
|
dryRun: true,
|
|
1004
1073
|
},
|
|
1074
|
+
{
|
|
1075
|
+
command: "tender app agent heartbeat <artifact-id> --source codex --status working --summary <text> --json",
|
|
1076
|
+
purpose: "Send a concise workspace feedback update from a coding agent.",
|
|
1077
|
+
requiresAuth: true,
|
|
1078
|
+
writes: true,
|
|
1079
|
+
},
|
|
1005
1080
|
{
|
|
1006
1081
|
command: "tender app context fetch <artifact-id> --dir <dir> --json",
|
|
1007
1082
|
purpose: "Fetch managed AGENTS.md, skills, and Tender context into an existing checkout.",
|
|
@@ -1285,6 +1360,12 @@ function parseTtl(value) {
|
|
|
1285
1360
|
}
|
|
1286
1361
|
return value;
|
|
1287
1362
|
}
|
|
1363
|
+
function parseScaffoldTemplate(value) {
|
|
1364
|
+
if (value !== "server-backed" && value !== "none") {
|
|
1365
|
+
throw new TenderCliUsageError("--scaffold must be one of server-backed or none.");
|
|
1366
|
+
}
|
|
1367
|
+
return value;
|
|
1368
|
+
}
|
|
1288
1369
|
function parseGitRemoteArgs(args) {
|
|
1289
1370
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1290
1371
|
return { command: "help", topic: "app git remote" };
|
|
@@ -2045,6 +2126,13 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2045
2126
|
let name = "";
|
|
2046
2127
|
let targetPackId = null;
|
|
2047
2128
|
let targetPackVersion = null;
|
|
2129
|
+
let init = false;
|
|
2130
|
+
let dir = ".";
|
|
2131
|
+
let remoteName = "tender";
|
|
2132
|
+
let ttl = "7d";
|
|
2133
|
+
let force = false;
|
|
2134
|
+
let scaffold = "server-backed";
|
|
2135
|
+
let preview = false;
|
|
2048
2136
|
let baseUrl = null;
|
|
2049
2137
|
let flagToken = null;
|
|
2050
2138
|
let profileName = null;
|
|
@@ -2055,6 +2143,22 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2055
2143
|
json = true;
|
|
2056
2144
|
continue;
|
|
2057
2145
|
}
|
|
2146
|
+
if (arg === "--init") {
|
|
2147
|
+
init = true;
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
2150
|
+
if (arg === "--preview") {
|
|
2151
|
+
preview = true;
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
if (arg === "--force") {
|
|
2155
|
+
force = true;
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
if (arg === "--no-scaffold") {
|
|
2159
|
+
scaffold = "none";
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2058
2162
|
if (arg === "--name") {
|
|
2059
2163
|
const value = args[index + 1];
|
|
2060
2164
|
if (!value || value.startsWith("-")) {
|
|
@@ -2082,6 +2186,30 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2082
2186
|
index += 1;
|
|
2083
2187
|
continue;
|
|
2084
2188
|
}
|
|
2189
|
+
if (arg === "--dir") {
|
|
2190
|
+
const value = args[index + 1];
|
|
2191
|
+
if (!value || value.startsWith("-")) {
|
|
2192
|
+
throw new TenderCliUsageError("--dir requires a path.");
|
|
2193
|
+
}
|
|
2194
|
+
dir = value;
|
|
2195
|
+
index += 1;
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (arg === "--remote") {
|
|
2199
|
+
remoteName = parseRemoteName(args[index + 1]);
|
|
2200
|
+
index += 1;
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (arg === "--ttl") {
|
|
2204
|
+
ttl = parseTtl(args[index + 1]);
|
|
2205
|
+
index += 1;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
if (arg === "--scaffold") {
|
|
2209
|
+
scaffold = parseScaffoldTemplate(args[index + 1]);
|
|
2210
|
+
index += 1;
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2085
2213
|
if (arg === "--base-url") {
|
|
2086
2214
|
baseUrl = parseBaseUrlFlag(args, index);
|
|
2087
2215
|
index += 1;
|
|
@@ -2102,11 +2230,38 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2102
2230
|
if (!name.trim()) {
|
|
2103
2231
|
throw new TenderCliUsageError("--name is required. Example: tender app create --name \"Exit Intent Widget\" --json");
|
|
2104
2232
|
}
|
|
2233
|
+
if (!init) {
|
|
2234
|
+
if (preview) {
|
|
2235
|
+
throw new TenderCliUsageError("--preview requires --init. Example: tender app create --name \"Exit Intent Widget\" --init --dir ./widget --preview --json");
|
|
2236
|
+
}
|
|
2237
|
+
if (force) {
|
|
2238
|
+
throw new TenderCliUsageError("--force is only valid with --init.");
|
|
2239
|
+
}
|
|
2240
|
+
if (dir !== ".") {
|
|
2241
|
+
throw new TenderCliUsageError("--dir is only valid with --init.");
|
|
2242
|
+
}
|
|
2243
|
+
if (remoteName !== "tender") {
|
|
2244
|
+
throw new TenderCliUsageError("--remote is only valid with --init.");
|
|
2245
|
+
}
|
|
2246
|
+
if (ttl !== "7d") {
|
|
2247
|
+
throw new TenderCliUsageError("--ttl is only valid with --init.");
|
|
2248
|
+
}
|
|
2249
|
+
if (scaffold !== "server-backed") {
|
|
2250
|
+
throw new TenderCliUsageError("--scaffold is only valid with --init.");
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2105
2253
|
return {
|
|
2106
2254
|
command: "app create",
|
|
2107
2255
|
name: name.trim(),
|
|
2108
2256
|
targetPackId,
|
|
2109
2257
|
targetPackVersion,
|
|
2258
|
+
init,
|
|
2259
|
+
dir,
|
|
2260
|
+
remoteName,
|
|
2261
|
+
ttl,
|
|
2262
|
+
force,
|
|
2263
|
+
scaffold,
|
|
2264
|
+
preview,
|
|
2110
2265
|
baseUrl,
|
|
2111
2266
|
flagToken,
|
|
2112
2267
|
profileName,
|
|
@@ -2185,6 +2340,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2185
2340
|
let flagToken = null;
|
|
2186
2341
|
let profileName = null;
|
|
2187
2342
|
let ttl = "7d";
|
|
2343
|
+
let scaffold = "server-backed";
|
|
2344
|
+
let preview = false;
|
|
2188
2345
|
let dryRun = false;
|
|
2189
2346
|
let force = false;
|
|
2190
2347
|
let json = false;
|
|
@@ -2202,6 +2359,14 @@ function parseArtifactsInitArgs(args) {
|
|
|
2202
2359
|
force = true;
|
|
2203
2360
|
continue;
|
|
2204
2361
|
}
|
|
2362
|
+
if (arg === "--preview") {
|
|
2363
|
+
preview = true;
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
if (arg === "--no-scaffold") {
|
|
2367
|
+
scaffold = "none";
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2205
2370
|
if (arg === "--dir") {
|
|
2206
2371
|
const value = args[index + 1];
|
|
2207
2372
|
if (!value || value.startsWith("-")) {
|
|
@@ -2236,6 +2401,11 @@ function parseArtifactsInitArgs(args) {
|
|
|
2236
2401
|
index += 1;
|
|
2237
2402
|
continue;
|
|
2238
2403
|
}
|
|
2404
|
+
if (arg === "--scaffold") {
|
|
2405
|
+
scaffold = parseScaffoldTemplate(args[index + 1]);
|
|
2406
|
+
index += 1;
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2239
2409
|
throw new TenderCliUsageError(`Unknown app init option: ${arg}`);
|
|
2240
2410
|
}
|
|
2241
2411
|
return {
|
|
@@ -2247,6 +2417,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2247
2417
|
flagToken,
|
|
2248
2418
|
profileName,
|
|
2249
2419
|
ttl,
|
|
2420
|
+
scaffold,
|
|
2421
|
+
preview,
|
|
2250
2422
|
dryRun,
|
|
2251
2423
|
force,
|
|
2252
2424
|
json,
|
|
@@ -3798,6 +3970,204 @@ function parseDbQueryArgs(args) {
|
|
|
3798
3970
|
...parsed,
|
|
3799
3971
|
};
|
|
3800
3972
|
}
|
|
3973
|
+
const AGENT_HEARTBEAT_SOURCE_VALUES = new Set([
|
|
3974
|
+
"codex",
|
|
3975
|
+
"claude-code",
|
|
3976
|
+
"tender-cli",
|
|
3977
|
+
"git",
|
|
3978
|
+
"other",
|
|
3979
|
+
]);
|
|
3980
|
+
const AGENT_HEARTBEAT_STATUS_VALUES = new Set([
|
|
3981
|
+
"connected",
|
|
3982
|
+
"working",
|
|
3983
|
+
"waiting_for_user",
|
|
3984
|
+
"needs_attention",
|
|
3985
|
+
]);
|
|
3986
|
+
const AGENT_HEARTBEAT_PHASE_VALUES = new Set([
|
|
3987
|
+
"planning",
|
|
3988
|
+
"exploring",
|
|
3989
|
+
"editing",
|
|
3990
|
+
"pushing",
|
|
3991
|
+
"validating",
|
|
3992
|
+
"building_preview",
|
|
3993
|
+
"preview_updating",
|
|
3994
|
+
"watching_preview",
|
|
3995
|
+
"waiting",
|
|
3996
|
+
]);
|
|
3997
|
+
const AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES = new Set([
|
|
3998
|
+
"answer_question",
|
|
3999
|
+
"review_preview",
|
|
4000
|
+
"approve_publish",
|
|
4001
|
+
"none",
|
|
4002
|
+
]);
|
|
4003
|
+
function parseAgentHeartbeatEnum(value, flag, allowed) {
|
|
4004
|
+
const parsed = parseRequiredFlagValue(value, flag);
|
|
4005
|
+
if (!allowed.has(parsed)) {
|
|
4006
|
+
throw new TenderCliUsageError(`${flag} must be one of ${Array.from(allowed).join(", ")}.`);
|
|
4007
|
+
}
|
|
4008
|
+
return parsed;
|
|
4009
|
+
}
|
|
4010
|
+
function parseAgentHeartbeatInputPath(value) {
|
|
4011
|
+
if (!value || (value !== "-" && value.startsWith("-"))) {
|
|
4012
|
+
throw new TenderCliUsageError("--input requires a JSON file path or - for stdin.");
|
|
4013
|
+
}
|
|
4014
|
+
return value;
|
|
4015
|
+
}
|
|
4016
|
+
function parseAgentHeartbeatArgs(args) {
|
|
4017
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
4018
|
+
return { command: "help", topic: "app agent heartbeat" };
|
|
4019
|
+
}
|
|
4020
|
+
const artifactId = args[0];
|
|
4021
|
+
if (!artifactId || artifactId.startsWith("-")) {
|
|
4022
|
+
throw new TenderCliUsageError("app id is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
4023
|
+
}
|
|
4024
|
+
let baseUrl = null;
|
|
4025
|
+
let flagToken = null;
|
|
4026
|
+
let profileName = null;
|
|
4027
|
+
let inputPath = null;
|
|
4028
|
+
let source = null;
|
|
4029
|
+
let sourceLabel = null;
|
|
4030
|
+
let status = null;
|
|
4031
|
+
let phase = null;
|
|
4032
|
+
let summary = null;
|
|
4033
|
+
let detail = null;
|
|
4034
|
+
let currentTask = null;
|
|
4035
|
+
let commitSha = null;
|
|
4036
|
+
let previewJobId = null;
|
|
4037
|
+
let ttlSeconds = null;
|
|
4038
|
+
let interruptible = false;
|
|
4039
|
+
let requestedAction = null;
|
|
4040
|
+
let json = false;
|
|
4041
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
4042
|
+
const arg = args[index];
|
|
4043
|
+
const sourceAssignment = parseFlagAssignment(arg, "--source");
|
|
4044
|
+
if (sourceAssignment !== null) {
|
|
4045
|
+
source = parseAgentHeartbeatEnum(sourceAssignment, "--source", AGENT_HEARTBEAT_SOURCE_VALUES);
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
const statusAssignment = parseFlagAssignment(arg, "--status");
|
|
4049
|
+
if (statusAssignment !== null) {
|
|
4050
|
+
status = parseAgentHeartbeatEnum(statusAssignment, "--status", AGENT_HEARTBEAT_STATUS_VALUES);
|
|
4051
|
+
continue;
|
|
4052
|
+
}
|
|
4053
|
+
const phaseAssignment = parseFlagAssignment(arg, "--phase");
|
|
4054
|
+
if (phaseAssignment !== null) {
|
|
4055
|
+
phase = parseAgentHeartbeatEnum(phaseAssignment, "--phase", AGENT_HEARTBEAT_PHASE_VALUES);
|
|
4056
|
+
continue;
|
|
4057
|
+
}
|
|
4058
|
+
const requestedActionAssignment = parseFlagAssignment(arg, "--requested-action");
|
|
4059
|
+
if (requestedActionAssignment !== null) {
|
|
4060
|
+
requestedAction = parseAgentHeartbeatEnum(requestedActionAssignment, "--requested-action", AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES);
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
4063
|
+
const ttlAssignment = parseFlagAssignment(arg, "--ttl-seconds");
|
|
4064
|
+
if (ttlAssignment !== null) {
|
|
4065
|
+
ttlSeconds = parsePositiveIntegerFlag(ttlAssignment, "--ttl-seconds");
|
|
4066
|
+
continue;
|
|
4067
|
+
}
|
|
4068
|
+
const inputAssignment = parseFlagAssignment(arg, "--input");
|
|
4069
|
+
if (inputAssignment !== null) {
|
|
4070
|
+
inputPath = parseAgentHeartbeatInputPath(inputAssignment);
|
|
4071
|
+
continue;
|
|
4072
|
+
}
|
|
4073
|
+
const textAssignments = [
|
|
4074
|
+
["--source-label", (value) => { sourceLabel = value; }],
|
|
4075
|
+
["--summary", (value) => { summary = value; }],
|
|
4076
|
+
["--detail", (value) => { detail = value; }],
|
|
4077
|
+
["--current-task", (value) => { currentTask = value; }],
|
|
4078
|
+
["--commit", (value) => { commitSha = value; }],
|
|
4079
|
+
["--preview-job", (value) => { previewJobId = value; }],
|
|
4080
|
+
];
|
|
4081
|
+
const matchedAssignment = textAssignments.find(([flag]) => arg.startsWith(`${flag}=`));
|
|
4082
|
+
if (matchedAssignment) {
|
|
4083
|
+
const [flag, assign] = matchedAssignment;
|
|
4084
|
+
assign(parseRequiredFlagValue(parseFlagAssignment(arg, flag) ?? undefined, flag));
|
|
4085
|
+
continue;
|
|
4086
|
+
}
|
|
4087
|
+
if (arg === "--json") {
|
|
4088
|
+
json = true;
|
|
4089
|
+
continue;
|
|
4090
|
+
}
|
|
4091
|
+
if (arg === "--interruptible") {
|
|
4092
|
+
interruptible = true;
|
|
4093
|
+
continue;
|
|
4094
|
+
}
|
|
4095
|
+
if (arg === "--base-url") {
|
|
4096
|
+
baseUrl = parseBaseUrlFlag(args, index);
|
|
4097
|
+
index += 1;
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
if (arg === "--token") {
|
|
4101
|
+
flagToken = parseTokenFlag(args, index);
|
|
4102
|
+
index += 1;
|
|
4103
|
+
continue;
|
|
4104
|
+
}
|
|
4105
|
+
if (arg === "--profile") {
|
|
4106
|
+
profileName = parseProfileName(args[index + 1], "--profile");
|
|
4107
|
+
index += 1;
|
|
4108
|
+
continue;
|
|
4109
|
+
}
|
|
4110
|
+
if (arg === "--source") {
|
|
4111
|
+
source = parseAgentHeartbeatEnum(args[index + 1], "--source", AGENT_HEARTBEAT_SOURCE_VALUES);
|
|
4112
|
+
index += 1;
|
|
4113
|
+
continue;
|
|
4114
|
+
}
|
|
4115
|
+
if (arg === "--status") {
|
|
4116
|
+
status = parseAgentHeartbeatEnum(args[index + 1], "--status", AGENT_HEARTBEAT_STATUS_VALUES);
|
|
4117
|
+
index += 1;
|
|
4118
|
+
continue;
|
|
4119
|
+
}
|
|
4120
|
+
if (arg === "--phase") {
|
|
4121
|
+
phase = parseAgentHeartbeatEnum(args[index + 1], "--phase", AGENT_HEARTBEAT_PHASE_VALUES);
|
|
4122
|
+
index += 1;
|
|
4123
|
+
continue;
|
|
4124
|
+
}
|
|
4125
|
+
if (arg === "--requested-action") {
|
|
4126
|
+
requestedAction = parseAgentHeartbeatEnum(args[index + 1], "--requested-action", AGENT_HEARTBEAT_REQUESTED_ACTION_VALUES);
|
|
4127
|
+
index += 1;
|
|
4128
|
+
continue;
|
|
4129
|
+
}
|
|
4130
|
+
if (arg === "--ttl-seconds") {
|
|
4131
|
+
ttlSeconds = parsePositiveIntegerFlag(args[index + 1], "--ttl-seconds");
|
|
4132
|
+
index += 1;
|
|
4133
|
+
continue;
|
|
4134
|
+
}
|
|
4135
|
+
if (arg === "--input") {
|
|
4136
|
+
inputPath = parseAgentHeartbeatInputPath(args[index + 1]);
|
|
4137
|
+
index += 1;
|
|
4138
|
+
continue;
|
|
4139
|
+
}
|
|
4140
|
+
const matchedFlag = textAssignments.find(([flag]) => arg === flag);
|
|
4141
|
+
if (matchedFlag) {
|
|
4142
|
+
const [flag, assign] = matchedFlag;
|
|
4143
|
+
assign(parseRequiredFlagValue(args[index + 1], flag));
|
|
4144
|
+
index += 1;
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
4147
|
+
throw new TenderCliUsageError(`Unknown app agent heartbeat option: ${arg}`);
|
|
4148
|
+
}
|
|
4149
|
+
return {
|
|
4150
|
+
command: "app agent heartbeat",
|
|
4151
|
+
artifactId,
|
|
4152
|
+
baseUrl,
|
|
4153
|
+
flagToken,
|
|
4154
|
+
profileName,
|
|
4155
|
+
inputPath,
|
|
4156
|
+
source,
|
|
4157
|
+
sourceLabel,
|
|
4158
|
+
status,
|
|
4159
|
+
phase,
|
|
4160
|
+
summary,
|
|
4161
|
+
detail,
|
|
4162
|
+
currentTask,
|
|
4163
|
+
commitSha,
|
|
4164
|
+
previewJobId,
|
|
4165
|
+
ttlSeconds,
|
|
4166
|
+
interruptible,
|
|
4167
|
+
requestedAction,
|
|
4168
|
+
json,
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
3801
4171
|
function parseTenderArgs(args) {
|
|
3802
4172
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
3803
4173
|
return { command: "help", topic: "root" };
|
|
@@ -3857,6 +4227,18 @@ function parseTenderArgs(args) {
|
|
|
3857
4227
|
if (resourceArgs[1] === "context" && resourceArgs[2] === "refresh") {
|
|
3858
4228
|
return parseContextRefreshArgs(resourceArgs.slice(3));
|
|
3859
4229
|
}
|
|
4230
|
+
if (resourceArgs[1] === "agent" &&
|
|
4231
|
+
(resourceArgs[2] === undefined ||
|
|
4232
|
+
resourceArgs[2] === "--help" ||
|
|
4233
|
+
resourceArgs[2] === "-h")) {
|
|
4234
|
+
return { command: "help", topic: "app agent heartbeat" };
|
|
4235
|
+
}
|
|
4236
|
+
if (resourceArgs[1] === "agent" && resourceArgs[2] === "heartbeat") {
|
|
4237
|
+
return parseAgentHeartbeatArgs(resourceArgs.slice(3));
|
|
4238
|
+
}
|
|
4239
|
+
if (resourceArgs[1] === "agent") {
|
|
4240
|
+
throw new TenderCliUsageError(`Unknown app agent command: ${resourceArgs[2]}`);
|
|
4241
|
+
}
|
|
3860
4242
|
if (resourceArgs[1] === "bindings" && resourceArgs[2] === "outbound") {
|
|
3861
4243
|
return parseBindingsOutboundArgs(resourceArgs.slice(3));
|
|
3862
4244
|
}
|
|
@@ -5246,6 +5628,31 @@ async function requestAgentContext(input) {
|
|
|
5246
5628
|
files: payload.files,
|
|
5247
5629
|
};
|
|
5248
5630
|
}
|
|
5631
|
+
async function requestArtifactScaffold(input) {
|
|
5632
|
+
const params = new URLSearchParams({ template: input.template });
|
|
5633
|
+
const response = await input.fetcher(`${input.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/scaffold?${params.toString()}`, {
|
|
5634
|
+
headers: {
|
|
5635
|
+
authorization: `Bearer ${input.token}`,
|
|
5636
|
+
},
|
|
5637
|
+
});
|
|
5638
|
+
const payload = await parseJsonResponse(response);
|
|
5639
|
+
if (!response.ok ||
|
|
5640
|
+
!payload?.ok ||
|
|
5641
|
+
!payload.files ||
|
|
5642
|
+
!payload.revision ||
|
|
5643
|
+
payload.template !== input.template) {
|
|
5644
|
+
throw new Error(payload?.message ??
|
|
5645
|
+
payload?.reasonCode ??
|
|
5646
|
+
`Artifact scaffold request failed with HTTP ${response.status}.`);
|
|
5647
|
+
}
|
|
5648
|
+
return {
|
|
5649
|
+
...payload,
|
|
5650
|
+
artifactId: payload.artifactId ?? input.artifactId,
|
|
5651
|
+
template: payload.template,
|
|
5652
|
+
revision: payload.revision,
|
|
5653
|
+
files: payload.files,
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5249
5656
|
async function runContextFetch(command, io, runtime) {
|
|
5250
5657
|
const credentials = await resolveApiCredentials({
|
|
5251
5658
|
baseUrl: command.baseUrl,
|
|
@@ -5454,8 +5861,33 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5454
5861
|
payload?.reasonCode ??
|
|
5455
5862
|
`Artifact create request failed with HTTP ${response.status}.`);
|
|
5456
5863
|
}
|
|
5864
|
+
let initResult = null;
|
|
5865
|
+
let initExitCode = null;
|
|
5866
|
+
if (command.init) {
|
|
5867
|
+
const initStdout = [];
|
|
5868
|
+
initExitCode = await runArtifactsInit({
|
|
5869
|
+
command: "app init",
|
|
5870
|
+
artifactId: payload.artifact.artifactId,
|
|
5871
|
+
dir: command.dir,
|
|
5872
|
+
remoteName: command.remoteName,
|
|
5873
|
+
baseUrl: credentials.baseUrl,
|
|
5874
|
+
flagToken: command.flagToken,
|
|
5875
|
+
profileName: credentials.profileName,
|
|
5876
|
+
ttl: command.ttl,
|
|
5877
|
+
scaffold: command.scaffold,
|
|
5878
|
+
preview: command.preview,
|
|
5879
|
+
dryRun: false,
|
|
5880
|
+
force: command.force,
|
|
5881
|
+
json: true,
|
|
5882
|
+
}, {
|
|
5883
|
+
stdout: (message) => initStdout.push(message),
|
|
5884
|
+
stderr: io.stderr,
|
|
5885
|
+
}, runtime);
|
|
5886
|
+
const initPayload = initStdout.join("\n").trim();
|
|
5887
|
+
initResult = initPayload ? JSON.parse(initPayload) : null;
|
|
5888
|
+
}
|
|
5457
5889
|
const result = {
|
|
5458
|
-
ok: true,
|
|
5890
|
+
ok: initExitCode === null ? true : initExitCode === 0,
|
|
5459
5891
|
command: command.command,
|
|
5460
5892
|
baseUrl: credentials.baseUrl,
|
|
5461
5893
|
authSource: credentials.source,
|
|
@@ -5463,7 +5895,9 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5463
5895
|
tenantId: payload.tenantId ?? null,
|
|
5464
5896
|
appId: payload.artifact.artifactId,
|
|
5465
5897
|
artifact: payload.artifact,
|
|
5898
|
+
init: initResult,
|
|
5466
5899
|
next: {
|
|
5900
|
+
init: `tender app init ${payload.artifact.artifactId} --dir . --preview`,
|
|
5467
5901
|
contextFetch: `tender app context fetch ${payload.artifact.artifactId} --dir .`,
|
|
5468
5902
|
gitSetup: `tender app git setup ${payload.artifact.artifactId} --dir .`,
|
|
5469
5903
|
},
|
|
@@ -5476,10 +5910,13 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5476
5910
|
`app_id: ${payload.artifact.artifactId}`,
|
|
5477
5911
|
`name: ${payload.artifact.name}`,
|
|
5478
5912
|
`preview_url: ${payload.artifact.previewUrl ?? ""}`,
|
|
5479
|
-
`next: ${result.next.
|
|
5480
|
-
|
|
5913
|
+
initExitCode === null ? `next: ${result.next.init}` : null,
|
|
5914
|
+
initExitCode !== null ? `init_exit_code: ${initExitCode}` : null,
|
|
5915
|
+
]
|
|
5916
|
+
.filter((line) => Boolean(line))
|
|
5917
|
+
.join("\n"));
|
|
5481
5918
|
}
|
|
5482
|
-
return 0;
|
|
5919
|
+
return initExitCode ?? 0;
|
|
5483
5920
|
}
|
|
5484
5921
|
async function runArtifactsUpdate(command, io, runtime) {
|
|
5485
5922
|
const credentials = await resolveApiCredentials({
|
|
@@ -5563,7 +6000,7 @@ function shellQuote(value) {
|
|
|
5563
6000
|
function createCredentialHelperCommand(input) {
|
|
5564
6001
|
const parts = [
|
|
5565
6002
|
"tender",
|
|
5566
|
-
"
|
|
6003
|
+
"app",
|
|
5567
6004
|
"git",
|
|
5568
6005
|
"credential",
|
|
5569
6006
|
shellQuote(input.artifactId),
|
|
@@ -5736,6 +6173,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5736
6173
|
skippedReason: "local_head_exists",
|
|
5737
6174
|
commitExitCode: null,
|
|
5738
6175
|
pushExitCode: null,
|
|
6176
|
+
commitSha: null,
|
|
5739
6177
|
};
|
|
5740
6178
|
}
|
|
5741
6179
|
if (input.sourceCheckout.checkedOut) {
|
|
@@ -5746,6 +6184,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5746
6184
|
skippedReason: "source_checked_out",
|
|
5747
6185
|
commitExitCode: null,
|
|
5748
6186
|
pushExitCode: null,
|
|
6187
|
+
commitSha: null,
|
|
5749
6188
|
};
|
|
5750
6189
|
}
|
|
5751
6190
|
if (input.sourceCheckout.skippedReason !== "remote_default_branch_missing") {
|
|
@@ -5756,6 +6195,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5756
6195
|
skippedReason: "remote_unavailable",
|
|
5757
6196
|
commitExitCode: null,
|
|
5758
6197
|
pushExitCode: null,
|
|
6198
|
+
commitSha: null,
|
|
5759
6199
|
};
|
|
5760
6200
|
}
|
|
5761
6201
|
const paths = [...new Set(input.paths)].sort();
|
|
@@ -5767,6 +6207,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5767
6207
|
skippedReason: "no_files_to_seed",
|
|
5768
6208
|
commitExitCode: null,
|
|
5769
6209
|
pushExitCode: null,
|
|
6210
|
+
commitSha: null,
|
|
5770
6211
|
};
|
|
5771
6212
|
}
|
|
5772
6213
|
await runGitCommand({
|
|
@@ -5779,6 +6220,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5779
6220
|
cwd: input.root,
|
|
5780
6221
|
args: ["commit", "-m", "Seed Tender App scaffold"],
|
|
5781
6222
|
});
|
|
6223
|
+
const commitShaResult = await runGitCommand({
|
|
6224
|
+
runtime: input.runtime,
|
|
6225
|
+
cwd: input.root,
|
|
6226
|
+
args: ["rev-parse", "HEAD"],
|
|
6227
|
+
});
|
|
5782
6228
|
const pushResult = await runGitCommand({
|
|
5783
6229
|
runtime: input.runtime,
|
|
5784
6230
|
cwd: input.root,
|
|
@@ -5791,6 +6237,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5791
6237
|
skippedReason: null,
|
|
5792
6238
|
commitExitCode: commitResult.exitCode,
|
|
5793
6239
|
pushExitCode: pushResult.exitCode,
|
|
6240
|
+
commitSha: commitShaResult.stdout.trim() || null,
|
|
5794
6241
|
};
|
|
5795
6242
|
}
|
|
5796
6243
|
async function requestGitRemoteLease(input) {
|
|
@@ -5808,6 +6255,26 @@ async function requestGitRemoteLease(input) {
|
|
|
5808
6255
|
}
|
|
5809
6256
|
return payload.git;
|
|
5810
6257
|
}
|
|
6258
|
+
async function requestPreviewRebuildJob(input) {
|
|
6259
|
+
const response = await input.fetcher(`${input.credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(input.artifactId)}/preview/rebuild`, {
|
|
6260
|
+
method: "POST",
|
|
6261
|
+
headers: {
|
|
6262
|
+
authorization: `Bearer ${input.credentials.token}`,
|
|
6263
|
+
"content-type": "application/json",
|
|
6264
|
+
},
|
|
6265
|
+
body: JSON.stringify({ commitSha: input.commitSha }),
|
|
6266
|
+
});
|
|
6267
|
+
const payload = await parseJsonResponse(response);
|
|
6268
|
+
if (!response.ok || !payload?.ok) {
|
|
6269
|
+
throw new Error(payload?.message ??
|
|
6270
|
+
payload?.reasonCode ??
|
|
6271
|
+
`Artifact preview request failed with HTTP ${response.status}.`);
|
|
6272
|
+
}
|
|
6273
|
+
return {
|
|
6274
|
+
job: payload.job ?? null,
|
|
6275
|
+
result: payload.result ?? null,
|
|
6276
|
+
};
|
|
6277
|
+
}
|
|
5811
6278
|
function parseCredentialInput(stdin) {
|
|
5812
6279
|
const values = new Map();
|
|
5813
6280
|
for (const line of stdin.split(/\r?\n/)) {
|
|
@@ -6010,13 +6477,28 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6010
6477
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
6011
6478
|
commitExitCode: null,
|
|
6012
6479
|
pushExitCode: null,
|
|
6480
|
+
commitSha: null,
|
|
6013
6481
|
};
|
|
6014
6482
|
let sourceExport = {
|
|
6015
6483
|
attempted: false,
|
|
6484
|
+
source: null,
|
|
6485
|
+
template: null,
|
|
6486
|
+
revision: null,
|
|
6016
6487
|
filesWritten: 0,
|
|
6017
6488
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
6018
6489
|
files: [],
|
|
6019
6490
|
};
|
|
6491
|
+
let preview = {
|
|
6492
|
+
attempted: false,
|
|
6493
|
+
commitSha: null,
|
|
6494
|
+
skippedReason: command.preview
|
|
6495
|
+
? command.dryRun
|
|
6496
|
+
? "dry_run"
|
|
6497
|
+
: null
|
|
6498
|
+
: "not_requested",
|
|
6499
|
+
job: null,
|
|
6500
|
+
result: null,
|
|
6501
|
+
};
|
|
6020
6502
|
let seedPaths = [];
|
|
6021
6503
|
let plannedFiles = await planContextFiles({
|
|
6022
6504
|
root,
|
|
@@ -6052,12 +6534,31 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6052
6534
|
defaultBranch: git.defaultBranch,
|
|
6053
6535
|
});
|
|
6054
6536
|
if (sourceCheckout.skippedReason === "remote_default_branch_missing") {
|
|
6055
|
-
|
|
6537
|
+
let sourceFiles = await collectArtifactWorkspaceSourceFiles({
|
|
6056
6538
|
artifactId: command.artifactId,
|
|
6057
6539
|
baseUrl: credentials.baseUrl,
|
|
6058
6540
|
token: credentials.token,
|
|
6059
6541
|
fetcher,
|
|
6060
6542
|
});
|
|
6543
|
+
let source = sourceFiles.length > 0 ? "workspace" : null;
|
|
6544
|
+
let scaffoldRevision = null;
|
|
6545
|
+
let scaffoldTemplate = null;
|
|
6546
|
+
if (sourceFiles.length === 0 && command.scaffold !== "none") {
|
|
6547
|
+
const scaffold = await requestArtifactScaffold({
|
|
6548
|
+
artifactId: command.artifactId,
|
|
6549
|
+
template: command.scaffold,
|
|
6550
|
+
baseUrl: credentials.baseUrl,
|
|
6551
|
+
token: credentials.token,
|
|
6552
|
+
fetcher,
|
|
6553
|
+
});
|
|
6554
|
+
sourceFiles = scaffold.files.map((file) => ({
|
|
6555
|
+
path: file.path,
|
|
6556
|
+
content: file.content,
|
|
6557
|
+
}));
|
|
6558
|
+
source = "api_scaffold";
|
|
6559
|
+
scaffoldRevision = scaffold.revision;
|
|
6560
|
+
scaffoldTemplate = scaffold.template;
|
|
6561
|
+
}
|
|
6061
6562
|
const plannedSourceFiles = await planSourceFiles({
|
|
6062
6563
|
root,
|
|
6063
6564
|
files: sourceFiles,
|
|
@@ -6066,8 +6567,15 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6066
6567
|
const sourceConflicts = plannedSourceFiles.filter((file) => file.action === "conflict");
|
|
6067
6568
|
sourceExport = {
|
|
6068
6569
|
attempted: true,
|
|
6570
|
+
source,
|
|
6571
|
+
template: scaffoldTemplate,
|
|
6572
|
+
revision: scaffoldRevision,
|
|
6069
6573
|
filesWritten: 0,
|
|
6070
|
-
skippedReason: plannedSourceFiles.length === 0
|
|
6574
|
+
skippedReason: plannedSourceFiles.length === 0
|
|
6575
|
+
? command.scaffold === "none"
|
|
6576
|
+
? "scaffold_disabled"
|
|
6577
|
+
: "empty_workspace"
|
|
6578
|
+
: null,
|
|
6071
6579
|
files: plannedSourceFiles.map((file) => ({
|
|
6072
6580
|
path: file.path,
|
|
6073
6581
|
action: file.action,
|
|
@@ -6133,6 +6641,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6133
6641
|
else if (sourceCheckout.checkedOut) {
|
|
6134
6642
|
sourceExport = {
|
|
6135
6643
|
attempted: false,
|
|
6644
|
+
source: null,
|
|
6645
|
+
template: null,
|
|
6646
|
+
revision: null,
|
|
6136
6647
|
filesWritten: 0,
|
|
6137
6648
|
skippedReason: "source_checked_out",
|
|
6138
6649
|
files: [],
|
|
@@ -6141,6 +6652,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6141
6652
|
else if (sourceCheckout.skippedReason === "local_head_exists") {
|
|
6142
6653
|
sourceExport = {
|
|
6143
6654
|
attempted: false,
|
|
6655
|
+
source: null,
|
|
6656
|
+
template: null,
|
|
6657
|
+
revision: null,
|
|
6144
6658
|
filesWritten: 0,
|
|
6145
6659
|
skippedReason: "local_head_exists",
|
|
6146
6660
|
files: [],
|
|
@@ -6149,6 +6663,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6149
6663
|
else if (sourceCheckout.skippedReason === "remote_unavailable") {
|
|
6150
6664
|
sourceExport = {
|
|
6151
6665
|
attempted: false,
|
|
6666
|
+
source: null,
|
|
6667
|
+
template: null,
|
|
6668
|
+
revision: null,
|
|
6152
6669
|
filesWritten: 0,
|
|
6153
6670
|
skippedReason: "remote_unavailable",
|
|
6154
6671
|
files: [],
|
|
@@ -6220,6 +6737,45 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6220
6737
|
sourceCheckout,
|
|
6221
6738
|
paths: seedPaths,
|
|
6222
6739
|
});
|
|
6740
|
+
if (command.preview) {
|
|
6741
|
+
let commitSha = seedCommit.commitSha;
|
|
6742
|
+
if (!commitSha && (seedCommit.pushed || sourceCheckout.checkedOut)) {
|
|
6743
|
+
const headResult = await runGitCommand({
|
|
6744
|
+
runtime,
|
|
6745
|
+
cwd: root,
|
|
6746
|
+
args: ["rev-parse", "HEAD"],
|
|
6747
|
+
allowFailure: true,
|
|
6748
|
+
});
|
|
6749
|
+
commitSha =
|
|
6750
|
+
headResult.exitCode === 0 ? headResult.stdout.trim() || null : null;
|
|
6751
|
+
}
|
|
6752
|
+
if (!commitSha) {
|
|
6753
|
+
preview = {
|
|
6754
|
+
attempted: false,
|
|
6755
|
+
commitSha: null,
|
|
6756
|
+
skippedReason: seedCommit.pushed || sourceCheckout.checkedOut
|
|
6757
|
+
? "commit_unavailable"
|
|
6758
|
+
: "seed_not_pushed",
|
|
6759
|
+
job: null,
|
|
6760
|
+
result: null,
|
|
6761
|
+
};
|
|
6762
|
+
}
|
|
6763
|
+
else {
|
|
6764
|
+
const previewJob = await requestPreviewRebuildJob({
|
|
6765
|
+
artifactId: command.artifactId,
|
|
6766
|
+
commitSha,
|
|
6767
|
+
credentials,
|
|
6768
|
+
fetcher,
|
|
6769
|
+
});
|
|
6770
|
+
preview = {
|
|
6771
|
+
attempted: true,
|
|
6772
|
+
commitSha,
|
|
6773
|
+
skippedReason: null,
|
|
6774
|
+
job: previewJob.job,
|
|
6775
|
+
result: previewJob.result,
|
|
6776
|
+
};
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6223
6779
|
}
|
|
6224
6780
|
const result = {
|
|
6225
6781
|
ok: true,
|
|
@@ -6233,11 +6789,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6233
6789
|
defaultBranch: git.defaultBranch,
|
|
6234
6790
|
dryRun: command.dryRun,
|
|
6235
6791
|
force: command.force,
|
|
6792
|
+
scaffold: command.scaffold,
|
|
6236
6793
|
authSource: credentials.source,
|
|
6237
6794
|
profile: credentials.profileName,
|
|
6238
6795
|
sourceCheckout,
|
|
6239
6796
|
sourceExport,
|
|
6240
6797
|
seedCommit,
|
|
6798
|
+
preview,
|
|
6241
6799
|
summary: {
|
|
6242
6800
|
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
6243
6801
|
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
@@ -6267,8 +6825,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6267
6825
|
`context_revision: ${context.revision}`,
|
|
6268
6826
|
`dir: ${root}`,
|
|
6269
6827
|
`remote: ${command.remoteName}`,
|
|
6828
|
+
preview.attempted && preview.job?.jobId
|
|
6829
|
+
? `preview_job: ${preview.job.jobId}`
|
|
6830
|
+
: null,
|
|
6270
6831
|
`next: ${result.next}`,
|
|
6271
|
-
]
|
|
6832
|
+
]
|
|
6833
|
+
.filter((line) => Boolean(line))
|
|
6834
|
+
.join("\n"));
|
|
6272
6835
|
}
|
|
6273
6836
|
return 0;
|
|
6274
6837
|
}
|
|
@@ -6666,6 +7229,109 @@ async function runPublishWatch(command, io, runtime) {
|
|
|
6666
7229
|
}
|
|
6667
7230
|
return 0;
|
|
6668
7231
|
}
|
|
7232
|
+
async function readAgentHeartbeatInputObject(input) {
|
|
7233
|
+
if (!input.command.inputPath) {
|
|
7234
|
+
return {};
|
|
7235
|
+
}
|
|
7236
|
+
const raw = input.command.inputPath === "-"
|
|
7237
|
+
? input.stdin
|
|
7238
|
+
: await readFile(path.resolve(input.command.inputPath), "utf8");
|
|
7239
|
+
if (!raw) {
|
|
7240
|
+
throw new TenderCliUsageError("--input - requires heartbeat JSON on stdin.");
|
|
7241
|
+
}
|
|
7242
|
+
let parsed;
|
|
7243
|
+
try {
|
|
7244
|
+
parsed = JSON.parse(raw);
|
|
7245
|
+
}
|
|
7246
|
+
catch {
|
|
7247
|
+
throw new TenderCliUsageError("--input must contain valid JSON.");
|
|
7248
|
+
}
|
|
7249
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7250
|
+
throw new TenderCliUsageError("--input must contain a JSON object.");
|
|
7251
|
+
}
|
|
7252
|
+
return parsed;
|
|
7253
|
+
}
|
|
7254
|
+
function assignAgentHeartbeatField(body, field, value) {
|
|
7255
|
+
if (value !== null) {
|
|
7256
|
+
body[field] = value;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
async function buildAgentHeartbeatBody(command, runtime) {
|
|
7260
|
+
const body = await readAgentHeartbeatInputObject({
|
|
7261
|
+
command,
|
|
7262
|
+
stdin: runtime.stdin,
|
|
7263
|
+
});
|
|
7264
|
+
assignAgentHeartbeatField(body, "source", command.source);
|
|
7265
|
+
assignAgentHeartbeatField(body, "sourceLabel", command.sourceLabel);
|
|
7266
|
+
assignAgentHeartbeatField(body, "status", command.status);
|
|
7267
|
+
assignAgentHeartbeatField(body, "phase", command.phase);
|
|
7268
|
+
assignAgentHeartbeatField(body, "summary", command.summary);
|
|
7269
|
+
assignAgentHeartbeatField(body, "detail", command.detail);
|
|
7270
|
+
assignAgentHeartbeatField(body, "currentTask", command.currentTask);
|
|
7271
|
+
assignAgentHeartbeatField(body, "commitSha", command.commitSha);
|
|
7272
|
+
assignAgentHeartbeatField(body, "previewJobId", command.previewJobId);
|
|
7273
|
+
assignAgentHeartbeatField(body, "ttlSeconds", command.ttlSeconds);
|
|
7274
|
+
assignAgentHeartbeatField(body, "requestedAction", command.requestedAction);
|
|
7275
|
+
if (command.interruptible) {
|
|
7276
|
+
body.interruptible = true;
|
|
7277
|
+
}
|
|
7278
|
+
if (typeof body.source !== "string" || body.source.trim().length === 0) {
|
|
7279
|
+
throw new TenderCliUsageError("--source is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7280
|
+
}
|
|
7281
|
+
if (typeof body.status !== "string" || body.status.trim().length === 0) {
|
|
7282
|
+
throw new TenderCliUsageError("--status is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7283
|
+
}
|
|
7284
|
+
if (typeof body.summary !== "string" || body.summary.trim().length === 0) {
|
|
7285
|
+
throw new TenderCliUsageError("--summary is required. Example: tender app agent heartbeat <app-id> --source codex --status working --summary \"Reading files\" --json");
|
|
7286
|
+
}
|
|
7287
|
+
return body;
|
|
7288
|
+
}
|
|
7289
|
+
async function runAgentHeartbeat(command, io, runtime) {
|
|
7290
|
+
const credentials = await resolveApiCredentials({
|
|
7291
|
+
baseUrl: command.baseUrl,
|
|
7292
|
+
flagToken: command.flagToken,
|
|
7293
|
+
profileName: command.profileName,
|
|
7294
|
+
env: runtime.env ?? process.env,
|
|
7295
|
+
});
|
|
7296
|
+
const body = await buildAgentHeartbeatBody(command, runtime);
|
|
7297
|
+
const response = await (runtime.fetcher ?? fetch)(`${credentials.baseUrl}/api/v1/artifacts/${encodeURIComponent(command.artifactId)}/agent/heartbeat`, {
|
|
7298
|
+
method: "POST",
|
|
7299
|
+
headers: {
|
|
7300
|
+
authorization: `Bearer ${credentials.token}`,
|
|
7301
|
+
accept: "application/json",
|
|
7302
|
+
"content-type": "application/json",
|
|
7303
|
+
},
|
|
7304
|
+
body: JSON.stringify(body),
|
|
7305
|
+
});
|
|
7306
|
+
const payload = await parseJsonResponse(response);
|
|
7307
|
+
if (!response.ok || !payload?.ok) {
|
|
7308
|
+
throw new Error((typeof payload?.message === "string" && payload.message) ||
|
|
7309
|
+
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
7310
|
+
`Agent heartbeat failed with HTTP ${response.status}.`);
|
|
7311
|
+
}
|
|
7312
|
+
const result = {
|
|
7313
|
+
ok: true,
|
|
7314
|
+
command: command.command,
|
|
7315
|
+
baseUrl: credentials.baseUrl,
|
|
7316
|
+
authSource: credentials.source,
|
|
7317
|
+
profile: credentials.profileName,
|
|
7318
|
+
tenantId: payload.tenantId ?? null,
|
|
7319
|
+
artifactId: payload.artifactId ?? command.artifactId,
|
|
7320
|
+
activity: payload.activity ?? null,
|
|
7321
|
+
};
|
|
7322
|
+
if (command.json) {
|
|
7323
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
7324
|
+
}
|
|
7325
|
+
else {
|
|
7326
|
+
io.stdout([
|
|
7327
|
+
`app_id: ${result.artifactId}`,
|
|
7328
|
+
`heartbeat: accepted`,
|
|
7329
|
+
`status: ${String(body.status)}`,
|
|
7330
|
+
`summary: ${String(body.summary)}`,
|
|
7331
|
+
].join("\n"));
|
|
7332
|
+
}
|
|
7333
|
+
return 0;
|
|
7334
|
+
}
|
|
6669
7335
|
async function runGitRemote(command, io, runtime) {
|
|
6670
7336
|
const credentials = await resolveApiCredentials({
|
|
6671
7337
|
baseUrl: command.baseUrl,
|
|
@@ -7433,6 +8099,9 @@ export async function runTenderCli(args, io = {
|
|
|
7433
8099
|
else if (command.topic === "app preview watch") {
|
|
7434
8100
|
io.stdout(previewWatchHelp());
|
|
7435
8101
|
}
|
|
8102
|
+
else if (command.topic === "app agent heartbeat") {
|
|
8103
|
+
io.stdout(agentHeartbeatHelp());
|
|
8104
|
+
}
|
|
7436
8105
|
else if (command.topic === "app publish") {
|
|
7437
8106
|
io.stdout(publishHelp());
|
|
7438
8107
|
}
|
|
@@ -7573,6 +8242,9 @@ export async function runTenderCli(args, io = {
|
|
|
7573
8242
|
if (command.command === "app preview watch") {
|
|
7574
8243
|
return await runPreviewWatch(command, io, runtime);
|
|
7575
8244
|
}
|
|
8245
|
+
if (command.command === "app agent heartbeat") {
|
|
8246
|
+
return await runAgentHeartbeat(command, io, runtime);
|
|
8247
|
+
}
|
|
7576
8248
|
if (command.command === "app publish") {
|
|
7577
8249
|
return await runPublish(command, io, runtime);
|
|
7578
8250
|
}
|
|
@@ -7639,10 +8311,15 @@ export async function runTenderCli(args, io = {
|
|
|
7639
8311
|
if (command.command === "app git remote") {
|
|
7640
8312
|
return await runGitRemote(command, io, runtime);
|
|
7641
8313
|
}
|
|
7642
|
-
|
|
8314
|
+
if (command.command === "app doctor") {
|
|
8315
|
+
return await runDoctor(command, io);
|
|
8316
|
+
}
|
|
8317
|
+
throw new TenderCliUsageError("Unhandled command.");
|
|
7643
8318
|
}
|
|
7644
8319
|
catch (error) {
|
|
7645
|
-
if (args.includes("--json") &&
|
|
8320
|
+
if (args.includes("--json") &&
|
|
8321
|
+
args[0] === "app" &&
|
|
8322
|
+
(args[1] === "db" || (args[1] === "agent" && args[2] === "heartbeat"))) {
|
|
7646
8323
|
io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
|
|
7647
8324
|
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
7648
8325
|
}
|
|
@@ -7659,6 +8336,9 @@ export function shouldReadEntrypointStdin(args) {
|
|
|
7659
8336
|
return (args[0] === "app" &&
|
|
7660
8337
|
((args[1] === "git" && args[2] === "credential") ||
|
|
7661
8338
|
(args[1] === "analytics" && hasFlagValue(args, "--spec", "-")) ||
|
|
8339
|
+
(args[1] === "agent" &&
|
|
8340
|
+
args[2] === "heartbeat" &&
|
|
8341
|
+
hasFlagValue(args, "--input", "-")) ||
|
|
7662
8342
|
(args[1] === "db" &&
|
|
7663
8343
|
args[2] === "query" &&
|
|
7664
8344
|
hasFlagValue(args, "--file", "-"))));
|