@tenderprompt/cli 0.1.14 → 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 +9 -3
- package/bin/tender.js +1 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +784 -24
- 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.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.",
|
|
@@ -55,12 +55,14 @@ class TenderCliDbApiError extends Error {
|
|
|
55
55
|
reasonCode;
|
|
56
56
|
scope;
|
|
57
57
|
example;
|
|
58
|
-
|
|
58
|
+
nextAction;
|
|
59
|
+
constructor(message, reasonCode, scope, example, nextAction = null) {
|
|
59
60
|
super(message);
|
|
60
61
|
this.name = "TenderCliDbApiError";
|
|
61
62
|
this.reasonCode = reasonCode;
|
|
62
63
|
this.scope = scope;
|
|
63
64
|
this.example = example;
|
|
65
|
+
this.nextAction = nextAction;
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
const DEFAULT_PROFILE_NAME = "default";
|
|
@@ -73,6 +75,8 @@ function rootHelp() {
|
|
|
73
75
|
return `Usage: tender <command> [options]
|
|
74
76
|
|
|
75
77
|
Commands:
|
|
78
|
+
tender --version
|
|
79
|
+
Show the installed Tender CLI version
|
|
76
80
|
tender capabilities --json
|
|
77
81
|
Describe the CLI command surface for coding agents
|
|
78
82
|
tender auth login --device
|
|
@@ -126,13 +130,15 @@ Commands:
|
|
|
126
130
|
Regenerate src/env.d.ts from app.json and bindings
|
|
127
131
|
|
|
128
132
|
Examples:
|
|
133
|
+
tender --version
|
|
129
134
|
tender capabilities --json
|
|
130
135
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
131
136
|
tender auth status --json
|
|
132
137
|
tender app list --json
|
|
133
138
|
tender app create --name "Exit Intent Widget" --json
|
|
139
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
134
140
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
135
|
-
tender app init artifact_123 --dir ./widget --json
|
|
141
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
136
142
|
tender app validate artifact_123 --json
|
|
137
143
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
138
144
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
@@ -203,7 +209,7 @@ function appHelp() {
|
|
|
203
209
|
Commands:
|
|
204
210
|
tender app list List Tender App records visible to the current token
|
|
205
211
|
tender app create --name <name>
|
|
206
|
-
Create a new Tender App
|
|
212
|
+
Create a new Tender App, optionally initialize source, and preview
|
|
207
213
|
tender app update <app-id> --name <name>
|
|
208
214
|
Update app metadata
|
|
209
215
|
tender app init <app-id>
|
|
@@ -218,6 +224,8 @@ Commands:
|
|
|
218
224
|
Show preview job status by latest, commit, or job id
|
|
219
225
|
tender app preview watch <app-id>
|
|
220
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
|
|
221
229
|
tender app publish <app-id>
|
|
222
230
|
Publish after dry-run or explicit confirmation
|
|
223
231
|
tender app publish status <app-id>
|
|
@@ -253,7 +261,7 @@ Commands:
|
|
|
253
261
|
tender app db query <app-id> --env <preview|published> --sql <sql>
|
|
254
262
|
Run one read-only app database query
|
|
255
263
|
tender app context fetch <app-id>
|
|
256
|
-
Fetch AGENTS.md, skills, and Tender
|
|
264
|
+
Fetch AGENTS.md, skills, and Tender project context
|
|
257
265
|
tender app context status <app-id>
|
|
258
266
|
Show managed context freshness for a checkout
|
|
259
267
|
tender app context refresh <app-id>
|
|
@@ -271,13 +279,15 @@ Commands:
|
|
|
271
279
|
Examples:
|
|
272
280
|
tender app list --json
|
|
273
281
|
tender app create --name "Exit Intent Widget" --json
|
|
282
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
274
283
|
tender app update artifact_123 --name "Sale Widget" --json
|
|
275
|
-
tender app init artifact_123 --dir ./widget --json
|
|
284
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
276
285
|
tender app validate artifact_123 --json
|
|
277
286
|
tender app build artifact_123 --commit $(git rev-parse HEAD) --json
|
|
278
287
|
tender app preview artifact_123 --commit $(git rev-parse HEAD) --stream --json
|
|
279
288
|
tender app preview status artifact_123 --commit $(git rev-parse HEAD) --json
|
|
280
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
|
|
281
291
|
tender app publish artifact_123 --dry-run --json
|
|
282
292
|
tender app publish status artifact_123 --job job_123 --json
|
|
283
293
|
tender app publish watch artifact_123 --job job_123 --stream --json
|
|
@@ -315,6 +325,17 @@ copying long command lists into skill prompts.
|
|
|
315
325
|
Examples:
|
|
316
326
|
tender capabilities --json`;
|
|
317
327
|
}
|
|
328
|
+
function versionHelp() {
|
|
329
|
+
return `Usage: tender --version
|
|
330
|
+
tender -v
|
|
331
|
+
tender version [--json]
|
|
332
|
+
|
|
333
|
+
Prints the installed Tender CLI package version.
|
|
334
|
+
|
|
335
|
+
Examples:
|
|
336
|
+
tender --version
|
|
337
|
+
tender version --json`;
|
|
338
|
+
}
|
|
318
339
|
function analyticsHelp() {
|
|
319
340
|
return `Usage: tender app analytics <command> <app-id> [options]
|
|
320
341
|
|
|
@@ -545,10 +566,18 @@ Examples:
|
|
|
545
566
|
TENDER_API_TOKEN=tpat_... tender app list --base-url https://app.tenderprompt.com --json`;
|
|
546
567
|
}
|
|
547
568
|
function artifactsCreateHelp() {
|
|
548
|
-
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]
|
|
549
570
|
|
|
550
571
|
Options:
|
|
551
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.
|
|
552
581
|
--target-pack <id> Target pack id. Defaults to the API default.
|
|
553
582
|
--target-pack-version <value> Target pack version. Defaults to the API default.
|
|
554
583
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
@@ -558,6 +587,7 @@ Options:
|
|
|
558
587
|
|
|
559
588
|
Examples:
|
|
560
589
|
tender app create --name "Exit Intent Widget" --json
|
|
590
|
+
tender app create --name "Exit Intent Widget" --init --dir ./widget --preview --json
|
|
561
591
|
tender app create --name "Exit Intent Widget" --profile account --json`;
|
|
562
592
|
}
|
|
563
593
|
function artifactsUpdateHelp() {
|
|
@@ -574,11 +604,14 @@ Examples:
|
|
|
574
604
|
tender app update artifact_123 --name "Sale Widget" --json`;
|
|
575
605
|
}
|
|
576
606
|
function artifactsInitHelp() {
|
|
577
|
-
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]
|
|
578
608
|
|
|
579
609
|
Options:
|
|
580
610
|
--dir <path> Local checkout directory to create/update. Defaults to current directory.
|
|
581
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.
|
|
582
615
|
--base-url <url> Tender base URL. Defaults to TENDER_BASE_URL, saved profile base URL, or https://app.tenderprompt.com.
|
|
583
616
|
--token <token> Tender API token. Precedence: --token, explicit --profile, TENDER_API_TOKEN, saved default profile.
|
|
584
617
|
--profile <name> Local config profile name. Defaults to the saved default profile.
|
|
@@ -590,6 +623,7 @@ Options:
|
|
|
590
623
|
Examples:
|
|
591
624
|
tender app init artifact_123 --dir ./widget --dry-run --json
|
|
592
625
|
tender app init artifact_123 --dir ./widget --json
|
|
626
|
+
tender app init artifact_123 --dir ./widget --preview --json
|
|
593
627
|
tender app init artifact_123 --dir ./widget --force --json`;
|
|
594
628
|
}
|
|
595
629
|
function artifactsContextFetchHelp() {
|
|
@@ -817,6 +851,34 @@ Examples:
|
|
|
817
851
|
tender app publish artifact_123 --dry-run --json
|
|
818
852
|
tender app publish artifact_123 --commit $(git rev-parse HEAD) --confirm publish --stream --json`;
|
|
819
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
|
+
}
|
|
820
882
|
function publishStatusHelp() {
|
|
821
883
|
return `Usage: tender app publish status <app-id> --job <job-id> [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
822
884
|
|
|
@@ -858,9 +920,11 @@ function tenderCliCapabilities() {
|
|
|
858
920
|
discovery: {
|
|
859
921
|
primary: "npm exec --yes @tenderprompt/cli@latest -- capabilities --json",
|
|
860
922
|
layeredHelp: [
|
|
923
|
+
"tender --version",
|
|
861
924
|
"tender --help",
|
|
862
925
|
"tender auth --help",
|
|
863
926
|
"tender app --help",
|
|
927
|
+
"tender app agent heartbeat --help",
|
|
864
928
|
"tender app analytics --help",
|
|
865
929
|
"tender app db --help",
|
|
866
930
|
"tender app bindings outbound --help",
|
|
@@ -875,17 +939,28 @@ function tenderCliCapabilities() {
|
|
|
875
939
|
"tender auth login --device --profile edit-preview --artifact <artifact-id> --ttl 7d --json",
|
|
876
940
|
],
|
|
877
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
|
+
},
|
|
878
953
|
{
|
|
879
954
|
name: "clone_or_bootstrap_existing_app",
|
|
880
955
|
when: "When the user wants to download or work on an existing app locally.",
|
|
881
956
|
commands: [
|
|
882
957
|
"tender app init <artifact-id> --dir <dir> --dry-run --json",
|
|
883
|
-
"tender app init <artifact-id> --dir <dir> --json",
|
|
958
|
+
"tender app init <artifact-id> --dir <dir> --preview --json",
|
|
884
959
|
],
|
|
885
960
|
notes: [
|
|
886
961
|
"init is the normal clone/bootstrap command.",
|
|
887
962
|
"If the app already has artifact Git source, init fetches it and configures the tender remote.",
|
|
888
|
-
"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.",
|
|
889
964
|
"Use context fetch only when you already have a checkout and only need AGENTS.md, skills, or Tender context refreshed.",
|
|
890
965
|
],
|
|
891
966
|
},
|
|
@@ -893,14 +968,18 @@ function tenderCliCapabilities() {
|
|
|
893
968
|
name: "local_edit_preview_publish",
|
|
894
969
|
when: "When editing app source from a local checkout.",
|
|
895
970
|
commands: [
|
|
971
|
+
"tender app agent heartbeat <artifact-id> --source codex --status connected --summary \"Loaded Tender Prompt context\" --json",
|
|
896
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",
|
|
897
974
|
"tender app git setup <artifact-id> --dir <dir> --dry-run --json",
|
|
898
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",
|
|
899
977
|
"tender app preview <artifact-id> --commit $(git rev-parse HEAD) --stream --json",
|
|
900
978
|
"tender app publish <artifact-id> --dry-run --json",
|
|
901
979
|
"tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json",
|
|
902
980
|
],
|
|
903
981
|
notes: [
|
|
982
|
+
"Send heartbeat updates only at meaningful milestones or when waiting for user input; do not send rapid polling updates.",
|
|
904
983
|
"Publishing is production-facing and requires explicit user intent.",
|
|
905
984
|
],
|
|
906
985
|
},
|
|
@@ -960,6 +1039,18 @@ function tenderCliCapabilities() {
|
|
|
960
1039
|
},
|
|
961
1040
|
],
|
|
962
1041
|
commands: [
|
|
1042
|
+
{
|
|
1043
|
+
command: "tender --version",
|
|
1044
|
+
purpose: "Print the installed Tender CLI package version.",
|
|
1045
|
+
requiresAuth: false,
|
|
1046
|
+
writes: false,
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
command: "tender version --json",
|
|
1050
|
+
purpose: "Print the installed Tender CLI package version in a stable JSON shape.",
|
|
1051
|
+
requiresAuth: false,
|
|
1052
|
+
writes: false,
|
|
1053
|
+
},
|
|
963
1054
|
{
|
|
964
1055
|
command: "tender capabilities --json",
|
|
965
1056
|
purpose: "Discover this CLI surface in a machine-readable shape.",
|
|
@@ -967,12 +1058,25 @@ function tenderCliCapabilities() {
|
|
|
967
1058
|
writes: false,
|
|
968
1059
|
},
|
|
969
1060
|
{
|
|
970
|
-
command: "tender app
|
|
971
|
-
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.",
|
|
972
1070
|
requiresAuth: true,
|
|
973
1071
|
writes: true,
|
|
974
1072
|
dryRun: true,
|
|
975
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
|
+
},
|
|
976
1080
|
{
|
|
977
1081
|
command: "tender app context fetch <artifact-id> --dir <dir> --json",
|
|
978
1082
|
purpose: "Fetch managed AGENTS.md, skills, and Tender context into an existing checkout.",
|
|
@@ -1091,6 +1195,19 @@ function runCapabilities(command, io) {
|
|
|
1091
1195
|
}
|
|
1092
1196
|
return 0;
|
|
1093
1197
|
}
|
|
1198
|
+
function runVersion(command, io) {
|
|
1199
|
+
if (command.json) {
|
|
1200
|
+
io.stdout(JSON.stringify({
|
|
1201
|
+
ok: true,
|
|
1202
|
+
name: CLI_PACKAGE_NAME,
|
|
1203
|
+
version: CLI_PACKAGE_VERSION,
|
|
1204
|
+
}, null, 2));
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
io.stdout(CLI_PACKAGE_VERSION);
|
|
1208
|
+
}
|
|
1209
|
+
return 0;
|
|
1210
|
+
}
|
|
1094
1211
|
function normalizeBaseUrl(value) {
|
|
1095
1212
|
return (value ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1096
1213
|
}
|
|
@@ -1243,6 +1360,12 @@ function parseTtl(value) {
|
|
|
1243
1360
|
}
|
|
1244
1361
|
return value;
|
|
1245
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
|
+
}
|
|
1246
1369
|
function parseGitRemoteArgs(args) {
|
|
1247
1370
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1248
1371
|
return { command: "help", topic: "app git remote" };
|
|
@@ -2003,6 +2126,13 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2003
2126
|
let name = "";
|
|
2004
2127
|
let targetPackId = null;
|
|
2005
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;
|
|
2006
2136
|
let baseUrl = null;
|
|
2007
2137
|
let flagToken = null;
|
|
2008
2138
|
let profileName = null;
|
|
@@ -2013,6 +2143,22 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2013
2143
|
json = true;
|
|
2014
2144
|
continue;
|
|
2015
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
|
+
}
|
|
2016
2162
|
if (arg === "--name") {
|
|
2017
2163
|
const value = args[index + 1];
|
|
2018
2164
|
if (!value || value.startsWith("-")) {
|
|
@@ -2040,6 +2186,30 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2040
2186
|
index += 1;
|
|
2041
2187
|
continue;
|
|
2042
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
|
+
}
|
|
2043
2213
|
if (arg === "--base-url") {
|
|
2044
2214
|
baseUrl = parseBaseUrlFlag(args, index);
|
|
2045
2215
|
index += 1;
|
|
@@ -2060,11 +2230,38 @@ function parseArtifactsCreateArgs(args) {
|
|
|
2060
2230
|
if (!name.trim()) {
|
|
2061
2231
|
throw new TenderCliUsageError("--name is required. Example: tender app create --name \"Exit Intent Widget\" --json");
|
|
2062
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
|
+
}
|
|
2063
2253
|
return {
|
|
2064
2254
|
command: "app create",
|
|
2065
2255
|
name: name.trim(),
|
|
2066
2256
|
targetPackId,
|
|
2067
2257
|
targetPackVersion,
|
|
2258
|
+
init,
|
|
2259
|
+
dir,
|
|
2260
|
+
remoteName,
|
|
2261
|
+
ttl,
|
|
2262
|
+
force,
|
|
2263
|
+
scaffold,
|
|
2264
|
+
preview,
|
|
2068
2265
|
baseUrl,
|
|
2069
2266
|
flagToken,
|
|
2070
2267
|
profileName,
|
|
@@ -2143,6 +2340,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2143
2340
|
let flagToken = null;
|
|
2144
2341
|
let profileName = null;
|
|
2145
2342
|
let ttl = "7d";
|
|
2343
|
+
let scaffold = "server-backed";
|
|
2344
|
+
let preview = false;
|
|
2146
2345
|
let dryRun = false;
|
|
2147
2346
|
let force = false;
|
|
2148
2347
|
let json = false;
|
|
@@ -2160,6 +2359,14 @@ function parseArtifactsInitArgs(args) {
|
|
|
2160
2359
|
force = true;
|
|
2161
2360
|
continue;
|
|
2162
2361
|
}
|
|
2362
|
+
if (arg === "--preview") {
|
|
2363
|
+
preview = true;
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
if (arg === "--no-scaffold") {
|
|
2367
|
+
scaffold = "none";
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2163
2370
|
if (arg === "--dir") {
|
|
2164
2371
|
const value = args[index + 1];
|
|
2165
2372
|
if (!value || value.startsWith("-")) {
|
|
@@ -2194,6 +2401,11 @@ function parseArtifactsInitArgs(args) {
|
|
|
2194
2401
|
index += 1;
|
|
2195
2402
|
continue;
|
|
2196
2403
|
}
|
|
2404
|
+
if (arg === "--scaffold") {
|
|
2405
|
+
scaffold = parseScaffoldTemplate(args[index + 1]);
|
|
2406
|
+
index += 1;
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2197
2409
|
throw new TenderCliUsageError(`Unknown app init option: ${arg}`);
|
|
2198
2410
|
}
|
|
2199
2411
|
return {
|
|
@@ -2205,6 +2417,8 @@ function parseArtifactsInitArgs(args) {
|
|
|
2205
2417
|
flagToken,
|
|
2206
2418
|
profileName,
|
|
2207
2419
|
ttl,
|
|
2420
|
+
scaffold,
|
|
2421
|
+
preview,
|
|
2208
2422
|
dryRun,
|
|
2209
2423
|
force,
|
|
2210
2424
|
json,
|
|
@@ -2595,6 +2809,20 @@ function parseCapabilitiesArgs(args) {
|
|
|
2595
2809
|
}
|
|
2596
2810
|
return { command: "capabilities", json };
|
|
2597
2811
|
}
|
|
2812
|
+
function parseVersionArgs(args) {
|
|
2813
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2814
|
+
return { command: "help", topic: "version" };
|
|
2815
|
+
}
|
|
2816
|
+
let json = false;
|
|
2817
|
+
for (const arg of args) {
|
|
2818
|
+
if (arg === "--json") {
|
|
2819
|
+
json = true;
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
throw new TenderCliUsageError(`Unknown version option: ${arg}`);
|
|
2823
|
+
}
|
|
2824
|
+
return { command: "version", json };
|
|
2825
|
+
}
|
|
2598
2826
|
function parseBindingId(value) {
|
|
2599
2827
|
if (!value || value.startsWith("-")) {
|
|
2600
2828
|
throw new TenderCliUsageError("--id requires a binding id.");
|
|
@@ -3742,10 +3970,214 @@ function parseDbQueryArgs(args) {
|
|
|
3742
3970
|
...parsed,
|
|
3743
3971
|
};
|
|
3744
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
|
+
}
|
|
3745
4171
|
function parseTenderArgs(args) {
|
|
3746
4172
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
3747
4173
|
return { command: "help", topic: "root" };
|
|
3748
4174
|
}
|
|
4175
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
4176
|
+
return parseVersionArgs(args.slice(1));
|
|
4177
|
+
}
|
|
4178
|
+
if (args[0] === "version") {
|
|
4179
|
+
return parseVersionArgs(args.slice(1));
|
|
4180
|
+
}
|
|
3749
4181
|
if (args[0] === "capabilities") {
|
|
3750
4182
|
return parseCapabilitiesArgs(args.slice(1));
|
|
3751
4183
|
}
|
|
@@ -3795,6 +4227,18 @@ function parseTenderArgs(args) {
|
|
|
3795
4227
|
if (resourceArgs[1] === "context" && resourceArgs[2] === "refresh") {
|
|
3796
4228
|
return parseContextRefreshArgs(resourceArgs.slice(3));
|
|
3797
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
|
+
}
|
|
3798
4242
|
if (resourceArgs[1] === "bindings" && resourceArgs[2] === "outbound") {
|
|
3799
4243
|
return parseBindingsOutboundArgs(resourceArgs.slice(3));
|
|
3800
4244
|
}
|
|
@@ -4345,6 +4789,9 @@ async function requestDbApi(input) {
|
|
|
4345
4789
|
const message = (typeof payload?.message === "string" && payload.message) ||
|
|
4346
4790
|
(typeof payload?.reasonCode === "string" && payload.reasonCode) ||
|
|
4347
4791
|
`App database request failed with HTTP ${response.status}.`;
|
|
4792
|
+
const nextAction = typeof payload?.nextAction === "string" && payload.nextAction
|
|
4793
|
+
? payload.nextAction
|
|
4794
|
+
: null;
|
|
4348
4795
|
const example = dbCorrectedExample(input.artifactId, input.dbPath);
|
|
4349
4796
|
throw new TenderCliDbApiError([
|
|
4350
4797
|
message,
|
|
@@ -4354,7 +4801,10 @@ async function requestDbApi(input) {
|
|
|
4354
4801
|
].join("\n"), reasonCode, {
|
|
4355
4802
|
artifactId: input.artifactId,
|
|
4356
4803
|
surface: `db/${input.dbPath}`,
|
|
4357
|
-
|
|
4804
|
+
...(input.scopeEnvironment ?? input.environment
|
|
4805
|
+
? { environment: input.scopeEnvironment ?? input.environment }
|
|
4806
|
+
: {}),
|
|
4807
|
+
}, example, nextAction);
|
|
4358
4808
|
}
|
|
4359
4809
|
return payload;
|
|
4360
4810
|
}
|
|
@@ -5178,6 +5628,31 @@ async function requestAgentContext(input) {
|
|
|
5178
5628
|
files: payload.files,
|
|
5179
5629
|
};
|
|
5180
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
|
+
}
|
|
5181
5656
|
async function runContextFetch(command, io, runtime) {
|
|
5182
5657
|
const credentials = await resolveApiCredentials({
|
|
5183
5658
|
baseUrl: command.baseUrl,
|
|
@@ -5386,8 +5861,33 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5386
5861
|
payload?.reasonCode ??
|
|
5387
5862
|
`Artifact create request failed with HTTP ${response.status}.`);
|
|
5388
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
|
+
}
|
|
5389
5889
|
const result = {
|
|
5390
|
-
ok: true,
|
|
5890
|
+
ok: initExitCode === null ? true : initExitCode === 0,
|
|
5391
5891
|
command: command.command,
|
|
5392
5892
|
baseUrl: credentials.baseUrl,
|
|
5393
5893
|
authSource: credentials.source,
|
|
@@ -5395,7 +5895,9 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5395
5895
|
tenantId: payload.tenantId ?? null,
|
|
5396
5896
|
appId: payload.artifact.artifactId,
|
|
5397
5897
|
artifact: payload.artifact,
|
|
5898
|
+
init: initResult,
|
|
5398
5899
|
next: {
|
|
5900
|
+
init: `tender app init ${payload.artifact.artifactId} --dir . --preview`,
|
|
5399
5901
|
contextFetch: `tender app context fetch ${payload.artifact.artifactId} --dir .`,
|
|
5400
5902
|
gitSetup: `tender app git setup ${payload.artifact.artifactId} --dir .`,
|
|
5401
5903
|
},
|
|
@@ -5408,10 +5910,13 @@ async function runArtifactsCreate(command, io, runtime) {
|
|
|
5408
5910
|
`app_id: ${payload.artifact.artifactId}`,
|
|
5409
5911
|
`name: ${payload.artifact.name}`,
|
|
5410
5912
|
`preview_url: ${payload.artifact.previewUrl ?? ""}`,
|
|
5411
|
-
`next: ${result.next.
|
|
5412
|
-
|
|
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"));
|
|
5413
5918
|
}
|
|
5414
|
-
return 0;
|
|
5919
|
+
return initExitCode ?? 0;
|
|
5415
5920
|
}
|
|
5416
5921
|
async function runArtifactsUpdate(command, io, runtime) {
|
|
5417
5922
|
const credentials = await resolveApiCredentials({
|
|
@@ -5495,7 +6000,7 @@ function shellQuote(value) {
|
|
|
5495
6000
|
function createCredentialHelperCommand(input) {
|
|
5496
6001
|
const parts = [
|
|
5497
6002
|
"tender",
|
|
5498
|
-
"
|
|
6003
|
+
"app",
|
|
5499
6004
|
"git",
|
|
5500
6005
|
"credential",
|
|
5501
6006
|
shellQuote(input.artifactId),
|
|
@@ -5668,6 +6173,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5668
6173
|
skippedReason: "local_head_exists",
|
|
5669
6174
|
commitExitCode: null,
|
|
5670
6175
|
pushExitCode: null,
|
|
6176
|
+
commitSha: null,
|
|
5671
6177
|
};
|
|
5672
6178
|
}
|
|
5673
6179
|
if (input.sourceCheckout.checkedOut) {
|
|
@@ -5678,6 +6184,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5678
6184
|
skippedReason: "source_checked_out",
|
|
5679
6185
|
commitExitCode: null,
|
|
5680
6186
|
pushExitCode: null,
|
|
6187
|
+
commitSha: null,
|
|
5681
6188
|
};
|
|
5682
6189
|
}
|
|
5683
6190
|
if (input.sourceCheckout.skippedReason !== "remote_default_branch_missing") {
|
|
@@ -5688,6 +6195,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5688
6195
|
skippedReason: "remote_unavailable",
|
|
5689
6196
|
commitExitCode: null,
|
|
5690
6197
|
pushExitCode: null,
|
|
6198
|
+
commitSha: null,
|
|
5691
6199
|
};
|
|
5692
6200
|
}
|
|
5693
6201
|
const paths = [...new Set(input.paths)].sort();
|
|
@@ -5699,6 +6207,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5699
6207
|
skippedReason: "no_files_to_seed",
|
|
5700
6208
|
commitExitCode: null,
|
|
5701
6209
|
pushExitCode: null,
|
|
6210
|
+
commitSha: null,
|
|
5702
6211
|
};
|
|
5703
6212
|
}
|
|
5704
6213
|
await runGitCommand({
|
|
@@ -5711,6 +6220,11 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5711
6220
|
cwd: input.root,
|
|
5712
6221
|
args: ["commit", "-m", "Seed Tender App scaffold"],
|
|
5713
6222
|
});
|
|
6223
|
+
const commitShaResult = await runGitCommand({
|
|
6224
|
+
runtime: input.runtime,
|
|
6225
|
+
cwd: input.root,
|
|
6226
|
+
args: ["rev-parse", "HEAD"],
|
|
6227
|
+
});
|
|
5714
6228
|
const pushResult = await runGitCommand({
|
|
5715
6229
|
runtime: input.runtime,
|
|
5716
6230
|
cwd: input.root,
|
|
@@ -5723,6 +6237,7 @@ async function maybeSeedInitialArtifactGitCommit(input) {
|
|
|
5723
6237
|
skippedReason: null,
|
|
5724
6238
|
commitExitCode: commitResult.exitCode,
|
|
5725
6239
|
pushExitCode: pushResult.exitCode,
|
|
6240
|
+
commitSha: commitShaResult.stdout.trim() || null,
|
|
5726
6241
|
};
|
|
5727
6242
|
}
|
|
5728
6243
|
async function requestGitRemoteLease(input) {
|
|
@@ -5740,6 +6255,26 @@ async function requestGitRemoteLease(input) {
|
|
|
5740
6255
|
}
|
|
5741
6256
|
return payload.git;
|
|
5742
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
|
+
}
|
|
5743
6278
|
function parseCredentialInput(stdin) {
|
|
5744
6279
|
const values = new Map();
|
|
5745
6280
|
for (const line of stdin.split(/\r?\n/)) {
|
|
@@ -5942,13 +6477,28 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
5942
6477
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
5943
6478
|
commitExitCode: null,
|
|
5944
6479
|
pushExitCode: null,
|
|
6480
|
+
commitSha: null,
|
|
5945
6481
|
};
|
|
5946
6482
|
let sourceExport = {
|
|
5947
6483
|
attempted: false,
|
|
6484
|
+
source: null,
|
|
6485
|
+
template: null,
|
|
6486
|
+
revision: null,
|
|
5948
6487
|
filesWritten: 0,
|
|
5949
6488
|
skippedReason: command.dryRun ? "dry_run" : null,
|
|
5950
6489
|
files: [],
|
|
5951
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
|
+
};
|
|
5952
6502
|
let seedPaths = [];
|
|
5953
6503
|
let plannedFiles = await planContextFiles({
|
|
5954
6504
|
root,
|
|
@@ -5984,12 +6534,31 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
5984
6534
|
defaultBranch: git.defaultBranch,
|
|
5985
6535
|
});
|
|
5986
6536
|
if (sourceCheckout.skippedReason === "remote_default_branch_missing") {
|
|
5987
|
-
|
|
6537
|
+
let sourceFiles = await collectArtifactWorkspaceSourceFiles({
|
|
5988
6538
|
artifactId: command.artifactId,
|
|
5989
6539
|
baseUrl: credentials.baseUrl,
|
|
5990
6540
|
token: credentials.token,
|
|
5991
6541
|
fetcher,
|
|
5992
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
|
+
}
|
|
5993
6562
|
const plannedSourceFiles = await planSourceFiles({
|
|
5994
6563
|
root,
|
|
5995
6564
|
files: sourceFiles,
|
|
@@ -5998,8 +6567,15 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
5998
6567
|
const sourceConflicts = plannedSourceFiles.filter((file) => file.action === "conflict");
|
|
5999
6568
|
sourceExport = {
|
|
6000
6569
|
attempted: true,
|
|
6570
|
+
source,
|
|
6571
|
+
template: scaffoldTemplate,
|
|
6572
|
+
revision: scaffoldRevision,
|
|
6001
6573
|
filesWritten: 0,
|
|
6002
|
-
skippedReason: plannedSourceFiles.length === 0
|
|
6574
|
+
skippedReason: plannedSourceFiles.length === 0
|
|
6575
|
+
? command.scaffold === "none"
|
|
6576
|
+
? "scaffold_disabled"
|
|
6577
|
+
: "empty_workspace"
|
|
6578
|
+
: null,
|
|
6003
6579
|
files: plannedSourceFiles.map((file) => ({
|
|
6004
6580
|
path: file.path,
|
|
6005
6581
|
action: file.action,
|
|
@@ -6065,6 +6641,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6065
6641
|
else if (sourceCheckout.checkedOut) {
|
|
6066
6642
|
sourceExport = {
|
|
6067
6643
|
attempted: false,
|
|
6644
|
+
source: null,
|
|
6645
|
+
template: null,
|
|
6646
|
+
revision: null,
|
|
6068
6647
|
filesWritten: 0,
|
|
6069
6648
|
skippedReason: "source_checked_out",
|
|
6070
6649
|
files: [],
|
|
@@ -6073,6 +6652,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6073
6652
|
else if (sourceCheckout.skippedReason === "local_head_exists") {
|
|
6074
6653
|
sourceExport = {
|
|
6075
6654
|
attempted: false,
|
|
6655
|
+
source: null,
|
|
6656
|
+
template: null,
|
|
6657
|
+
revision: null,
|
|
6076
6658
|
filesWritten: 0,
|
|
6077
6659
|
skippedReason: "local_head_exists",
|
|
6078
6660
|
files: [],
|
|
@@ -6081,6 +6663,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6081
6663
|
else if (sourceCheckout.skippedReason === "remote_unavailable") {
|
|
6082
6664
|
sourceExport = {
|
|
6083
6665
|
attempted: false,
|
|
6666
|
+
source: null,
|
|
6667
|
+
template: null,
|
|
6668
|
+
revision: null,
|
|
6084
6669
|
filesWritten: 0,
|
|
6085
6670
|
skippedReason: "remote_unavailable",
|
|
6086
6671
|
files: [],
|
|
@@ -6152,6 +6737,45 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6152
6737
|
sourceCheckout,
|
|
6153
6738
|
paths: seedPaths,
|
|
6154
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
|
+
}
|
|
6155
6779
|
}
|
|
6156
6780
|
const result = {
|
|
6157
6781
|
ok: true,
|
|
@@ -6165,11 +6789,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6165
6789
|
defaultBranch: git.defaultBranch,
|
|
6166
6790
|
dryRun: command.dryRun,
|
|
6167
6791
|
force: command.force,
|
|
6792
|
+
scaffold: command.scaffold,
|
|
6168
6793
|
authSource: credentials.source,
|
|
6169
6794
|
profile: credentials.profileName,
|
|
6170
6795
|
sourceCheckout,
|
|
6171
6796
|
sourceExport,
|
|
6172
6797
|
seedCommit,
|
|
6798
|
+
preview,
|
|
6173
6799
|
summary: {
|
|
6174
6800
|
create: plannedFiles.filter((file) => file.action === "create").length,
|
|
6175
6801
|
overwrite: plannedFiles.filter((file) => file.action === "overwrite").length,
|
|
@@ -6199,8 +6825,13 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
6199
6825
|
`context_revision: ${context.revision}`,
|
|
6200
6826
|
`dir: ${root}`,
|
|
6201
6827
|
`remote: ${command.remoteName}`,
|
|
6828
|
+
preview.attempted && preview.job?.jobId
|
|
6829
|
+
? `preview_job: ${preview.job.jobId}`
|
|
6830
|
+
: null,
|
|
6202
6831
|
`next: ${result.next}`,
|
|
6203
|
-
]
|
|
6832
|
+
]
|
|
6833
|
+
.filter((line) => Boolean(line))
|
|
6834
|
+
.join("\n"));
|
|
6204
6835
|
}
|
|
6205
6836
|
return 0;
|
|
6206
6837
|
}
|
|
@@ -6598,6 +7229,109 @@ async function runPublishWatch(command, io, runtime) {
|
|
|
6598
7229
|
}
|
|
6599
7230
|
return 0;
|
|
6600
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
|
+
}
|
|
6601
7335
|
async function runGitRemote(command, io, runtime) {
|
|
6602
7336
|
const credentials = await resolveApiCredentials({
|
|
6603
7337
|
baseUrl: command.baseUrl,
|
|
@@ -6770,6 +7504,7 @@ async function runDbQuery(command, io, runtime) {
|
|
|
6770
7504
|
baseUrl: credentials.baseUrl,
|
|
6771
7505
|
token: credentials.token,
|
|
6772
7506
|
fetcher: runtime.fetcher ?? fetch,
|
|
7507
|
+
scopeEnvironment: command.environment,
|
|
6773
7508
|
body,
|
|
6774
7509
|
});
|
|
6775
7510
|
const rows = Array.isArray(payload.rows) ? payload.rows : [];
|
|
@@ -7270,15 +8005,20 @@ function exampleForError(error, args, command) {
|
|
|
7270
8005
|
const artifactId = artifactIdForError(args, command) ?? "artifact_123";
|
|
7271
8006
|
return dbPath ? dbCorrectedExample(artifactId, dbPath) : null;
|
|
7272
8007
|
}
|
|
8008
|
+
function nextActionForError(error) {
|
|
8009
|
+
return error instanceof TenderCliDbApiError ? error.nextAction : null;
|
|
8010
|
+
}
|
|
7273
8011
|
function cliErrorPayload(error, args, command) {
|
|
7274
8012
|
const scope = scopeForError(error, args, command);
|
|
7275
8013
|
const example = exampleForError(error, args, command);
|
|
8014
|
+
const nextAction = nextActionForError(error);
|
|
7276
8015
|
return {
|
|
7277
8016
|
ok: false,
|
|
7278
8017
|
command: commandNameForError(args, command),
|
|
7279
8018
|
reasonCode: cliErrorCode(error),
|
|
7280
8019
|
message: cliErrorMessage(error),
|
|
7281
8020
|
...(scope ? { scope } : {}),
|
|
8021
|
+
...(nextAction ? { nextAction } : {}),
|
|
7282
8022
|
...(example ? { example } : {}),
|
|
7283
8023
|
};
|
|
7284
8024
|
}
|
|
@@ -7293,6 +8033,9 @@ export async function runTenderCli(args, io = {
|
|
|
7293
8033
|
if (command.topic === "capabilities") {
|
|
7294
8034
|
io.stdout(capabilitiesHelp());
|
|
7295
8035
|
}
|
|
8036
|
+
else if (command.topic === "version") {
|
|
8037
|
+
io.stdout(versionHelp());
|
|
8038
|
+
}
|
|
7296
8039
|
else if (command.topic === "auth login") {
|
|
7297
8040
|
io.stdout(authLoginHelp());
|
|
7298
8041
|
}
|
|
@@ -7356,6 +8099,9 @@ export async function runTenderCli(args, io = {
|
|
|
7356
8099
|
else if (command.topic === "app preview watch") {
|
|
7357
8100
|
io.stdout(previewWatchHelp());
|
|
7358
8101
|
}
|
|
8102
|
+
else if (command.topic === "app agent heartbeat") {
|
|
8103
|
+
io.stdout(agentHeartbeatHelp());
|
|
8104
|
+
}
|
|
7359
8105
|
else if (command.topic === "app publish") {
|
|
7360
8106
|
io.stdout(publishHelp());
|
|
7361
8107
|
}
|
|
@@ -7439,6 +8185,9 @@ export async function runTenderCli(args, io = {
|
|
|
7439
8185
|
if (command.command === "capabilities") {
|
|
7440
8186
|
return runCapabilities(command, io);
|
|
7441
8187
|
}
|
|
8188
|
+
if (command.command === "version") {
|
|
8189
|
+
return runVersion(command, io);
|
|
8190
|
+
}
|
|
7442
8191
|
if (command.command === "auth login") {
|
|
7443
8192
|
return await runAuthLogin(command, io, runtime);
|
|
7444
8193
|
}
|
|
@@ -7493,6 +8242,9 @@ export async function runTenderCli(args, io = {
|
|
|
7493
8242
|
if (command.command === "app preview watch") {
|
|
7494
8243
|
return await runPreviewWatch(command, io, runtime);
|
|
7495
8244
|
}
|
|
8245
|
+
if (command.command === "app agent heartbeat") {
|
|
8246
|
+
return await runAgentHeartbeat(command, io, runtime);
|
|
8247
|
+
}
|
|
7496
8248
|
if (command.command === "app publish") {
|
|
7497
8249
|
return await runPublish(command, io, runtime);
|
|
7498
8250
|
}
|
|
@@ -7559,10 +8311,15 @@ export async function runTenderCli(args, io = {
|
|
|
7559
8311
|
if (command.command === "app git remote") {
|
|
7560
8312
|
return await runGitRemote(command, io, runtime);
|
|
7561
8313
|
}
|
|
7562
|
-
|
|
8314
|
+
if (command.command === "app doctor") {
|
|
8315
|
+
return await runDoctor(command, io);
|
|
8316
|
+
}
|
|
8317
|
+
throw new TenderCliUsageError("Unhandled command.");
|
|
7563
8318
|
}
|
|
7564
8319
|
catch (error) {
|
|
7565
|
-
if (args.includes("--json") &&
|
|
8320
|
+
if (args.includes("--json") &&
|
|
8321
|
+
args[0] === "app" &&
|
|
8322
|
+
(args[1] === "db" || (args[1] === "agent" && args[2] === "heartbeat"))) {
|
|
7566
8323
|
io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
|
|
7567
8324
|
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
7568
8325
|
}
|
|
@@ -7575,10 +8332,13 @@ const currentFile = fileURLToPath(import.meta.url);
|
|
|
7575
8332
|
function hasFlagValue(args, flag, value) {
|
|
7576
8333
|
return args.some((arg, index) => (arg === flag && args[index + 1] === value) || arg === `${flag}=${value}`);
|
|
7577
8334
|
}
|
|
7578
|
-
function shouldReadEntrypointStdin(args) {
|
|
8335
|
+
export function shouldReadEntrypointStdin(args) {
|
|
7579
8336
|
return (args[0] === "app" &&
|
|
7580
8337
|
((args[1] === "git" && args[2] === "credential") ||
|
|
7581
8338
|
(args[1] === "analytics" && hasFlagValue(args, "--spec", "-")) ||
|
|
8339
|
+
(args[1] === "agent" &&
|
|
8340
|
+
args[2] === "heartbeat" &&
|
|
8341
|
+
hasFlagValue(args, "--input", "-")) ||
|
|
7582
8342
|
(args[1] === "db" &&
|
|
7583
8343
|
args[2] === "query" &&
|
|
7584
8344
|
hasFlagValue(args, "--file", "-"))));
|