@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/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.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
- constructor(message, reasonCode, scope, example) {
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 in the current account
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 App scaffold files
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/scaffold files and configures the tender remote.",
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 init <artifact-id> --dir <dir> --json",
971
- purpose: "Clone/bootstrap an existing Tender App checkout and configure context plus the tender Git remote.",
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
- }, example);
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.contextFetch}`,
5412
- ].join("\n"));
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
- "artifacts",
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
- const sourceFiles = await collectArtifactWorkspaceSourceFiles({
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 ? "empty_workspace" : null,
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
- ].join("\n"));
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
- return await runDoctor(command, io);
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") && args[0] === "app" && args[1] === "db") {
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", "-"))));