@tenderprompt/cli 0.1.15 → 0.1.17

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