@promptbook/cli 0.114.0-32 → 0.114.0-34

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 (177) hide show
  1. package/esm/index.es.js +1417 -400
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-codex-prompts/common/createFreeDiskSpaceGuard.d.ts +25 -0
  4. package/esm/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  5. package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +21 -3
  6. package/esm/scripts/run-codex-prompts/main/listCoderPrompts.d.ts +20 -0
  7. package/esm/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +77 -0
  8. package/esm/scripts/run-codex-prompts/prompts/buildPromptRunTracePath.d.ts +10 -0
  9. package/esm/scripts/run-codex-prompts/prompts/isPromptCompatibleWithRunner.d.ts +11 -6
  10. package/esm/scripts/run-codex-prompts/prompts/loadPromptFiles.d.ts +1 -1
  11. package/esm/scripts/run-codex-prompts/prompts/writePromptRunTrace.d.ts +19 -0
  12. package/esm/scripts/run-codex-prompts/runners/common/buildHarnessAuthenticationError.d.ts +20 -0
  13. package/esm/scripts/run-codex-prompts/runners/common/createAuthenticationAwarePromptRunner.d.ts +11 -0
  14. package/esm/scripts/run-codex-prompts/runners/common/extractHarnessAuthenticationFailureReason.d.ts +9 -0
  15. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +3 -0
  16. package/esm/src/book-2.0/book-language-documentation/getSafeCodeBlock.d.ts +1 -1
  17. package/esm/src/book-components/Chat/MockedChat/MockedChat.test.d.ts +2 -0
  18. package/esm/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.d.ts +2 -1
  19. package/esm/src/cli/cli-commands/coder/agentCliOptions.d.ts +21 -0
  20. package/esm/src/cli/cli-commands/coder/boilerplateTemplates.d.ts +8 -21
  21. package/esm/src/cli/cli-commands/coder/coderReferencedArtifacts.d.ts +33 -0
  22. package/esm/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.d.ts +4 -2
  23. package/esm/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.d.ts +2 -0
  24. package/esm/src/cli/cli-commands/coder/ensureDirectory.d.ts +6 -0
  25. package/esm/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.d.ts +6 -0
  26. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +7 -5
  27. package/esm/src/cli/cli-commands/coder/list.d.ts +10 -0
  28. package/esm/src/cli/cli-commands/coder/mergeStringRecordJsonFile.d.ts +19 -1
  29. package/esm/src/cli/cli-commands/coder/printCoderRunFailure.d.ts +6 -0
  30. package/esm/src/cli/cli-commands/coder.d.ts +1 -0
  31. package/esm/src/cli/cli-commands/common/$askForConfirmation.d.ts +4 -2
  32. package/esm/src/cli/cli-commands/common/$askForConfirmation.test.d.ts +1 -0
  33. package/esm/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.d.ts +12 -0
  34. package/esm/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.test.d.ts +1 -0
  35. package/esm/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.d.ts +11 -0
  36. package/esm/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.test.d.ts +1 -0
  37. package/esm/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.d.ts +33 -0
  38. package/esm/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.d.ts +6 -0
  39. package/esm/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.test.d.ts +1 -0
  40. package/esm/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.d.ts +10 -0
  41. package/esm/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.d.ts +28 -0
  42. package/esm/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.d.ts +7 -0
  43. package/esm/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.test.d.ts +1 -0
  44. package/esm/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.d.ts +2 -1
  45. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -2
  46. package/esm/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +26 -0
  47. package/esm/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.d.ts +4 -2
  48. package/esm/src/cli/cli-commands/common/promptPriorityCliOptions.d.ts +7 -0
  49. package/esm/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +2 -1
  50. package/esm/src/cli/cli-commands/common/questionsCliOptions.d.ts +45 -0
  51. package/esm/src/cli/cli-commands/common/questionsCliOptions.test.d.ts +1 -0
  52. package/esm/src/cli/common/$deprecateCliCommand.d.ts +3 -0
  53. package/esm/src/cli/common/$hideCliCommandFromHelp.d.ts +11 -0
  54. package/esm/src/cli/common/$hideCliCommandFromHelp.test.d.ts +1 -0
  55. package/esm/src/cli/common/$requireCliSubcommand.d.ts +18 -0
  56. package/esm/src/cli/common/$requireCliSubcommand.test.d.ts +1 -0
  57. package/esm/src/llm-providers/vercel/VercelExecutionToolsOptions.d.ts +5 -2
  58. package/esm/src/version.d.ts +1 -1
  59. package/package.json +8 -11
  60. package/src/book-2.0/book-language-documentation/getSafeCodeBlock.ts +1 -1
  61. package/src/book-components/Chat/MockedChat/MockedChat.tsx +25 -1
  62. package/src/cli/cli-commands/agent-folder.ts +2 -6
  63. package/src/cli/cli-commands/agent.ts +2 -6
  64. package/src/cli/cli-commands/agents-server.ts +2 -6
  65. package/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.ts +3 -0
  66. package/src/cli/cli-commands/coder/add.ts +27 -2
  67. package/src/cli/cli-commands/coder/agentCliOptions.ts +32 -0
  68. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +17 -44
  69. package/src/cli/cli-commands/coder/coderReferencedArtifacts.ts +106 -0
  70. package/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.ts +4 -2
  71. package/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.ts +5 -1
  72. package/src/cli/cli-commands/coder/ensureDirectory.ts +27 -1
  73. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +99 -10
  74. package/src/cli/cli-commands/coder/init.ts +23 -14
  75. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +18 -27
  76. package/src/cli/cli-commands/coder/list.ts +89 -0
  77. package/src/cli/cli-commands/coder/mergeStringRecordJsonFile.ts +31 -7
  78. package/src/cli/cli-commands/coder/ping.ts +25 -22
  79. package/src/cli/cli-commands/coder/printCoderRunFailure.ts +14 -0
  80. package/src/cli/cli-commands/coder/printInitializationSummary.ts +23 -16
  81. package/src/cli/cli-commands/coder/run.ts +36 -39
  82. package/src/cli/cli-commands/coder/server.ts +45 -48
  83. package/src/cli/cli-commands/coder.ts +6 -6
  84. package/src/cli/cli-commands/common/$askForConfirmation.ts +12 -2
  85. package/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.ts +35 -0
  86. package/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.ts +37 -0
  87. package/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.ts +39 -0
  88. package/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.ts +26 -0
  89. package/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.ts +33 -0
  90. package/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.ts +33 -0
  91. package/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.ts +21 -0
  92. package/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts +17 -5
  93. package/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts +8 -3
  94. package/src/cli/cli-commands/common/harness/HarnessDefinition.ts +36 -0
  95. package/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts +8 -3
  96. package/src/cli/cli-commands/common/promptPriorityCliOptions.ts +30 -0
  97. package/src/cli/cli-commands/common/promptRunnerCliOptions.ts +5 -5
  98. package/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.ts +13 -2
  99. package/src/cli/cli-commands/common/questionsCliOptions.ts +89 -0
  100. package/src/cli/cli-commands/run.ts +3 -1
  101. package/src/cli/common/$deprecateCliCommand.ts +5 -0
  102. package/src/cli/common/$hideCliCommandFromHelp.ts +27 -0
  103. package/src/cli/common/$requireCliSubcommand.ts +35 -0
  104. package/src/cli/promptbookCli.ts +13 -9
  105. package/src/llm-providers/vercel/VercelExecutionToolsOptions.ts +7 -2
  106. package/src/llm-providers/vercel/createExecutionToolsFromVercelProvider.ts +18 -17
  107. package/src/other/templates/getTemplatesPipelineCollection.ts +695 -707
  108. package/src/version.ts +2 -2
  109. package/src/versions.txt +2 -0
  110. package/umd/index.umd.js +1416 -399
  111. package/umd/index.umd.js.map +1 -1
  112. package/umd/scripts/run-codex-prompts/common/createFreeDiskSpaceGuard.d.ts +25 -0
  113. package/umd/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  114. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +21 -3
  115. package/umd/scripts/run-codex-prompts/main/listCoderPrompts.d.ts +20 -0
  116. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +77 -0
  117. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTracePath.d.ts +10 -0
  118. package/umd/scripts/run-codex-prompts/prompts/isPromptCompatibleWithRunner.d.ts +11 -6
  119. package/umd/scripts/run-codex-prompts/prompts/loadPromptFiles.d.ts +1 -1
  120. package/umd/scripts/run-codex-prompts/prompts/writePromptRunTrace.d.ts +19 -0
  121. package/umd/scripts/run-codex-prompts/runners/common/buildHarnessAuthenticationError.d.ts +20 -0
  122. package/umd/scripts/run-codex-prompts/runners/common/createAuthenticationAwarePromptRunner.d.ts +11 -0
  123. package/umd/scripts/run-codex-prompts/runners/common/extractHarnessAuthenticationFailureReason.d.ts +9 -0
  124. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +3 -0
  125. package/umd/src/book-2.0/book-language-documentation/getSafeCodeBlock.d.ts +1 -1
  126. package/umd/src/book-components/Chat/MockedChat/MockedChat.test.d.ts +2 -0
  127. package/umd/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.d.ts +2 -1
  128. package/umd/src/cli/cli-commands/coder/agentCliOptions.d.ts +21 -0
  129. package/umd/src/cli/cli-commands/coder/boilerplateTemplates.d.ts +8 -21
  130. package/umd/src/cli/cli-commands/coder/coderReferencedArtifacts.d.ts +33 -0
  131. package/umd/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.d.ts +4 -2
  132. package/umd/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.d.ts +2 -0
  133. package/umd/src/cli/cli-commands/coder/ensureDirectory.d.ts +6 -0
  134. package/umd/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.d.ts +6 -0
  135. package/umd/src/cli/cli-commands/coder/init.test.d.ts +1 -0
  136. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +7 -5
  137. package/umd/src/cli/cli-commands/coder/list.d.ts +10 -0
  138. package/umd/src/cli/cli-commands/coder/list.test.d.ts +1 -0
  139. package/umd/src/cli/cli-commands/coder/mergeStringRecordJsonFile.d.ts +19 -1
  140. package/umd/src/cli/cli-commands/coder/printCoderRunFailure.d.ts +6 -0
  141. package/umd/src/cli/cli-commands/coder.d.ts +1 -0
  142. package/umd/src/cli/cli-commands/common/$askForConfirmation.d.ts +4 -2
  143. package/umd/src/cli/cli-commands/common/$askForConfirmation.test.d.ts +1 -0
  144. package/umd/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.d.ts +12 -0
  145. package/umd/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.test.d.ts +1 -0
  146. package/umd/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.d.ts +11 -0
  147. package/umd/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.test.d.ts +1 -0
  148. package/umd/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.d.ts +33 -0
  149. package/umd/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.d.ts +6 -0
  150. package/umd/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.test.d.ts +1 -0
  151. package/umd/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.d.ts +10 -0
  152. package/umd/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.d.ts +28 -0
  153. package/umd/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.d.ts +7 -0
  154. package/umd/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.test.d.ts +1 -0
  155. package/umd/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.d.ts +2 -1
  156. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -2
  157. package/umd/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +26 -0
  158. package/umd/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.d.ts +4 -2
  159. package/umd/src/cli/cli-commands/common/promptPriorityCliOptions.d.ts +7 -0
  160. package/umd/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +2 -1
  161. package/umd/src/cli/cli-commands/common/questionsCliOptions.d.ts +45 -0
  162. package/umd/src/cli/cli-commands/common/questionsCliOptions.test.d.ts +1 -0
  163. package/umd/src/cli/common/$deprecateCliCommand.d.ts +3 -0
  164. package/umd/src/cli/common/$hideCliCommandFromHelp.d.ts +11 -0
  165. package/umd/src/cli/common/$hideCliCommandFromHelp.test.d.ts +1 -0
  166. package/umd/src/cli/common/$requireCliSubcommand.d.ts +18 -0
  167. package/umd/src/cli/common/$requireCliSubcommand.test.d.ts +1 -0
  168. package/umd/src/llm-providers/vercel/VercelExecutionToolsOptions.d.ts +5 -2
  169. package/umd/src/version.d.ts +1 -1
  170. package/esm/src/cli/cli-commands/coder/agentCodingFile.d.ts +0 -13
  171. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +0 -29
  172. package/src/cli/cli-commands/coder/agentCodingFile.ts +0 -25
  173. package/src/cli/cli-commands/common/harnessUpdateCliOptions.ts +0 -46
  174. package/umd/src/cli/cli-commands/coder/agentCodingFile.d.ts +0 -13
  175. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +0 -29
  176. /package/esm/src/cli/cli-commands/{common/harnessUpdateCliOptions.test.d.ts → coder/init.test.d.ts} +0 -0
  177. /package/{umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts → esm/src/cli/cli-commands/coder/list.test.d.ts} +0 -0
package/umd/index.umd.js CHANGED
@@ -58,7 +58,7 @@
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-32';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-34';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1718,6 +1718,35 @@
1718
1718
  // TODO: [🗽] Unite branding and make single place for it
1719
1719
  // Note: [💞] Ignore a discrepancy between file name and entity name
1720
1720
 
1721
+ /**
1722
+ * Message printed when one CLI command which only groups subcommands is run without picking any of them.
1723
+ *
1724
+ * @private internal constant of `$requireCliSubcommand`
1725
+ */
1726
+ const MISSING_CLI_SUBCOMMAND_MESSAGE = 'Please specify a subcommand.';
1727
+ /**
1728
+ * Makes one CLI command which only groups subcommands ask for a subcommand instead of doing anything on its own.
1729
+ *
1730
+ * Note: `$` is used to indicate that this function is not a pure function - it registers an action in the CLI
1731
+ *
1732
+ * @private utility of CLI
1733
+ */
1734
+ function $requireCliSubcommand(command) {
1735
+ command.action(() => $reportMissingCliSubcommand(command));
1736
+ }
1737
+ /**
1738
+ * Asks for a subcommand of one CLI command, prints its help and exits the process.
1739
+ *
1740
+ * Note: `$` is used to indicate that this function is not a pure function - it writes to the console and exits the process
1741
+ *
1742
+ * @private utility of CLI
1743
+ */
1744
+ function $reportMissingCliSubcommand(command) {
1745
+ console.info(colors__default["default"].yellow(MISSING_CLI_SUBCOMMAND_MESSAGE));
1746
+ console.info('');
1747
+ return command.help();
1748
+ }
1749
+
1721
1750
  // Note: [💞] Ignore a discrepancy between file name and entity name
1722
1751
  /**
1723
1752
  * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
@@ -1822,20 +1851,20 @@
1822
1851
  */
1823
1852
  const PROMPT_RUNNER_DESCRIPTION = _spaceTrim.spaceTrim(`
1824
1853
  Runners:
1825
- - openai-codex: OpenAI Codex integration (requires --model)
1854
+ - openai-codex: OpenAI Codex integration (requires --model when executing)
1826
1855
  - github-copilot: GitHub Copilot CLI integration
1827
1856
  - cline: Cline CLI integration
1828
1857
  - claude-code: Claude Code integration
1829
1858
  - opencode: Opencode integration
1830
- - gemini: Google Gemini CLI integration (requires --model)
1831
- - qwen-code: Qwen Code CLI integration (requires --model)
1859
+ - gemini: Google Gemini CLI integration (requires --model when executing)
1860
+ - qwen-code: Qwen Code CLI integration (requires --model when executing)
1832
1861
  `);
1833
1862
  /**
1834
1863
  * Commander description for the `--harness` option.
1835
1864
  *
1836
1865
  * @private internal utility of `promptbookCli`
1837
1866
  */
1838
- const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = `Select runner: ${PROMPT_RUNNER_HARNESS_NAMES.join(', ')} (required for non-dry-run)`;
1867
+ const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = `Select runner: ${PROMPT_RUNNER_HARNESS_NAMES.join(', ')} (required when executing prompts)`;
1839
1868
  /**
1840
1869
  * Runner harness names listed as alternatives of the `--harness` option in error messages.
1841
1870
  *
@@ -1848,7 +1877,7 @@
1848
1877
  * @private internal utility of `promptbookCli`
1849
1878
  */
1850
1879
  const PROMPT_RUNNER_MODEL_OPTION_DESCRIPTION = _spaceTrim.spaceTrim(`
1851
- Model to use (required for openai-codex, gemini and qwen-code)
1880
+ Model to use or filter by (required when executing with openai-codex, gemini and qwen-code)
1852
1881
 
1853
1882
  OpenAI examples: gpt-5.2-codex, default
1854
1883
  Gemini examples: gemini-3-flash-preview, default
@@ -2113,11 +2142,7 @@
2113
2142
  `));
2114
2143
  $initializeAgentChatCommand(agentCommand);
2115
2144
  $initializeAgentExecCommand(agentCommand);
2116
- agentCommand.action(() => {
2117
- console.info(colors__default["default"].yellow('Please specify a subcommand.'));
2118
- console.info('');
2119
- agentCommand.help();
2120
- });
2145
+ $requireCliSubcommand(agentCommand);
2121
2146
  }
2122
2147
  // Note: [🟡] Code for CLI command [agent](src/cli/cli-commands/agent.ts) should never be published outside of `@promptbook/cli`
2123
2148
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2275,6 +2300,23 @@
2275
2300
  }
2276
2301
  return 'unchanged';
2277
2302
  }
2303
+ /**
2304
+ * Checks whether a project-relative directory is missing or contains no entries.
2305
+ *
2306
+ * @private internal utility of `coder init` command
2307
+ */
2308
+ async function isDirectoryEmpty(projectPath, relativeDirectoryPath) {
2309
+ const directoryPath = path.join(projectPath, relativeDirectoryPath);
2310
+ try {
2311
+ return (await promises.readdir(directoryPath)).length === 0;
2312
+ }
2313
+ catch (error) {
2314
+ if (isNodeJsErrorWithCode$1(error, 'ENOENT')) {
2315
+ return true;
2316
+ }
2317
+ throw error;
2318
+ }
2319
+ }
2278
2320
  /**
2279
2321
  * Checks whether a path exists and is a directory.
2280
2322
  */
@@ -2286,6 +2328,12 @@
2286
2328
  return false;
2287
2329
  }
2288
2330
  }
2331
+ /**
2332
+ * Checks whether a caught value is a Node.js error with the given error code.
2333
+ */
2334
+ function isNodeJsErrorWithCode$1(error, code) {
2335
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
2336
+ }
2289
2337
  // Note: [🟡] Code for coder init directory creation [ensureDirectory](src/cli/cli-commands/coder/ensureDirectory.ts) should never be published outside of `@promptbook/cli`
2290
2338
 
2291
2339
  /**
@@ -2573,11 +2621,7 @@
2573
2621
  $initializeAgentTickCommand(agentFolderCommand);
2574
2622
  $initializeAgentRunCommand(agentFolderCommand);
2575
2623
  $initializeAgentRunMultipleCommand(agentFolderCommand);
2576
- agentFolderCommand.action(() => {
2577
- console.info(colors__default["default"].yellow('Please specify a subcommand.'));
2578
- console.info('');
2579
- agentFolderCommand.help();
2580
- });
2624
+ $requireCliSubcommand(agentFolderCommand);
2581
2625
  }
2582
2626
  // Note: [🟡] Code for CLI command [agent-folder](src/cli/cli-commands/agent-folder.ts) should never be published outside of `@promptbook/cli`
2583
2627
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -21428,10 +21472,30 @@
21428
21472
  return relativePath.replace(/\\/gu, '/');
21429
21473
  }
21430
21474
 
21475
+ /**
21476
+ * AuthenticationError is thrown from login function which is dependency of remote server
21477
+ *
21478
+ * @public exported from `@promptbook/core`
21479
+ */
21480
+ class AuthenticationError extends Error {
21481
+ constructor(message) {
21482
+ super(message);
21483
+ this.name = 'AuthenticationError';
21484
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
21485
+ }
21486
+ }
21487
+
21431
21488
  /**
21432
21489
  * Formats one unknown error-like value into readable text for logs and feedback.
21490
+ *
21491
+ * An `AuthenticationError` is reported without its stack, because its message is written for the user - it
21492
+ * says which harness has to be signed in again and how - and the frames of a failure nobody can debug would
21493
+ * only push those instructions out of sight.
21433
21494
  */
21434
21495
  function formatUnknownErrorDetails(error) {
21496
+ if (error instanceof AuthenticationError) {
21497
+ return error.message;
21498
+ }
21435
21499
  if (error instanceof Error) {
21436
21500
  return error.stack || error.message;
21437
21501
  }
@@ -24148,6 +24212,7 @@
24148
24212
  commandName: 'codex',
24149
24213
  npmPackageName: '@openai/codex',
24150
24214
  projectGitignoreRules: ['.codex'],
24215
+ authenticationMethod: { command: 'codex login' },
24151
24216
  // Note: The official standalone installer keeps its package in `~/.codex` and links it onto the `PATH`
24152
24217
  standaloneInstallation: {
24153
24218
  directoryNames: ['.codex'],
@@ -24160,6 +24225,7 @@
24160
24225
  commandName: 'copilot',
24161
24226
  npmPackageName: '@github/copilot',
24162
24227
  projectGitignoreRules: ['.github/copilot/settings.local.json'],
24228
+ authenticationMethod: { command: 'copilot', interactiveStep: 'running the `/login` command inside it' },
24163
24229
  // Note: The GitHub Copilot CLI downloads its native binary in a postinstall script
24164
24230
  npmInstallEnvironment: { npm_config_ignore_scripts: 'false' },
24165
24231
  },
@@ -24169,6 +24235,7 @@
24169
24235
  commandName: 'cline',
24170
24236
  npmPackageName: 'cline',
24171
24237
  projectGitignoreRules: ['.cline'],
24238
+ authenticationMethod: { command: 'cline', interactiveStep: 'signing in to the provider inside it' },
24172
24239
  },
24173
24240
  'claude-code': {
24174
24241
  harnessName: 'claude-code',
@@ -24176,6 +24243,7 @@
24176
24243
  commandName: 'claude',
24177
24244
  npmPackageName: '@anthropic-ai/claude-code',
24178
24245
  projectGitignoreRules: ['.claude'],
24246
+ authenticationMethod: { command: 'claude', interactiveStep: 'running the `/login` command inside it' },
24179
24247
  },
24180
24248
  opencode: {
24181
24249
  harnessName: 'opencode',
@@ -24183,6 +24251,7 @@
24183
24251
  commandName: 'opencode',
24184
24252
  npmPackageName: 'opencode-ai',
24185
24253
  projectGitignoreRules: ['.opencode'],
24254
+ authenticationMethod: { command: 'opencode auth login' },
24186
24255
  },
24187
24256
  gemini: {
24188
24257
  harnessName: 'gemini',
@@ -24190,6 +24259,7 @@
24190
24259
  commandName: 'gemini',
24191
24260
  npmPackageName: '@google/gemini-cli',
24192
24261
  projectGitignoreRules: ['.gemini'],
24262
+ authenticationMethod: { command: 'gemini', interactiveStep: 'running the `/auth` command inside it' },
24193
24263
  },
24194
24264
  'qwen-code': {
24195
24265
  harnessName: 'qwen-code',
@@ -24197,6 +24267,7 @@
24197
24267
  commandName: 'qwen',
24198
24268
  npmPackageName: '@qwen-code/qwen-code',
24199
24269
  projectGitignoreRules: ['.qwen'],
24270
+ authenticationMethod: { command: 'qwen', interactiveStep: 'running the `/auth` command inside it' },
24200
24271
  },
24201
24272
  };
24202
24273
  /**
@@ -26230,6 +26301,183 @@
26230
26301
  return `${sessionId.slice(0, 8)}...`;
26231
26302
  }
26232
26303
 
26304
+ /**
26305
+ * Formats one unknown error-like value into its readable message without the stack trace.
26306
+ *
26307
+ * Use this instead of `formatUnknownErrorDetails` whenever the text is shown to the user, for example
26308
+ * inside a branded error, where the stack of the wrapped error would only bury the actual cause.
26309
+ */
26310
+ function formatUnknownErrorMessage(error) {
26311
+ if (error instanceof Error) {
26312
+ return error.message;
26313
+ }
26314
+ if (typeof error === 'string') {
26315
+ return error;
26316
+ }
26317
+ const serializedError = JSON.stringify(error, null, 2);
26318
+ return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
26319
+ }
26320
+
26321
+ /**
26322
+ * Builds the branded error which tells the user that the selected harness has to be signed in again.
26323
+ */
26324
+ function buildHarnessAuthenticationError(options) {
26325
+ const { harnessName, reason } = options;
26326
+ const { label, authenticationMethod } = getHarnessDefinition(harnessName);
26327
+ return new AuthenticationError(spaceTrim((block) => `
26328
+ The **${label}** harness is not authenticated, so the prompt was not run.
26329
+
26330
+ ${label} reported:
26331
+ > ${reason}
26332
+
26333
+ ${block(buildSignInInstruction(authenticationMethod))}
26334
+
26335
+ Once you are signed in, start the very same \`ptbk coder\` command again.
26336
+ `));
26337
+ }
26338
+ /**
26339
+ * Builds the sentence which tells the user how to sign in to one harness again.
26340
+ */
26341
+ function buildSignInInstruction(authenticationMethod) {
26342
+ const { command, interactiveStep } = authenticationMethod;
26343
+ if (interactiveStep === undefined) {
26344
+ return `Sign in again by running \`${command}\`.`;
26345
+ }
26346
+ return `Sign in again by running \`${command}\` and then ${interactiveStep}.`;
26347
+ }
26348
+
26349
+ /**
26350
+ * Maximum length of the harness reason quoted back to the user.
26351
+ */
26352
+ const MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH = 300;
26353
+ /**
26354
+ * Keys of machine-readable harness output whose string value carries the human-readable failure reason.
26355
+ *
26356
+ * Harnesses which stream JSON events - like Claude Code with its `stream-json` output - report the reason only
26357
+ * inside such a value, surrounded by hundreds of characters of session metadata which must never be shown to
26358
+ * the user. The keys are ordered from the most to the least conclusive one.
26359
+ */
26360
+ const HARNESS_FAILURE_MESSAGE_KEYS = ['result', 'error_message', 'message', 'text'];
26361
+ /**
26362
+ * Patterns which extract one JSON string value reported under a harness failure message key.
26363
+ */
26364
+ const HARNESS_FAILURE_MESSAGE_PATTERNS = HARNESS_FAILURE_MESSAGE_KEYS.map((messageKey) => new RegExp(`"${messageKey}"\\s*:\\s*("(?:[^"\\\\]|\\\\.)*")`, 'g'));
26365
+ /**
26366
+ * Signatures which mean that the harness refused to work because it is not logged in.
26367
+ *
26368
+ * They are deliberately narrow phrases instead of single words like `login`, because the output of a harness
26369
+ * routinely mentions its own `/login` command among the commands it offers, and such a mention must never be
26370
+ * read as a failed login.
26371
+ */
26372
+ const AUTHENTICATION_FAILURE_PATTERNS = [
26373
+ /\bauthentication[ _-]?failed\b/i,
26374
+ /\bfailed to authenticate\b/i,
26375
+ /\bnot authenticated\b/i,
26376
+ /\bnot logged[ -]?in\b/i,
26377
+ /\bnot signed[ -]?in\b/i,
26378
+ /\bno authentication information found\b/i,
26379
+ /\bauthentication (?:is )?required\b/i,
26380
+ /\blogin (?:is )?required\b/i,
26381
+ /\bplease (?:log|sign) in\b/i,
26382
+ /\bunauthorized\b/i,
26383
+ /\b(?:oauth|session|token|credentials?|login)\b[^.]{0,60}?\bexpired\b/i,
26384
+ /\bexpired\b[^.]{0,60}?\b(?:oauth|session|token|credentials?|login)\b/i,
26385
+ /\binvalid api[ _-]?key\b/i,
26386
+ /\bmissing api[ _-]?key\b/i,
26387
+ /\binvalid credentials?\b/i,
26388
+ ];
26389
+ /**
26390
+ * Reads the reason why a harness refused to work because it is not logged in, from its raw CLI output.
26391
+ *
26392
+ * Messages the harness reports in machine-readable output are preferred over its raw output lines, so the
26393
+ * reason is the sentence the harness wrote for a human and not the JSON event which transported it.
26394
+ *
26395
+ * @returns One short single-line reason, or `undefined` when the failure was not caused by authentication.
26396
+ */
26397
+ function extractHarnessAuthenticationFailureReason(harnessOutput) {
26398
+ const reportedMessages = [...extractHarnessReportedMessages(harnessOutput), ...harnessOutput.split(/\r?\n/)];
26399
+ for (const reportedMessage of reportedMessages) {
26400
+ const reason = reportedMessage.replace(/\s+/g, ' ').trim();
26401
+ if (isAuthenticationFailureReason(reason)) {
26402
+ return limitAuthenticationFailureReason(reason);
26403
+ }
26404
+ }
26405
+ return undefined;
26406
+ }
26407
+ /**
26408
+ * Lists the human-readable messages a harness reported in its machine-readable output.
26409
+ */
26410
+ function extractHarnessReportedMessages(harnessOutput) {
26411
+ const reportedMessages = [];
26412
+ for (const messagePattern of HARNESS_FAILURE_MESSAGE_PATTERNS) {
26413
+ for (const match of harnessOutput.matchAll(messagePattern)) {
26414
+ const reportedMessage = parseSerializedJsonString(match[1]);
26415
+ if (reportedMessage !== undefined) {
26416
+ reportedMessages.push(reportedMessage);
26417
+ }
26418
+ }
26419
+ }
26420
+ return reportedMessages;
26421
+ }
26422
+ /**
26423
+ * Reads one JSON string literal back into plain text, tolerating output which only looks like JSON.
26424
+ */
26425
+ function parseSerializedJsonString(serializedString) {
26426
+ if (serializedString === undefined) {
26427
+ return undefined;
26428
+ }
26429
+ try {
26430
+ const parsedString = JSON.parse(serializedString);
26431
+ return typeof parsedString === 'string' ? parsedString : undefined;
26432
+ }
26433
+ catch (_a) {
26434
+ return undefined;
26435
+ }
26436
+ }
26437
+ /**
26438
+ * Returns true when one message of the harness says that it is not logged in.
26439
+ */
26440
+ function isAuthenticationFailureReason(reportedMessage) {
26441
+ return AUTHENTICATION_FAILURE_PATTERNS.some((pattern) => pattern.test(reportedMessage));
26442
+ }
26443
+ /**
26444
+ * Shortens a very long reason, so the guidance which follows it stays visible in the terminal.
26445
+ */
26446
+ function limitAuthenticationFailureReason(reason) {
26447
+ if (reason.length <= MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH) {
26448
+ return reason;
26449
+ }
26450
+ return `${reason.slice(0, MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH)}...`;
26451
+ }
26452
+
26453
+ /**
26454
+ * Wraps one prompt runner so a failure caused by a missing or expired harness login is reported as a branded
26455
+ * `AuthenticationError` which tells the user how to sign in again.
26456
+ *
26457
+ * Each harness reports its own failures differently, but all of them report them by failing `runPrompt` with
26458
+ * the raw CLI output as the message. Translating the failure here therefore keeps exactly one place which has
26459
+ * to recognize an unauthenticated harness, for every harness and for every command which runs prompts.
26460
+ */
26461
+ function createAuthenticationAwarePromptRunner(runner, harnessName) {
26462
+ var _a;
26463
+ return {
26464
+ name: runner.name,
26465
+ runPrompt: async (options) => {
26466
+ try {
26467
+ return await runner.runPrompt(options);
26468
+ }
26469
+ catch (error) {
26470
+ const reason = extractHarnessAuthenticationFailureReason(formatUnknownErrorMessage(error));
26471
+ if (reason === undefined) {
26472
+ throw error;
26473
+ }
26474
+ throw buildHarnessAuthenticationError({ harnessName, reason });
26475
+ }
26476
+ },
26477
+ getSubscriptionUsage: (_a = runner.getSubscriptionUsage) === null || _a === void 0 ? void 0 : _a.bind(runner),
26478
+ };
26479
+ }
26480
+
26233
26481
  /**
26234
26482
  * Creates a temporary script file, runs it, and cleans it up unless preservation is requested or the run fails.
26235
26483
  */
@@ -27969,6 +28217,18 @@
27969
28217
  if (!agentName) {
27970
28218
  throw new Error('Missing --harness in non-dry run mode');
27971
28219
  }
28220
+ const resolution = resolveHarnessPromptRunner(agentName, options);
28221
+ return {
28222
+ ...resolution,
28223
+ // Note: Every harness is wrapped here, so a harness which is not logged in is reported the same clear
28224
+ // way no matter which harness it is and which command has resolved the runner
28225
+ runner: createAuthenticationAwarePromptRunner(resolution.runner, agentName),
28226
+ };
28227
+ }
28228
+ /**
28229
+ * Creates the runner of one selected harness together with its status-line metadata.
28230
+ */
28231
+ function resolveHarnessPromptRunner(agentName, options) {
27972
28232
  if (agentName === 'openai-codex') {
27973
28233
  return createOpenAiCodexRunnerResolution(options);
27974
28234
  }
@@ -28436,11 +28696,19 @@
28436
28696
  * Maximum width used for the rich coder-run frame.
28437
28697
  */
28438
28698
  const MAX_FRAME_WIDTH = 96;
28699
+ /**
28700
+ * Number of terminal columns deliberately left unused by the rich coder-run frame.
28701
+ *
28702
+ * A row which fills the final physical terminal column can auto-wrap. The incremental terminal
28703
+ * updater moves by logical rows, so an auto-wrapped row would put its cursor out of sync with the
28704
+ * frame and let a later animated avatar redraw overwrite dashboard content.
28705
+ */
28706
+ const TERMINAL_AUTO_WRAP_RESERVED_COLUMNS = 1;
28439
28707
  /**
28440
28708
  * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
28441
28709
  */
28442
28710
  function buildCoderRunUiFrame(options) {
28443
- const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
28711
+ const totalWidth = resolveCoderRunUiFrameWidth(options.terminalWidth);
28444
28712
  const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
28445
28713
  const promptStatusPrefix = isPromptActive ? `${colors__default["default"].yellow(`${options.spinner} `)}` : '';
28446
28714
  const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
@@ -28477,6 +28745,16 @@
28477
28745
  frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors__default["default"].white.bold));
28478
28746
  return frame;
28479
28747
  }
28748
+ /**
28749
+ * Resolves the safe width of the rich terminal frame from the available physical terminal columns.
28750
+ *
28751
+ * The reserved trailing column keeps every boxed row from auto-wrapping, which makes the frame's
28752
+ * logical row count match its physical terminal row count during animated incremental redraws.
28753
+ */
28754
+ function resolveCoderRunUiFrameWidth(terminalWidth) {
28755
+ const availableFrameWidth = terminalWidth - TERMINAL_AUTO_WRAP_RESERVED_COLUMNS;
28756
+ return Math.max(MIN_FRAME_WIDTH, Math.min(availableFrameWidth, MAX_FRAME_WIDTH));
28757
+ }
28480
28758
  /**
28481
28759
  * Builds the header visual above the dashboard boxes.
28482
28760
  *
@@ -29953,16 +30231,14 @@
29953
30231
  */
29954
30232
  const DEFAULT_TERMINAL_WIDTH = 80;
29955
30233
  /**
29956
- * Maximum terminal width supported by the boxed runner UI.
29957
- */
29958
- const MAX_TERMINAL_WIDTH = 96;
29959
- /**
29960
- * Returns the usable terminal width, capped at 96.
30234
+ * Returns the number of columns reported by the current terminal.
30235
+ *
30236
+ * The frame builder owns clamping and reserves its trailing auto-wrap safety column.
29961
30237
  *
29962
30238
  * @private internal utility of coder run UI
29963
30239
  */
29964
- function getTerminalWidth() {
29965
- return Math.min(process.stdout.columns || DEFAULT_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH);
30240
+ function getTerminalColumnCount() {
30241
+ return process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
29966
30242
  }
29967
30243
  /**
29968
30244
  * Boots the ANSI terminal UI for `ptbk coder run`.
@@ -30069,7 +30345,7 @@
30069
30345
  */
30070
30346
  function buildFrameLines() {
30071
30347
  return buildFrameLinesFromState({
30072
- terminalWidth: getTerminalWidth(),
30348
+ terminalWidth: getTerminalColumnCount(),
30073
30349
  animationFrame: spinnerFrame,
30074
30350
  animationTimeMs: Date.now() - animationStartTimeMs,
30075
30351
  spinner: SPINNER_FRAMES[spinnerFrame],
@@ -33800,19 +34076,6 @@
33800
34076
  }
33801
34077
  }
33802
34078
 
33803
- /**
33804
- * AuthenticationError is thrown from login function which is dependency of remote server
33805
- *
33806
- * @public exported from `@promptbook/core`
33807
- */
33808
- class AuthenticationError extends Error {
33809
- constructor(message) {
33810
- super(message);
33811
- this.name = 'AuthenticationError';
33812
- Object.setPrototypeOf(this, AuthenticationError.prototype);
33813
- }
33814
- }
33815
-
33816
34079
  /**
33817
34080
  * This error indicates that the pipeline collection cannot be properly loaded
33818
34081
  *
@@ -42041,11 +42304,7 @@
42041
42304
  $initializeAgentsServerDevCommand(agentsServerCommand);
42042
42305
  $initializeAgentsServerInitCommand(agentsServerCommand);
42043
42306
  $initializeAgentsServerStartCommand(agentsServerCommand);
42044
- agentsServerCommand.action(() => {
42045
- console.info(colors__default["default"].yellow('Please specify a subcommand.'));
42046
- console.info('');
42047
- agentsServerCommand.help();
42048
- });
42307
+ $requireCliSubcommand(agentsServerCommand);
42049
42308
  }
42050
42309
  // Note: [🟡] Code for CLI command [agents-server](src/cli/cli-commands/agents-server.ts) should never be published outside of `@promptbook/cli`
42051
42310
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -42135,24 +42394,82 @@
42135
42394
  // Note: [🟡] Code for CLI git synchronization options [coderGitSyncCliOptions](src/cli/cli-commands/common/coderGitSyncCliOptions.ts) should never be published outside of `@promptbook/cli`
42136
42395
  // Note: [💞] Ignore a discrepancy between file name and exported helper names
42137
42396
 
42397
+ /**
42398
+ * Description block shared by the `ptbk coder` commands which can ask interactive questions.
42399
+ *
42400
+ * @private internal utility of `promptbookCli`
42401
+ */
42402
+ const QUESTIONS_DESCRIPTION = _spaceTrim.spaceTrim(`
42403
+ Interactive questions:
42404
+ - Steps which need an answer to continue are asked in the terminal
42405
+ - --no-questions never asks: an optional step is skipped with a note how to do it manually, a required one fails right away
42406
+ `);
42407
+ /**
42408
+ * Registers the shared `--no-questions` option on a `ptbk coder` command which can ask interactive questions.
42409
+ *
42410
+ * @private internal utility of `promptbookCli`
42411
+ */
42412
+ function addQuestionsOption(command) {
42413
+ command.option('--no-questions', 'Never ask an interactive question and skip every optional step which needs an answer to continue');
42414
+ }
42415
+ /**
42416
+ * Converts the Commander questions flag into the normalized interactive-questions option.
42417
+ *
42418
+ * @private internal utility of `promptbookCli`
42419
+ */
42420
+ function normalizeQuestionsCliOptions(cliOptions) {
42421
+ return {
42422
+ isAskingQuestionsEnabled: cliOptions.questions,
42423
+ };
42424
+ }
42425
+ /**
42426
+ * Asserts that a command which waits for a user confirmation before each prompt is allowed to ask for it.
42427
+ *
42428
+ * @throws {NotAllowed} when `--no-auto` is combined with `--no-questions`
42429
+ * @private internal utility of `promptbookCli`
42430
+ */
42431
+ function assertUserConfirmationIsAllowed(options) {
42432
+ const { isAskingQuestionsEnabled, isWaitingForUser } = options;
42433
+ if (!isWaitingForUser || isAskingQuestionsEnabled) {
42434
+ return;
42435
+ }
42436
+ throw new NotAllowed(_spaceTrim.spaceTrim(`
42437
+ Flag \`--no-auto\` can not be used together with \`--no-questions\`.
42438
+
42439
+ **There is nobody who could confirm each prompt when no question may be asked.**
42440
+
42441
+ Actionable hints:
42442
+ - Keep \`--no-auto\` for a supervised run, for example \`ptbk coder run --harness claude-code --no-auto\`.
42443
+ - Keep \`--no-questions\` for an unattended run, for example \`ptbk coder run --harness claude-code --no-questions\`.
42444
+ `));
42445
+ }
42446
+ // Note: [🟡] Code for CLI interactive questions options [questionsCliOptions](src/cli/cli-commands/common/questionsCliOptions.ts) should never be published outside of `@promptbook/cli`
42447
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
42448
+
42138
42449
  /**
42139
42450
  * Relative path to the root prompts directory used by Promptbook coder utilities.
42140
42451
  *
42141
42452
  * @private internal utility of `ptbk coder`
42142
42453
  */
42143
- const PROMPTS_DIRECTORY_PATH = 'prompts';
42454
+ const PROMPTS_DIRECTORY_PATH$1 = 'prompts';
42144
42455
  /**
42145
42456
  * Relative path to the archive directory used by `coder verify`.
42146
42457
  *
42147
42458
  * @private internal utility of `ptbk coder`
42148
42459
  */
42149
- const PROMPTS_DONE_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'done');
42460
+ const PROMPTS_DONE_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH$1, 'done');
42150
42461
  /**
42151
42462
  * Relative path to the project-owned boilerplate templates directory.
42152
42463
  *
42153
42464
  * @private internal utility of `ptbk coder`
42154
42465
  */
42155
- const PROMPTS_TEMPLATES_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'templates');
42466
+ const PROMPTS_TEMPLATES_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH$1, 'templates');
42467
+ /**
42468
+ * Relative path to the project-agnostic `common` boilerplate template referenced by the default coder scripts.
42469
+ *
42470
+ * @private internal utility of `ptbk coder`
42471
+ */
42472
+ const COMMON_PROMPT_TEMPLATE_FILE_PATH = path.join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md');
42156
42473
  /**
42157
42474
  * Built-in boilerplate templates available to `coder generate-boilerplates`.
42158
42475
  *
@@ -42161,7 +42478,7 @@
42161
42478
  const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
42162
42479
  {
42163
42480
  id: 'common',
42164
- relativeFilePath: path.join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md'),
42481
+ relativeFilePath: COMMON_PROMPT_TEMPLATE_FILE_PATH,
42165
42482
  slugPrefix: null,
42166
42483
  content: _spaceTrim.spaceTrim(`
42167
42484
  - @@@
@@ -42207,30 +42524,17 @@
42207
42524
  return definition;
42208
42525
  }
42209
42526
  /**
42210
- * Ensures the default project-owned coder template files exist without overwriting user customizations.
42527
+ * Ensures one project-owned coder template file exists without overwriting user customizations.
42211
42528
  *
42212
42529
  * @private internal utility of `ptbk coder`
42213
42530
  */
42214
- async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
42215
- const ensuredTemplateFiles = [];
42216
- for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
42217
- const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
42218
- if (await isExistingFile$3(absoluteTemplatePath)) {
42219
- ensuredTemplateFiles.push({
42220
- id: definition.id,
42221
- relativeFilePath: definition.relativeFilePath,
42222
- status: 'unchanged',
42223
- });
42224
- continue;
42225
- }
42226
- await promises.writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
42227
- ensuredTemplateFiles.push({
42228
- id: definition.id,
42229
- relativeFilePath: definition.relativeFilePath,
42230
- status: 'created',
42231
- });
42531
+ async function ensureDefaultCoderPromptTemplateFile(projectPath, definition) {
42532
+ const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
42533
+ if (await isExistingFile$3(absoluteTemplatePath)) {
42534
+ return 'unchanged';
42232
42535
  }
42233
- return ensuredTemplateFiles;
42536
+ await promises.writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
42537
+ return 'created';
42234
42538
  }
42235
42539
  /**
42236
42540
  * Resolves the template requested by `coder generate-boilerplates`.
@@ -42386,6 +42690,8 @@
42386
42690
  - \`ptbk coder add\`
42387
42691
 
42388
42692
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
42693
+
42694
+ ${block(QUESTIONS_DESCRIPTION)}
42389
42695
  `));
42390
42696
  command.argument('[description]', 'Plain-language description of the feature or task to implement');
42391
42697
  command.option('--priority <priority>', 'Priority of the new prompt — higher priorities run first (rendered as trailing `!` markers)', parsePriorityOption, 0);
@@ -42397,11 +42703,13 @@
42397
42703
  .join(', ')}) or a markdown file path relative to the current project root.
42398
42704
  `));
42399
42705
  addCoderGitSyncOptions(command);
42706
+ addQuestionsOption(command);
42400
42707
  command.action(handleActionErrors(async (descriptionArgument, cliOptions) => {
42401
42708
  const { priority, template: templateOption } = cliOptions;
42402
42709
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
42710
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
42403
42711
  const projectPath = process.cwd();
42404
- const description = await resolveCoderPromptDescription(descriptionArgument);
42712
+ const description = await resolveCoderPromptDescription(descriptionArgument, questionsOptions);
42405
42713
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
42406
42714
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
42407
42715
  const commitScope = await $startCoderGitSync({ gitSync, projectPath });
@@ -42436,7 +42744,7 @@
42436
42744
  // Note: Import these dynamically to avoid circular dependencies and keep the CLI fast
42437
42745
  const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
42438
42746
  const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
42439
- const promptsDirectory = path.join(projectPath, PROMPTS_DIRECTORY_PATH);
42747
+ const promptsDirectory = path.join(projectPath, PROMPTS_DIRECTORY_PATH$1);
42440
42748
  fs.mkdirSync(promptsDirectory, { recursive: true });
42441
42749
  const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
42442
42750
  const promptNumbering = await getPromptNumbering({
@@ -42460,7 +42768,7 @@
42460
42768
  });
42461
42769
  const slug = buildAddPromptSlug(promptTemplate.slugPrefix, title);
42462
42770
  const filename = buildPromptFilename(promptNumbering.datePrefix, promptNumbering.startNumber, slug);
42463
- const filePath = path.join(PROMPTS_DIRECTORY_PATH, filename);
42771
+ const filePath = path.join(PROMPTS_DIRECTORY_PATH$1, filename);
42464
42772
  const absoluteFilePath = path.join(projectPath, filePath);
42465
42773
  fs.writeFileSync(absoluteFilePath, `${section}\n`, 'utf-8');
42466
42774
  console.info(colors__default["default"].green(`✓ Added prompt ${emojiTag} in ${filePath}`));
@@ -42471,7 +42779,7 @@
42471
42779
  *
42472
42780
  * @private internal utility of `coder add` command
42473
42781
  */
42474
- async function resolveCoderPromptDescription(descriptionArgument) {
42782
+ async function resolveCoderPromptDescription(descriptionArgument, { isAskingQuestionsEnabled }) {
42475
42783
  if (descriptionArgument !== undefined && descriptionArgument.trim() !== '') {
42476
42784
  return descriptionArgument.trim();
42477
42785
  }
@@ -42489,6 +42797,14 @@
42489
42797
  }
42490
42798
  return standardInputDescription;
42491
42799
  }
42800
+ if (!isAskingQuestionsEnabled) {
42801
+ throw new ParseError(_spaceTrim.spaceTrim(`
42802
+ Cannot add a prompt without a description.
42803
+
42804
+ The description can not be typed interactively because \`--no-questions\` is used — provide it as an argument or pipe it through stdin instead:
42805
+ - \`ptbk coder add --no-questions "some new feature"\`
42806
+ `));
42807
+ }
42492
42808
  const { default: prompts } = await loadPromptsModule();
42493
42809
  const response = await prompts({
42494
42810
  type: 'text',
@@ -43052,10 +43368,10 @@
43052
43368
  const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
43053
43369
  const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
43054
43370
  console.info(`🚀 Generate prompt boilerplate files`);
43055
- fs.mkdirSync(path.join(projectPath, PROMPTS_DIRECTORY_PATH), { recursive: true });
43371
+ fs.mkdirSync(path.join(projectPath, PROMPTS_DIRECTORY_PATH$1), { recursive: true });
43056
43372
  const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
43057
43373
  const promptNumbering = await getPromptNumbering({
43058
- promptsDir: path.join(projectPath, PROMPTS_DIRECTORY_PATH),
43374
+ promptsDir: path.join(projectPath, PROMPTS_DIRECTORY_PATH$1),
43059
43375
  step: 10,
43060
43376
  ignoreGlobs: ['**/node_modules/**'],
43061
43377
  });
@@ -43079,7 +43395,7 @@
43079
43395
  const title = titles[i % titles.length];
43080
43396
  const emojiTag = formatPromptEmojiTag(selectedEmojis[i]);
43081
43397
  const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(promptTemplate.slugPrefix, title));
43082
- const filepath = path.join(PROMPTS_DIRECTORY_PATH, filename);
43398
+ const filepath = path.join(PROMPTS_DIRECTORY_PATH$1, filename);
43083
43399
  const absoluteFilepath = path.join(projectPath, filepath);
43084
43400
  const content = buildBoilerplatePromptFileContent({
43085
43401
  emojiTag,
@@ -43139,10 +43455,15 @@
43139
43455
  *
43140
43456
  * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin.
43141
43457
  *
43142
- * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive.
43458
+ * @returns `true` when the user confirms, `false` when the user declines, the terminal is not interactive
43459
+ * or the questions are disabled by `--no-questions`.
43143
43460
  * @private internal utility of `promptbookCli`
43144
43461
  */
43145
- async function $askForConfirmation(question) {
43462
+ async function $askForConfirmation(question, { isAskingQuestionsEnabled }) {
43463
+ if (!isAskingQuestionsEnabled) {
43464
+ // Note: `--no-questions` forbids asking, so the optional change is declined without blocking the run.
43465
+ return false;
43466
+ }
43146
43467
  if (!process.stdin.isTTY) {
43147
43468
  // Note: In non-interactive environments like CI there is nobody who could confirm the change.
43148
43469
  return false;
@@ -43165,11 +43486,12 @@
43165
43486
  *
43166
43487
  * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin
43167
43488
  *
43168
- * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive
43489
+ * @returns `true` when the user confirms, `false` when the user declines, the terminal is not interactive
43490
+ * or the questions are disabled by `--no-questions`
43169
43491
  * @private internal utility of `promptbookCli`
43170
43492
  */
43171
- async function $askForNpmPackageInstallationApproval(question) {
43172
- return $askForConfirmation(question);
43493
+ async function $askForNpmPackageInstallationApproval(question, questionsOptions) {
43494
+ return $askForConfirmation(question, questionsOptions);
43173
43495
  }
43174
43496
  // Note: [🟡] Code for CLI npm package installation approval [$askForNpmPackageInstallationApproval](src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts) should never be published outside of `@promptbook/cli`
43175
43497
 
@@ -43359,7 +43681,7 @@
43359
43681
  *
43360
43682
  * @private internal utility of `promptbookCli`
43361
43683
  */
43362
- async function $applyHarnessInstallationStatus(status) {
43684
+ async function $applyHarnessInstallationStatus(status, questionsOptions) {
43363
43685
  const { definition, installationState, installedVersion } = status;
43364
43686
  if (installationState === 'up-to-date') {
43365
43687
  console.info(colors__default["default"].gray(`✔ ${definition.label} ${installedVersion} is up to date.`));
@@ -43375,17 +43697,17 @@
43375
43697
  }
43376
43698
  console.warn(colors__default["default"].yellow(formatHarnessInstallationWarning(status)));
43377
43699
  if (installationState === 'not-installed') {
43378
- await $applyMissingHarnessInstallation(definition);
43700
+ await $applyMissingHarnessInstallation(definition, questionsOptions);
43379
43701
  return;
43380
43702
  }
43381
- await $applyOutdatedHarnessInstallation(status);
43703
+ await $applyOutdatedHarnessInstallation(status, questionsOptions);
43382
43704
  }
43383
43705
  /**
43384
43706
  * Offers to install a missing harness globally through npm.
43385
43707
  */
43386
- async function $applyMissingHarnessInstallation(definition) {
43708
+ async function $applyMissingHarnessInstallation(definition, questionsOptions) {
43387
43709
  const installCommand = buildHarnessInstallCommand(definition);
43388
- const isInstallationApproved = await $askForNpmPackageInstallationApproval(`Install ${definition.label} globally now?`);
43710
+ const isInstallationApproved = await $askForNpmPackageInstallationApproval(`Install ${definition.label} globally now?`, questionsOptions);
43389
43711
  if (!isInstallationApproved) {
43390
43712
  console.info(colors__default["default"].gray(`Skipped, run \`${installCommand}\` to do it manually.`));
43391
43713
  return;
@@ -43400,14 +43722,14 @@
43400
43722
  /**
43401
43723
  * Offers to update an outdated harness exactly where it is installed.
43402
43724
  */
43403
- async function $applyOutdatedHarnessInstallation(status) {
43725
+ async function $applyOutdatedHarnessInstallation(status, questionsOptions) {
43404
43726
  const { definition, latestVersion, installationOrigin } = status;
43405
43727
  const updatePlan = resolveHarnessUpdatePlan(definition, installationOrigin.installationMethod);
43406
43728
  if (updatePlan.command === null || !updatePlan.isRunnableByPromptbook) {
43407
43729
  console.info(colors__default["default"].gray(formatHarnessManualUpdateInstruction(status, updatePlan)));
43408
43730
  return;
43409
43731
  }
43410
- const isUpdateApproved = await $askForNpmPackageInstallationApproval(`Update ${definition.label} to ${latestVersion} with \`${updatePlan.command}\` now?`);
43732
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval(`Update ${definition.label} to ${latestVersion} with \`${updatePlan.command}\` now?`, questionsOptions);
43411
43733
  if (!isUpdateApproved) {
43412
43734
  console.info(colors__default["default"].gray(`Skipped, run \`${updatePlan.command}\` to do it manually.`));
43413
43735
  return;
@@ -43843,7 +44165,7 @@
43843
44165
  // Note: [🟡] Code for CLI harness installation check [$checkHarnessInstallation](src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts) should never be published outside of `@promptbook/cli`
43844
44166
 
43845
44167
  /**
43846
- * Checks that every given CLI coding harness is installed globally and, when enabled, up to date.
44168
+ * Checks that every given CLI coding harness is installed globally and, when the questions are enabled, up to date.
43847
44169
  * Offers to install missing harnesses and update outdated ones.
43848
44170
  *
43849
44171
  * Harness names which are `undefined`, for example when no `--harness` is selected in a dry run,
@@ -43853,15 +44175,18 @@
43853
44175
  *
43854
44176
  * @private internal utility of `promptbookCli`
43855
44177
  */
43856
- async function $ensureHarnessInstallations(harnessNames, isHarnessUpdateCheckEnabled = true) {
44178
+ async function $ensureHarnessInstallations(harnessNames, questionsOptions) {
43857
44179
  const definitions = resolveCheckedHarnessDefinitions(harnessNames);
43858
44180
  if (definitions.length === 0) {
43859
44181
  return;
43860
44182
  }
44183
+ // Note: An outdated harness can be updated only after the user confirms it, so `--no-questions`
44184
+ // skips the slower npm registry lookup entirely and only reports what is installed.
44185
+ const isHarnessUpdateCheckEnabled = questionsOptions.isAskingQuestionsEnabled;
43861
44186
  // Note: Detection of all harnesses runs in parallel, the questions have to be asked one by one
43862
44187
  const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled)));
43863
44188
  for (const status of statuses) {
43864
- await $applyHarnessInstallationStatus(status);
44189
+ await $applyHarnessInstallationStatus(status, questionsOptions);
43865
44190
  }
43866
44191
  }
43867
44192
  /**
@@ -43873,48 +44198,6 @@
43873
44198
  }
43874
44199
  // Note: [🟡] Code for CLI harness installation orchestration [$ensureHarnessInstallations](src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts) should never be published outside of `@promptbook/cli`
43875
44200
 
43876
- /**
43877
- * Registers the shared `--no-harness-update` option on a `ptbk coder` command that uses a coding harness.
43878
- *
43879
- * @private internal utility of `promptbookCli`
43880
- */
43881
- function addHarnessUpdateOption(command) {
43882
- command.option('--no-harness-update', 'Skip checking whether the installed coding harnesses are up to date');
43883
- }
43884
- /**
43885
- * Converts the Commander harness-update flag into the normalized harness-check option.
43886
- *
43887
- * @private internal utility of `promptbookCli`
43888
- */
43889
- function normalizeHarnessUpdateCliOptions(cliOptions) {
43890
- return {
43891
- isHarnessUpdateCheckEnabled: cliOptions.harnessUpdate,
43892
- };
43893
- }
43894
- // Note: [🟡] Code for CLI harness update options [harnessUpdateCliOptions](src/cli/cli-commands/common/harnessUpdateCliOptions.ts) should never be published outside of `@promptbook/cli`
43895
- // Note: [💞] Ignore a discrepancy between file name and exported helper names
43896
-
43897
- /**
43898
- * Relative path to the Promptbook Coder quick-reference file initialized in project roots.
43899
- *
43900
- * @private internal utility of `ptbk coder`
43901
- */
43902
- const AGENT_CODING_FILE_PATH = 'AGENT_CODING.md';
43903
- /**
43904
- * Returns the default coder `AGENT_CODING.md` quick-reference content.
43905
- *
43906
- * @private internal utility of `ptbk coder`
43907
- */
43908
- function getDefaultCoderAgentCodingFileContent() {
43909
- return _spaceTrim.spaceTrim(`
43910
- # ✨ Promptbook Coder agent coding
43911
-
43912
- This project is using [Promptbook Coder](https://coder.ptbk.io) or run \`ptbk coder\`!
43913
- `);
43914
- }
43915
- // Note: [🟡] Code for coder AGENT_CODING file boilerplate [agentCodingFile](src/cli/cli-commands/coder/agentCodingFile.ts) should never be published outside of `@promptbook/cli`
43916
- // Note: [💞] Ignore a discrepancy between file name and exported helper names
43917
-
43918
44201
  /**
43919
44202
  * Relative path to the shared coder context file initialized in project roots.
43920
44203
  *
@@ -44016,6 +44299,51 @@
44016
44299
  }
44017
44300
  // Note: [🟡] Code for coder path formatting [formatDisplayPath](src/cli/cli-commands/coder/formatDisplayPath.ts) should never be published outside of `@promptbook/cli`
44018
44301
 
44302
+ /**
44303
+ * Creates the artifact definition of one project-owned coder prompt template.
44304
+ */
44305
+ function createCoderPromptTemplateArtifactDefinition(definition) {
44306
+ return {
44307
+ relativeFilePath: definition.relativeFilePath,
44308
+ ensureArtifactFile: (projectPath) => ensureDefaultCoderPromptTemplateFile(projectPath, definition),
44309
+ };
44310
+ }
44311
+ /**
44312
+ * Every artifact which the default coder scripts can reference.
44313
+ */
44314
+ const CODER_REFERENCED_ARTIFACT_DEFINITIONS = [
44315
+ {
44316
+ relativeFilePath: CODER_DEVELOPER_AGENT_FILE_PATH,
44317
+ ensureArtifactFile: ensureCoderDeveloperAgentFile,
44318
+ },
44319
+ {
44320
+ relativeFilePath: AGENTS_FILE_PATH,
44321
+ ensureArtifactFile: (projectPath) => ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent()),
44322
+ },
44323
+ ...getDefaultCoderProjectPromptTemplateDefinitions().map(createCoderPromptTemplateArtifactDefinition),
44324
+ ];
44325
+ /**
44326
+ * Creates only those referenced artifacts which the freshly added coder scripts point at.
44327
+ *
44328
+ * An artifact of a script which the project already defines is intentionally **not** created - the project-owned
44329
+ * script is kept as is, so it does not reference the default artifact in the first place.
44330
+ *
44331
+ * @private function of `initializeCoderProjectConfiguration`
44332
+ */
44333
+ async function ensureCoderReferencedArtifacts(projectPath, referencedArtifactPaths) {
44334
+ const ensuredArtifacts = [];
44335
+ for (const { relativeFilePath, ensureArtifactFile } of CODER_REFERENCED_ARTIFACT_DEFINITIONS) {
44336
+ if (!referencedArtifactPaths.has(relativeFilePath)) {
44337
+ ensuredArtifacts.push({ relativeFilePath, status: 'not-referenced' });
44338
+ continue;
44339
+ }
44340
+ ensuredArtifacts.push({ relativeFilePath, status: await ensureArtifactFile(projectPath) });
44341
+ }
44342
+ return ensuredArtifacts;
44343
+ }
44344
+ // Note: [🟡] Code for coder init referenced artifacts [coderReferencedArtifacts](src/cli/cli-commands/coder/coderReferencedArtifacts.ts) should never be published outside of `@promptbook/cli`
44345
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44346
+
44019
44347
  /**
44020
44348
  * Fallback `.env` content used when no required variables need to be appended.
44021
44349
  */
@@ -44110,26 +44438,79 @@
44110
44438
  }
44111
44439
  // Note: [🟡] Code for coder init gitignore bootstrapping [ensureCoderGitignoreFile](src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts) should never be published outside of `@promptbook/cli`
44112
44440
 
44441
+ /**
44442
+ * Name of the npm script which `ptbk coder run` uses to verify the project.
44443
+ */
44444
+ const CODER_TEST_SCRIPT_NAME = 'test-for-ptbk-coder';
44113
44445
  /**
44114
44446
  * Default npm scripts initialized by `ptbk coder init`.
44447
+ *
44448
+ * Note: Using NPX because `ptbk` can be installed globally or locally, and NPX will resolve it correctly in either case.
44115
44449
  */
44116
- const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
44117
- // Note: Using NPX because `ptbk` can be installed globally or locally, and NPX will resolve it correctly in either case.
44118
- 'coder:generate-boilerplates': `npx ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ./prompts/templates/common.md`,
44119
- 'coder:add': 'npx ptbk coder add --template ./prompts/templates/common.md',
44120
- 'coder:run': 'npx ptbk coder run --harness openai-codex --model gpt-5.6-terra --thinking-level max --agent agents/developer.book --context AGENTS.md --test-before yes-and-fix',
44121
- // 'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
44122
- 'coder:verify': 'npx ptbk coder verify',
44123
- };
44450
+ const DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS = [
44451
+ {
44452
+ scriptName: 'coder:generate-boilerplates',
44453
+ scriptCommand: `npx ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ${formatCoderScriptFilePath(COMMON_PROMPT_TEMPLATE_FILE_PATH)}`,
44454
+ referencedArtifactPaths: [COMMON_PROMPT_TEMPLATE_FILE_PATH],
44455
+ },
44456
+ {
44457
+ scriptName: 'coder:add',
44458
+ scriptCommand: `npx ptbk coder add --template ${formatCoderScriptFilePath(COMMON_PROMPT_TEMPLATE_FILE_PATH)}`,
44459
+ referencedArtifactPaths: [COMMON_PROMPT_TEMPLATE_FILE_PATH],
44460
+ },
44461
+ {
44462
+ scriptName: 'coder:run',
44463
+ scriptCommand: [
44464
+ 'npx ptbk coder run --harness openai-codex --model gpt-5.6-terra --thinking-level max',
44465
+ `--agent ${formatDisplayPath(CODER_DEVELOPER_AGENT_FILE_PATH)}`,
44466
+ `--context ${formatDisplayPath(AGENTS_FILE_PATH)}`,
44467
+ `--test "npm run ${CODER_TEST_SCRIPT_NAME}" --test-before yes-and-fix`,
44468
+ ].join(' '),
44469
+ referencedArtifactPaths: [CODER_DEVELOPER_AGENT_FILE_PATH, AGENTS_FILE_PATH],
44470
+ },
44471
+ // { scriptName: 'coder:find-refactor-candidates', scriptCommand: 'npx ptbk coder find-refactor-candidates', referencedArtifactPaths: [] },
44472
+ {
44473
+ scriptName: 'coder:verify',
44474
+ scriptCommand: 'npx ptbk coder verify',
44475
+ referencedArtifactPaths: [],
44476
+ },
44477
+ {
44478
+ // Note: The verification command of `coder:run` is a project-owned script, so every project can decide
44479
+ // what "verified" means without touching the `coder:run` script itself
44480
+ scriptName: CODER_TEST_SCRIPT_NAME,
44481
+ scriptCommand: 'npm test',
44482
+ referencedArtifactPaths: [],
44483
+ },
44484
+ ];
44124
44485
  /**
44125
44486
  * Lists the default npm scripts initialized by `ptbk coder init`.
44126
44487
  *
44127
44488
  * @private internal utility of `coder init` command
44128
44489
  */
44129
44490
  function getDefaultCoderPackageJsonScripts() {
44130
- return DEFAULT_CODER_PACKAGE_JSON_SCRIPTS;
44491
+ return Object.fromEntries(DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS.map(({ scriptName, scriptCommand }) => [
44492
+ scriptName,
44493
+ scriptCommand,
44494
+ ]));
44495
+ }
44496
+ /**
44497
+ * Collects the project-relative artifact paths referenced by the given default coder scripts.
44498
+ *
44499
+ * @private internal utility of `coder init` command
44500
+ */
44501
+ function resolveCoderPackageJsonScriptReferencedArtifactPaths(scriptNames) {
44502
+ return new Set(DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS.filter(({ scriptName }) => scriptNames.includes(scriptName))
44503
+ .map(({ referencedArtifactPaths }) => referencedArtifactPaths)
44504
+ .flat());
44505
+ }
44506
+ /**
44507
+ * Formats one project-relative path as an explicitly project-rooted path usable inside a shell command.
44508
+ */
44509
+ function formatCoderScriptFilePath(relativeFilePath) {
44510
+ return `./${formatDisplayPath(relativeFilePath)}`;
44131
44511
  }
44132
44512
  // Note: [🟡] Code for coder init package scripts [getDefaultCoderPackageJsonScripts](src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts) should never be published outside of `@promptbook/cli`
44513
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44133
44514
 
44134
44515
  /**
44135
44516
  * Loads the TypeScript runtime used for parsing JSONC-style project files.
@@ -44162,6 +44543,9 @@
44162
44543
  /**
44163
44544
  * Ensures one JSON object field contains the provided string-record entries.
44164
44545
  *
44546
+ * Entries which the project already defines are **never** overridden - only missing keys are added,
44547
+ * so hand-tuned scripts and settings survive every repeated `ptbk coder init`.
44548
+ *
44165
44549
  * @private function of `initializeCoderProjectConfiguration`
44166
44550
  */
44167
44551
  async function mergeStringRecordJsonFile({ projectPath, relativeFilePath, fieldPath, nextEntries, ensureParentDirectoryPath, }) {
@@ -44173,21 +44557,24 @@
44173
44557
  const formatting = detectJsonFileFormatting(fileContent);
44174
44558
  const jsonObject = fileContent === undefined ? {} : await parseJsonObjectFile(relativeFilePath, fileContent);
44175
44559
  const existingEntries = getStringRecordOrDefault(jsonObject[fieldPath], relativeFilePath, fieldPath);
44176
- let hasChanges = fileContent === undefined;
44560
+ const addedEntryKeys = [];
44177
44561
  const mergedEntries = { ...existingEntries };
44178
44562
  for (const [entryKey, entryValue] of Object.entries(nextEntries)) {
44179
- if (mergedEntries[entryKey] !== entryValue) {
44180
- mergedEntries[entryKey] = entryValue;
44181
- hasChanges = true;
44563
+ if (Object.prototype.hasOwnProperty.call(mergedEntries, entryKey)) {
44564
+ // Note: The project already defines this entry, keep its own value untouched
44565
+ continue;
44182
44566
  }
44567
+ mergedEntries[entryKey] = entryValue;
44568
+ addedEntryKeys.push(entryKey);
44183
44569
  }
44570
+ const hasChanges = fileContent === undefined || addedEntryKeys.length > 0;
44184
44571
  if (!hasChanges) {
44185
- return 'unchanged';
44572
+ return { status: 'unchanged', addedEntryKeys };
44186
44573
  }
44187
44574
  const nextJsonObject = { ...jsonObject };
44188
44575
  nextJsonObject[fieldPath] = mergedEntries;
44189
44576
  await promises.writeFile(absoluteFilePath, serializeJsonObject(nextJsonObject, formatting), 'utf-8');
44190
- return fileContent === undefined ? 'created' : 'updated';
44577
+ return { status: fileContent === undefined ? 'created' : 'updated', addedEntryKeys };
44191
44578
  }
44192
44579
  /**
44193
44580
  * Parses one JSON object file while accepting VS Code style comments and trailing commas.
@@ -44276,6 +44663,8 @@
44276
44663
  /**
44277
44664
  * Ensures `package.json` contains the standalone Promptbook coder helper scripts.
44278
44665
  *
44666
+ * Scripts which the project already defines are kept as they are, only missing ones are added.
44667
+ *
44279
44668
  * @private function of `initializeCoderProjectConfiguration`
44280
44669
  */
44281
44670
  async function ensureCoderPackageJsonFile(projectPath) {
@@ -44329,17 +44718,20 @@
44329
44718
  /**
44330
44719
  * Ensures VS Code routes pasted prompt images into `prompts/screenshots`.
44331
44720
  *
44721
+ * Settings which the project already defines are kept as they are, only missing ones are added.
44722
+ *
44332
44723
  * @private function of `initializeCoderProjectConfiguration`
44333
44724
  */
44334
44725
  async function ensureCoderVscodeSettingsFile(projectPath) {
44335
44726
  const [fieldPath, nextEntries] = resolveDefaultCoderVscodeSettingsEntry();
44336
- return mergeStringRecordJsonFile({
44727
+ const { status } = await mergeStringRecordJsonFile({
44337
44728
  projectPath,
44338
44729
  relativeFilePath: VSCODE_SETTINGS_FILE_PATH,
44339
44730
  fieldPath,
44340
44731
  nextEntries,
44341
44732
  ensureParentDirectoryPath: VSCODE_DIRECTORY_PATH,
44342
44733
  });
44734
+ return status;
44343
44735
  }
44344
44736
  /**
44345
44737
  * Resolves the default string-record entry that `coder init` merges into VS Code settings.
@@ -44356,34 +44748,32 @@
44356
44748
  /**
44357
44749
  * Creates or updates all coder configuration artifacts required in the current project.
44358
44750
  *
44751
+ * Nothing the project already owns is ever overwritten - existing scripts, settings and files are kept as they are,
44752
+ * and the artifacts they would reference are created only together with the scripts which actually reference them.
44753
+ *
44359
44754
  * @private internal utility of `coder init` command
44360
44755
  */
44361
44756
  async function initializeCoderProjectConfiguration(projectPath) {
44362
- const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
44757
+ const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH$1);
44363
44758
  const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
44364
44759
  const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
44365
- const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
44366
44760
  const agentsDirectoryStatus = await ensureDirectory(projectPath, CODER_AGENTS_DIRECTORY_PATH);
44367
- const developerAgentFileStatus = await ensureCoderDeveloperAgentFile(projectPath);
44368
- const agentsFileStatus = await ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent());
44369
- const agentCodingFileStatus = await ensureCoderMarkdownFile(projectPath, AGENT_CODING_FILE_PATH, getDefaultCoderAgentCodingFileContent());
44370
44761
  const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
44371
44762
  const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
44372
- const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
44763
+ const { status: packageJsonFileStatus, addedEntryKeys: addedPackageJsonScriptNames } = await ensureCoderPackageJsonFile(projectPath);
44373
44764
  const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
44765
+ const referencedArtifactStatuses = await ensureCoderReferencedArtifacts(projectPath, resolveCoderPackageJsonScriptReferencedArtifactPaths(addedPackageJsonScriptNames));
44374
44766
  return {
44375
44767
  promptsDirectoryStatus,
44376
44768
  promptsDoneDirectoryStatus,
44377
44769
  promptsTemplatesDirectoryStatus,
44378
- promptTemplateFileStatuses,
44379
44770
  agentsDirectoryStatus,
44380
- developerAgentFileStatus,
44381
- agentsFileStatus,
44382
- agentCodingFileStatus,
44383
44771
  envFileStatus,
44384
44772
  gitignoreFileStatus,
44385
44773
  packageJsonFileStatus,
44386
44774
  vscodeSettingsFileStatus,
44775
+ addedPackageJsonScriptNames,
44776
+ referencedArtifactStatuses,
44387
44777
  initializedEnvVariableNames,
44388
44778
  };
44389
44779
  }
@@ -44399,17 +44789,20 @@
44399
44789
  printInitializationStatusLine('prompts/', summary.promptsDirectoryStatus);
44400
44790
  printInitializationStatusLine('prompts/done/', summary.promptsDoneDirectoryStatus);
44401
44791
  printInitializationStatusLine('prompts/templates/', summary.promptsTemplatesDirectoryStatus);
44402
- for (const templateFileStatus of summary.promptTemplateFileStatuses) {
44403
- printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
44404
- }
44405
44792
  printInitializationStatusLine('agents/', summary.agentsDirectoryStatus);
44406
- printInitializationStatusLine(CODER_DEVELOPER_AGENT_FILE_PATH, summary.developerAgentFileStatus);
44407
- printInitializationStatusLine(AGENTS_FILE_PATH, summary.agentsFileStatus);
44408
- printInitializationStatusLine(AGENT_CODING_FILE_PATH, summary.agentCodingFileStatus);
44409
44793
  printInitializationStatusLine('.env', summary.envFileStatus);
44410
44794
  printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
44411
44795
  printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
44412
44796
  printInitializationStatusLine('.vscode/settings.json', summary.vscodeSettingsFileStatus);
44797
+ for (const referencedArtifactStatus of summary.referencedArtifactStatuses) {
44798
+ printInitializationStatusLine(formatDisplayPath(referencedArtifactStatus.relativeFilePath), referencedArtifactStatus.status);
44799
+ }
44800
+ if (summary.addedPackageJsonScriptNames.length > 0) {
44801
+ printInitializationNote(`Added npm scripts: ${summary.addedPackageJsonScriptNames.join(', ')}`, colors__default["default"].cyan);
44802
+ }
44803
+ else {
44804
+ printInitializationNote('All Promptbook coder npm scripts are already present.', colors__default["default"].gray);
44805
+ }
44413
44806
  if (summary.initializedEnvVariableNames.length > 0) {
44414
44807
  printInitializationNote(`Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`, colors__default["default"].cyan);
44415
44808
  }
@@ -44427,6 +44820,9 @@
44427
44820
  if (status === 'updated') {
44428
44821
  return 'updated';
44429
44822
  }
44823
+ if (status === 'not-referenced') {
44824
+ return 'not referenced by the added scripts, kept as is';
44825
+ }
44430
44826
  return 'unchanged';
44431
44827
  }
44432
44828
  /**
@@ -44469,39 +44865,48 @@
44469
44865
  ${block(listDefaultCoderProjectPromptTemplateDisplayPaths())}
44470
44866
  - ${CODER_DEVELOPER_AGENT_FILE_PATH}
44471
44867
  - ${AGENTS_FILE_PATH}
44472
- - ${AGENT_CODING_FILE_PATH}
44473
44868
  - .gitignore with local artifacts from every supported harness
44474
44869
  - package.json
44475
44870
  - .vscode/settings.json
44476
44871
 
44872
+ Never overwrites what the project already owns:
44873
+ - Existing package.json scripts and .vscode/settings.json settings are kept, only missing ones are added
44874
+ - Files referenced by a script, like the agent and context of coder:run, are created only together
44875
+ with the script which references them
44876
+
44477
44877
  Ensures required coding-agent environment variables in .env:
44478
44878
  - CODING_AGENT_GIT_NAME
44479
44879
  - CODING_AGENT_GIT_EMAIL
44480
44880
  - CODING_AGENT_GIT_SIGNING_KEY
44481
44881
 
44482
- Checks that the coding harnesses are installed and up to date unless \`--no-harness-update\` is used:
44882
+ Checks that the coding harnesses are installed and up to date unless \`--no-questions\` is used:
44483
44883
  ${block(listCheckedHarnessLabels())}
44484
44884
 
44485
44885
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
44886
+
44887
+ ${block(QUESTIONS_DESCRIPTION)}
44486
44888
  `));
44487
44889
  addCoderGitSyncOptions(command);
44488
- addHarnessUpdateOption(command);
44890
+ addQuestionsOption(command);
44489
44891
  command.action(handleActionErrors(async (cliOptions) => {
44490
44892
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
44491
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
44893
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
44492
44894
  const projectPath = process.cwd();
44493
44895
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
44494
44896
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
44495
44897
  const commitScope = await $startCoderGitSync({ gitSync, projectPath });
44898
+ const isPromptsDirectoryEmpty = await isDirectoryEmpty(projectPath, PROMPTS_DIRECTORY_PATH$1);
44496
44899
  const summary = await initializeCoderProjectConfiguration(projectPath);
44497
44900
  printInitializationSummary(summary);
44498
- await generatePromptBoilerplate({ projectPath, boilerplateCount: DEFAULT_BOILERPLATE_COUNT });
44901
+ if (isPromptsDirectoryEmpty) {
44902
+ await generatePromptBoilerplate({ projectPath, boilerplateCount: DEFAULT_BOILERPLATE_COUNT });
44903
+ }
44499
44904
  await $commitCoderChanges({
44500
44905
  gitSync,
44501
44906
  commitScope,
44502
44907
  commitMessage: 'Initialize Promptbook Coder',
44503
44908
  });
44504
- await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, isHarnessUpdateCheckEnabled);
44909
+ await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, questionsOptions);
44505
44910
  }));
44506
44911
  }
44507
44912
  /**
@@ -44521,6 +44926,112 @@
44521
44926
  // Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
44522
44927
  // Note: [💞] Ignore a discrepancy between file name and entity name
44523
44928
 
44929
+ /**
44930
+ * Creates a Commander argument parser that accepts only non-negative integers.
44931
+ *
44932
+ * The returned parser is meant to be passed as the coercion callback of `command.option(...)`.
44933
+ * It throws a branded `NotAllowed` error with a clear message referencing the given option name
44934
+ * when the provided value is not a non-negative integer.
44935
+ *
44936
+ * @private internal utility of `promptbookCli`
44937
+ */
44938
+ function createNonNegativeIntegerOptionParser(optionName) {
44939
+ return (value) => {
44940
+ const parsedValue = Number(value);
44941
+ if (!Number.isInteger(parsedValue) || parsedValue < 0) {
44942
+ throw new NotAllowed(_spaceTrim.spaceTrim(`
44943
+ Invalid value for \`${optionName}\`: \`${value}\`.
44944
+
44945
+ Use a non-negative integer.
44946
+ `));
44947
+ }
44948
+ return parsedValue;
44949
+ };
44950
+ }
44951
+ // Note: [🟡] Code for CLI option parser [createNonNegativeIntegerOptionParser](src/cli/cli-commands/common/createNonNegativeIntegerOptionParser.ts) should never be published outside of `@promptbook/cli`
44952
+
44953
+ /**
44954
+ * Registers the shared prompt-priority filter options on a queue-based `ptbk coder` command.
44955
+ *
44956
+ * @private internal utility of `promptbookCli`
44957
+ */
44958
+ function addPromptPriorityOptions(command) {
44959
+ command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
44960
+ command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
44961
+ command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
44962
+ }
44963
+ // Note: [🟡] Code for CLI prompt priority options [promptPriorityCliOptions](src/cli/cli-commands/common/promptPriorityCliOptions.ts) should never be published outside of `@promptbook/cli`
44964
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44965
+
44966
+ /**
44967
+ * Description shared by coder commands which accept an agent Book.
44968
+ *
44969
+ * @private internal utility of `ptbk coder`
44970
+ */
44971
+ const CODER_AGENT_OPTION_DESCRIPTION = 'Path to a .book file used to personalize coding prompts and select agent-specific tasks';
44972
+ /**
44973
+ * Registers the shared `--agent` option for coder commands.
44974
+ *
44975
+ * @private internal utility of `ptbk coder`
44976
+ */
44977
+ function addCoderAgentOption(command) {
44978
+ command.option('--agent <agent-book-path>', CODER_AGENT_OPTION_DESCRIPTION);
44979
+ }
44980
+ // Note: [🟡] Code for CLI coder agent options should never be published outside of `@promptbook/cli`
44981
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44982
+
44983
+ /**
44984
+ * Initializes `coder list` command for Promptbook CLI utilities.
44985
+ *
44986
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI.
44987
+ *
44988
+ * @private internal function of `promptbookCli`
44989
+ */
44990
+ function $initializeCoderListCommand(program) {
44991
+ const command = program.command('list');
44992
+ command.description(_spaceTrim.spaceTrim(`
44993
+ List ready coding prompts by priority without executing them
44994
+
44995
+ ${PROMPT_RUNNER_DESCRIPTION}
44996
+
44997
+ Features:
44998
+ - Lists only ready, fully authored prompts
44999
+ - Groups prompts from highest to lowest priority
45000
+ - Optional --harness, --model and --agent filters show only prompts compatible with that selection
45001
+ - Does not start a coding harness or modify prompt files
45002
+ `));
45003
+ addPromptRunnerSelectionOptions(command);
45004
+ addCoderAgentOption(command);
45005
+ addPromptPriorityOptions(command);
45006
+ command.action(handleActionErrors(async (cliOptions) => {
45007
+ const { agent, priority, minPriority: minimumPriority, maxPriority: maximumPriority, } = cliOptions;
45008
+ const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: false });
45009
+ const resolvedCoderAgentBook = agent === undefined
45010
+ ? undefined
45011
+ : await (await Promise.resolve().then(function () { return resolveCoderAgent$1; }))
45012
+ .resolveCoderAgentBook(agent, process.cwd());
45013
+ const promptRunnerIdentity = runnerOptions.agentName === undefined &&
45014
+ runnerOptions.model === undefined &&
45015
+ resolvedCoderAgentBook === undefined
45016
+ ? undefined
45017
+ : {
45018
+ harnessName: runnerOptions.agentName,
45019
+ modelName: runnerOptions.model,
45020
+ agentReferences: resolvedCoderAgentBook === null || resolvedCoderAgentBook === void 0 ? void 0 : resolvedCoderAgentBook.agentReferences,
45021
+ };
45022
+ // Note: Import dynamically to avoid loading prompt parsing dependencies until this command is used.
45023
+ const { listCoderPrompts } = await Promise.resolve().then(function () { return listCoderPrompts$1; });
45024
+ await listCoderPrompts({
45025
+ priority,
45026
+ minimumPriority,
45027
+ maximumPriority,
45028
+ promptRunnerIdentity,
45029
+ });
45030
+ }));
45031
+ }
45032
+ // Note: [🟡] Code for CLI command [list](src/cli/cli-commands/coder/list.ts) should never be published outside of `@promptbook/cli`
45033
+ // Note: [💞] Ignore a discrepancy between file name and entity name
45034
+
44524
45035
  /**
44525
45036
  * Default wait duration applied before retrying a prompt round after an error (10 minutes).
44526
45037
  *
@@ -44574,7 +45085,7 @@
44574
45085
  *
44575
45086
  * @private internal utility of `ptbk coder`
44576
45087
  */
44577
- async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName) {
45088
+ async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName, questionsOptions) {
44578
45089
  if (harnessName === undefined) {
44579
45090
  return;
44580
45091
  }
@@ -44585,7 +45096,7 @@
44585
45096
  const { label } = getHarnessDefinition(harnessName);
44586
45097
  const formattedRules = formatGitignoreRules(missingRules);
44587
45098
  const entryLabel = missingRules.length === 1 ? 'entry' : 'entries';
44588
- const isAdditionApproved = await $askForConfirmation(`Add the missing ${label} ignore ${entryLabel} ${formattedRules} to \`.gitignore\` now?`);
45099
+ const isAdditionApproved = await $askForConfirmation(`Add the missing ${label} ignore ${entryLabel} ${formattedRules} to \`.gitignore\` now?`, questionsOptions);
44589
45100
  if (!isAdditionApproved) {
44590
45101
  console.info(colors__default["default"].gray(`Skipped, add ${formattedRules} to \`.gitignore\` manually.`));
44591
45102
  return;
@@ -44610,22 +45121,24 @@
44610
45121
  */
44611
45122
  function $initializeCoderPingCommand(program) {
44612
45123
  const command = program.command('ping');
44613
- command.description(_spaceTrim.spaceTrim(`
44614
- Send one tiny dummy prompt to a harness and model to measure and warm them up
45124
+ command.description(_spaceTrim.spaceTrim((block) => `
45125
+ Send one tiny dummy prompt to a harness and model to measure and warm them up
44615
45126
 
44616
- ${PROMPT_RUNNER_DESCRIPTION}
45127
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
44617
45128
 
44618
- Features:
44619
- - Verifies that the selected harness, model, thinking level and authentication really work
44620
- - Reports the answer of the harness, the response time and the reported usage
44621
- - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
44622
- - Optional --period keeps the quota window refreshing by pinging once per period until stopped
44623
- - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
44624
- - Checks that the selected harness is installed and up to date unless --no-harness-update is used
44625
- - Use --no-ui to stream the raw harness output instead of only the compact result
44626
- `));
45129
+ ${block(QUESTIONS_DESCRIPTION)}
45130
+
45131
+ Features:
45132
+ - Verifies that the selected harness, model, thinking level and authentication really work
45133
+ - Reports the answer of the harness, the response time and the reported usage
45134
+ - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
45135
+ - Optional --period keeps the quota window refreshing by pinging once per period until stopped
45136
+ - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
45137
+ - Checks that the selected harness is installed and up to date unless --no-questions is used
45138
+ - Use --no-ui to stream the raw harness output instead of only the compact result
45139
+ `));
44627
45140
  addPromptRunnerSelectionOptions(command);
44628
- addHarnessUpdateOption(command);
45141
+ addQuestionsOption(command);
44629
45142
  addPromptRunnerRuntimeOptions(command);
44630
45143
  command.option('--period <duration>', _spaceTrim.spaceTrim(`
44631
45144
  Keep pinging once per period instead of pinging only once.
@@ -44634,11 +45147,11 @@
44634
45147
  command.action(handleActionErrors(async (cliOptions) => {
44635
45148
  const { period: periodValue } = cliOptions;
44636
45149
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: true });
44637
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45150
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
44638
45151
  // Note: The period is validated before the harness installation check, so a mistyped duration fails fast
44639
45152
  const periodMs = parseOptionalPeriodDuration('--period', periodValue);
44640
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
44641
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45153
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45154
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
44642
45155
  const pingOptions = {
44643
45156
  agentName: runnerOptions.agentName,
44644
45157
  model: runnerOptions.model,
@@ -44662,28 +45175,157 @@
44662
45175
  // Note: [💞] Ignore a discrepancy between file name and entity name
44663
45176
 
44664
45177
  /**
44665
- * Creates a Commander argument parser that accepts only non-negative integers.
45178
+ * Free disk space below which `ptbk coder` refuses to start a new run.
44666
45179
  *
44667
- * The returned parser is meant to be passed as the coercion callback of `command.option(...)`.
44668
- * It throws a branded `NotAllowed` error with a clear message referencing the given option name
44669
- * when the provided value is not a non-negative integer.
45180
+ * A coding round writes prompt and log artifacts, lets the harness rewrite the project, may install npm packages,
45181
+ * may create a git worktree for `--isolate` and usually runs a build, so a run started with less than this
45182
+ * would very likely die somewhere in the middle with a filesystem error.
44670
45183
  *
44671
45184
  * @private internal utility of `promptbookCli`
44672
45185
  */
44673
- function createNonNegativeIntegerOptionParser(optionName) {
44674
- return (value) => {
44675
- const parsedValue = Number(value);
44676
- if (!Number.isInteger(parsedValue) || parsedValue < 0) {
44677
- throw new NotAllowed(_spaceTrim.spaceTrim(`
44678
- Invalid value for \`${optionName}\`: \`${value}\`.
45186
+ const LOW_FREE_DISK_SPACE_BYTES = 2 * 1024 * 1024 * 1024;
45187
+ /**
45188
+ * Free disk space below which a started `ptbk coder` run stops at its next checkpoint and waits until space is freed.
45189
+ *
45190
+ * This limit is lower than `LOW_FREE_DISK_SPACE_BYTES`, because interrupting a run which is already in progress
45191
+ * is more expensive than not starting one at all.
45192
+ *
45193
+ * @private internal utility of `promptbookCli`
45194
+ */
45195
+ const CRITICALLY_LOW_FREE_DISK_SPACE_BYTES = 1 * 1024 * 1024 * 1024;
45196
+ /**
45197
+ * How long one free disk space measurement is reused before the filesystem is measured again during a run.
45198
+ *
45199
+ * Pause checkpoints are passed very often - for example on every tick of a timed wait - so without this
45200
+ * interval the filesystem would be measured many times per second.
45201
+ *
45202
+ * @private internal utility of `promptbookCli`
45203
+ */
45204
+ const FREE_DISK_SPACE_RECHECK_INTERVAL_MS = 60 * 1000;
45205
+ // Note: [🟡] Code for CLI free disk space handling [freeDiskSpaceConstants](src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.ts) should never be published outside of `@promptbook/cli`
45206
+ // Note: [💞] Ignore a discrepancy between file name and entity name
44679
45207
 
44680
- Use a non-negative integer.
44681
- `));
45208
+ /**
45209
+ * Classifies one measured amount of free disk space against the `ptbk coder` free disk space limits.
45210
+ *
45211
+ * @private internal utility of `promptbookCli`
45212
+ */
45213
+ function resolveFreeDiskSpaceLevel(availableBytes) {
45214
+ if (availableBytes < CRITICALLY_LOW_FREE_DISK_SPACE_BYTES) {
45215
+ return 'critical';
45216
+ }
45217
+ if (availableBytes < LOW_FREE_DISK_SPACE_BYTES) {
45218
+ return 'low';
45219
+ }
45220
+ return 'sufficient';
45221
+ }
45222
+ // Note: [🟡] Code for CLI free disk space handling [resolveFreeDiskSpaceLevel](src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.ts) should never be published outside of `@promptbook/cli`
45223
+
45224
+ /**
45225
+ * Measures the free disk space on the filesystem which hosts the given path.
45226
+ *
45227
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the state of the filesystem
45228
+ *
45229
+ * @returns the measurement, or `null` when the filesystem can not be measured, for example on a platform which
45230
+ * does not report filesystem statistics - a run is never stopped by a measurement which could not be taken
45231
+ * @private internal utility of `promptbookCli`
45232
+ */
45233
+ async function $readFreeDiskSpaceStatus(inspectedPath) {
45234
+ try {
45235
+ const fileSystemStats = await promises.statfs(inspectedPath);
45236
+ const blockSizeBytes = fileSystemStats.bsize;
45237
+ const availableBytes = fileSystemStats.bavail * blockSizeBytes;
45238
+ const totalBytes = fileSystemStats.blocks * blockSizeBytes;
45239
+ if (!Number.isFinite(availableBytes) || availableBytes < 0) {
45240
+ return null;
44682
45241
  }
44683
- return parsedValue;
44684
- };
45242
+ return {
45243
+ inspectedPath,
45244
+ availableBytes,
45245
+ totalBytes,
45246
+ level: resolveFreeDiskSpaceLevel(availableBytes),
45247
+ };
45248
+ }
45249
+ catch (_a) {
45250
+ // Note: A path or a platform which can not report its free disk space must not stop `ptbk coder` from running
45251
+ return null;
45252
+ }
44685
45253
  }
44686
- // Note: [🟡] Code for CLI option parser [createNonNegativeIntegerOptionParser](src/cli/cli-commands/common/createNonNegativeIntegerOptionParser.ts) should never be published outside of `@promptbook/cli`
45254
+ // Note: [🟡] Code for CLI free disk space handling [$readFreeDiskSpaceStatus](src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.ts) should never be published outside of `@promptbook/cli`
45255
+
45256
+ /**
45257
+ * Byte units used when one amount of disk space is written for a human.
45258
+ */
45259
+ const FREE_DISK_SPACE_BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
45260
+ /**
45261
+ * Writes one byte count as a compact human-readable amount of disk space, for example `812 MB` or `1.5 GB`.
45262
+ *
45263
+ * @private internal utility of `promptbookCli`
45264
+ */
45265
+ function formatFreeDiskSpaceBytes(bytes) {
45266
+ if (!Number.isFinite(bytes) || bytes <= 0) {
45267
+ return '0 B';
45268
+ }
45269
+ const unitIndex = Math.min(FREE_DISK_SPACE_BYTE_UNITS.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
45270
+ const value = bytes / Math.pow(1024, unitIndex);
45271
+ // Note: One decimal place only matters for small values, `476 GB` is more readable than `476.3 GB`
45272
+ const decimalPlaceCount = value >= 10 || unitIndex === 0 ? 0 : 1;
45273
+ // Note: An empty decimal place only adds noise, `2 GB` is more readable than `2.0 GB`
45274
+ const displayedValue = value.toFixed(decimalPlaceCount).replace(/\.0$/, '');
45275
+ return `${displayedValue} ${FREE_DISK_SPACE_BYTE_UNITS[unitIndex]}`;
45276
+ }
45277
+ // Note: [🟡] Code for CLI free disk space handling [formatFreeDiskSpaceBytes](src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.ts) should never be published outside of `@promptbook/cli`
45278
+
45279
+ /**
45280
+ * Writes the shared warning which explains one low free disk space measurement and how to get out of it.
45281
+ *
45282
+ * The very same wording is used by the check which refuses to start a run and by the check which pauses
45283
+ * a run that ran out of disk space while it was working.
45284
+ *
45285
+ * @private internal utility of `promptbookCli`
45286
+ */
45287
+ function formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus) {
45288
+ const { inspectedPath } = freeDiskSpaceStatus;
45289
+ const availableSpace = formatFreeDiskSpaceBytes(freeDiskSpaceStatus.availableBytes);
45290
+ const totalSpace = formatFreeDiskSpaceBytes(freeDiskSpaceStatus.totalBytes);
45291
+ const requiredSpaceToStart = formatFreeDiskSpaceBytes(LOW_FREE_DISK_SPACE_BYTES);
45292
+ const requiredSpaceToContinue = formatFreeDiskSpaceBytes(CRITICALLY_LOW_FREE_DISK_SPACE_BYTES);
45293
+ return _spaceTrim.spaceTrim(`
45294
+ Only **${availableSpace}** of ${totalSpace} is left on the disk which hosts \`${inspectedPath}\`.
45295
+
45296
+ **\`ptbk coder\` needs at least ${requiredSpaceToStart} of free disk space to start a run and at least ${requiredSpaceToContinue} to keep one running.**
45297
+
45298
+ Actionable hints:
45299
+ - Free up disk space on the drive which hosts \`${inspectedPath}\`.
45300
+ - Remove build artifacts, package caches and \`node_modules\` folders of projects you are not working on.
45301
+ - Remove the temporary worktrees which earlier \`--isolate\` runs left behind.
45302
+ `);
45303
+ }
45304
+ // Note: [🟡] Code for CLI free disk space handling [formatLowFreeDiskSpaceWarning](src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.ts) should never be published outside of `@promptbook/cli`
45305
+
45306
+ /**
45307
+ * Refuses to start one `ptbk coder` command when the disk which hosts the project is nearly full.
45308
+ *
45309
+ * A run which starts without enough free disk space fails somewhere in the middle, after the coding agent has
45310
+ * already rewritten a part of the project, so it is stopped before it writes anything at all.
45311
+ *
45312
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the state of the filesystem
45313
+ *
45314
+ * @throws {LimitReachedError} when there is not enough free disk space left to start
45315
+ * @private internal utility of `promptbookCli`
45316
+ */
45317
+ async function $assertSufficientFreeDiskSpace(inspectedPath) {
45318
+ const freeDiskSpaceStatus = await $readFreeDiskSpaceStatus(inspectedPath);
45319
+ if (freeDiskSpaceStatus === null || freeDiskSpaceStatus.level === 'sufficient') {
45320
+ return;
45321
+ }
45322
+ throw new LimitReachedError(_spaceTrim.spaceTrim((block) => `
45323
+ There is not enough free disk space to run \`ptbk coder\`.
45324
+
45325
+ ${block(formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus))}
45326
+ `));
45327
+ }
45328
+ // Note: [🟡] Code for CLI free disk space handling [$assertSufficientFreeDiskSpace](src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.ts) should never be published outside of `@promptbook/cli`
44687
45329
 
44688
45330
  /**
44689
45331
  * Npm packages which directly provide the `ptbk` executable.
@@ -45013,7 +45655,11 @@
45013
45655
  * @returns `true` when at least one installation was updated and the current coder run should stop
45014
45656
  * @private internal utility of `promptbookCli`
45015
45657
  */
45016
- async function $ensurePromptbookCliInstallations() {
45658
+ async function $ensurePromptbookCliInstallations(questionsOptions) {
45659
+ if (!questionsOptions.isAskingQuestionsEnabled) {
45660
+ // Note: `--no-questions` forbids asking for the approval, so the registry checks would be useless
45661
+ return false;
45662
+ }
45017
45663
  if (!process.stdin.isTTY) {
45018
45664
  // Note: Non-interactive runs cannot approve an update and should not wait for registry checks
45019
45665
  return false;
@@ -45025,7 +45671,7 @@
45025
45671
  return false;
45026
45672
  }
45027
45673
  console.warn(colors__default["default"].yellow(formatPromptbookCliInstallationWarning(outdatedStatuses)));
45028
- const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?');
45674
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?', questionsOptions);
45029
45675
  if (!isUpdateApproved) {
45030
45676
  reportManualPromptbookCliUpdateCommands(outdatedStatuses);
45031
45677
  return false;
@@ -45092,6 +45738,17 @@
45092
45738
  return TEST_BEFORE_MODE_VALUES.includes(value);
45093
45739
  }
45094
45740
 
45741
+ /**
45742
+ * Prints why a coder run has ended to the terminal.
45743
+ *
45744
+ * @private internal utility of `coder run` and `coder server` commands
45745
+ */
45746
+ function printCoderRunFailure(error) {
45747
+ console.error(colors__default["default"].bgRed(`${error.name}`));
45748
+ console.error(colors__default["default"].red(formatUnknownErrorDetails(error)));
45749
+ }
45750
+ // Note: [🟡] Code for coder failure reporting [printCoderRunFailure](src/cli/cli-commands/coder/printCoderRunFailure.ts) should never be published outside of `@promptbook/cli`
45751
+
45095
45752
  /**
45096
45753
  * Initializes `coder run` command for Promptbook CLI utilities
45097
45754
  *
@@ -45101,10 +45758,12 @@
45101
45758
  */
45102
45759
  function $initializeCoderRunCommand(program) {
45103
45760
  const command = program.command('run');
45104
- command.description(_spaceTrim.spaceTrim(`
45761
+ command.description(_spaceTrim.spaceTrim((block) => `
45105
45762
  Execute coding prompts through selected AI agent
45106
45763
 
45107
- ${PROMPT_RUNNER_DESCRIPTION}
45764
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45765
+
45766
+ ${block(QUESTIONS_DESCRIPTION)}
45108
45767
 
45109
45768
  Features:
45110
45769
  - Automatically stages and commits changes with agent identity unless --no-commit is used
@@ -45114,9 +45773,11 @@
45114
45773
  - Optional post-commit git push with explicit --auto-push opt-in
45115
45774
  - Optional pre-prompt git pull with explicit --auto-pull opt-in
45116
45775
  - Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
45776
+ - Saves the run trace of every round into prompts/traces/, named after its prompt file
45117
45777
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
45118
45778
  - Optional --no-ui keeps plain streaming console output for logging and debugging
45119
- - Checks that the selected harness is installed and up to date before the first prompt unless --no-harness-update is used
45779
+ - Refuses to start on a nearly full disk and pauses the run when the free disk space becomes critical, unless --no-questions is used
45780
+ - Checks that the selected harness is installed and up to date before the first prompt unless --no-questions is used
45120
45781
  - Offers to add missing project-local ignore rules for the selected harness
45121
45782
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
45122
45783
  - Supports GPG signing of commits
@@ -45127,8 +45788,8 @@
45127
45788
  `));
45128
45789
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45129
45790
  addPromptRunnerSelectionOptions(command);
45130
- addHarnessUpdateOption(command);
45131
- command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
45791
+ addQuestionsOption(command);
45792
+ addCoderAgentOption(command);
45132
45793
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45133
45794
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45134
45795
  command.addOption(new commander.Option('--test-before <mode>', `Run tests before coding: ${TEST_BEFORE_MODE_VALUES.join(', ')} (defaults to no; uses npm test when --test is omitted)`)
@@ -45141,9 +45802,7 @@
45141
45802
  A verified task is merged back into the branch the coder runs on and the worktree is deleted.
45142
45803
  A task that cannot be merged is marked as failed and its worktree is kept for a manual merge.
45143
45804
  `), false);
45144
- command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
45145
- command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
45146
- command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
45805
+ addPromptPriorityOptions(command);
45147
45806
  command.option('--limit <run-count>', 'Stop after processing this many prompt runs', createPositiveIntegerOptionParser('--limit'));
45148
45807
  command.option('--wait-after-prompt <duration>', _spaceTrim.spaceTrim(`
45149
45808
  Wait this long after each prompt has been implemented, verified and committed before starting the next prompt.
@@ -45169,7 +45828,7 @@
45169
45828
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45170
45829
  isAgentRequired: !dryRun,
45171
45830
  });
45172
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45831
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45173
45832
  // [1] Parse the wait options and --no-auto:
45174
45833
  // default: run automatically through the queue (no waiting between prompts)
45175
45834
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -45177,11 +45836,15 @@
45177
45836
  // --wait-between-prompts: pace from start of one prompt to start of next
45178
45837
  // --wait-after-error: wait before retrying after an error (default 10m)
45179
45838
  const waitForUser = !auto;
45180
- if (await $ensurePromptbookCliInstallations()) {
45839
+ assertUserConfirmationIsAllowed({ ...questionsOptions, isWaitingForUser: waitForUser });
45840
+ // Note: Checked before anything is installed or written, because installing a harness, updating the
45841
+ // Promptbook CLI and coding itself all need the disk space which is missing
45842
+ await $assertSufficientFreeDiskSpace(process.cwd());
45843
+ if (await $ensurePromptbookCliInstallations(questionsOptions)) {
45181
45844
  return process.exit(0);
45182
45845
  }
45183
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45184
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45846
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45847
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
45185
45848
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45186
45849
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45187
45850
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45214,6 +45877,7 @@
45214
45877
  allowDestructiveAutoMigrate,
45215
45878
  autoPush: runnerOptions.autoPush,
45216
45879
  autoPull: runnerOptions.autoPull,
45880
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45217
45881
  };
45218
45882
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
45219
45883
  const { runCodexPrompts } = await Promise.resolve().then(function () { return runCodexPrompts$1; });
@@ -45223,8 +45887,7 @@
45223
45887
  }
45224
45888
  catch (error) {
45225
45889
  assertsError(error);
45226
- console.error(colors__default["default"].bgRed(`${error.name}`));
45227
- console.error(colors__default["default"].red(error.stack || error.message));
45890
+ printCoderRunFailure(error);
45228
45891
  return process.exit(1);
45229
45892
  }
45230
45893
  return process.exit(0);
@@ -45268,35 +45931,36 @@
45268
45931
  */
45269
45932
  function $initializeCoderServerCommand(program) {
45270
45933
  const command = program.command('server');
45271
- command.description(_spaceTrim.spaceTrim(`
45272
- Start a coder server that watches for prompts and serves a kanban web UI
45934
+ command.description(_spaceTrim.spaceTrim((block) => `
45935
+ Start a coder server that watches for prompts and serves a kanban web UI
45273
45936
 
45274
- ${PROMPT_RUNNER_DESCRIPTION}
45937
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45275
45938
 
45276
- Features:
45277
- - Runs the same prompt processing as \`ptbk coder run\`
45278
- - Checks that the selected harness is installed and up to date on startup unless --no-harness-update is used
45279
- - Offers to add missing project-local ignore rules for the selected harness
45280
- - Does not exit when all prompts are done; polls for new prompt files instead
45281
- - Serves a kanban board at http://localhost:<port> for visual progress tracking
45282
- - Allows editing prompt files directly from the browser (Trello-style)
45283
- - Play / pause button in the browser stays in sync with the CLI pause state
45284
- - Terminal controls match \`ptbk coder run\`: P pause/resume, S skip waiting, X end after current prompt
45285
- `));
45939
+ ${block(QUESTIONS_DESCRIPTION)}
45940
+
45941
+ Features:
45942
+ - Runs the same prompt processing as \`ptbk coder run\`
45943
+ - Refuses to start on a nearly full disk and pauses the run when the free disk space becomes critical, unless --no-questions is used
45944
+ - Checks that the selected harness is installed and up to date on startup unless --no-questions is used
45945
+ - Offers to add missing project-local ignore rules for the selected harness
45946
+ - Does not exit when all prompts are done; polls for new prompt files instead
45947
+ - Serves a kanban board at http://localhost:<port> for visual progress tracking
45948
+ - Allows editing prompt files directly from the browser (Trello-style)
45949
+ - Play / pause button in the browser stays in sync with the CLI pause state
45950
+ - Terminal controls match \`ptbk coder run\`: P pause/resume, S skip waiting, X end after current prompt
45951
+ `));
45286
45952
  command.addOption(new commander.Option('--port <port>', 'Port to start the coder server on')
45287
45953
  .env('PTBK_CODER_SERVER_PORT')
45288
45954
  .default(DEFAULT_CODER_SERVER_PORT));
45289
45955
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45290
45956
  addPromptRunnerSelectionOptions(command);
45291
- addHarnessUpdateOption(command);
45292
- command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
45957
+ addQuestionsOption(command);
45958
+ addCoderAgentOption(command);
45293
45959
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45294
45960
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45295
45961
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
45296
45962
  addPromptRunnerExecutionOptions(command);
45297
- command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
45298
- command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
45299
- command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
45963
+ addPromptPriorityOptions(command);
45300
45964
  command.option('--wait-after-prompt <duration>', _spaceTrim.spaceTrim(`
45301
45965
  Wait this long after each prompt has been implemented, verified and committed before starting the next prompt.
45302
45966
  Accepts durations like 1h, 30m, 5s. Defaults to 0 (no wait).
@@ -45320,11 +45984,15 @@
45320
45984
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45321
45985
  isAgentRequired: !dryRun,
45322
45986
  });
45323
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45324
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45325
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45987
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45326
45988
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
45327
45989
  const waitForUser = !auto;
45990
+ assertUserConfirmationIsAllowed({ ...questionsOptions, isWaitingForUser: waitForUser });
45991
+ // Note: Checked before anything is installed or written, because installing a harness and coding
45992
+ // itself both need the disk space which is missing
45993
+ await $assertSufficientFreeDiskSpace(process.cwd());
45994
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45995
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
45328
45996
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45329
45997
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45330
45998
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45354,6 +46022,7 @@
45354
46022
  allowDestructiveAutoMigrate,
45355
46023
  autoPush: runnerOptions.autoPush,
45356
46024
  autoPull: runnerOptions.autoPull,
46025
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45357
46026
  };
45358
46027
  // Note: Import dynamically to avoid loading heavy dependencies until needed
45359
46028
  const { runCodexPromptsServer } = await Promise.resolve().then(function () { return runCodexPromptsServer$1; });
@@ -45362,8 +46031,7 @@
45362
46031
  }
45363
46032
  catch (error) {
45364
46033
  assertsError(error);
45365
- console.error(colors__default["default"].bgRed(`${error.name}`));
45366
- console.error(colors__default["default"].red(error.stack || error.message));
46034
+ printCoderRunFailure(error);
45367
46035
  return process.exit(1);
45368
46036
  }
45369
46037
  return process.exit(0);
@@ -45511,6 +46179,7 @@
45511
46179
  * - add: Add one ready-to-run prompt file to the queue
45512
46180
  * - generate-boilerplates: Generate prompt boilerplate files
45513
46181
  * - find-refactor-candidates: Find files that need refactoring
46182
+ * - list: List ready prompts in priority order without running them
45514
46183
  * - run: Run coding prompts with AI agents
45515
46184
  * - ping: Test one harness and model with a tiny dummy prompt
45516
46185
  * - verify: Verify completed prompts
@@ -45531,6 +46200,7 @@
45531
46200
  - generate-boilerplates: Generate prompt boilerplate files
45532
46201
  - find-refactor-candidates: Find files that need refactoring
45533
46202
  - find-unwritten: List prompt sections that still need to be authored
46203
+ - list: List ready prompts in priority order without running them
45534
46204
  - run: Run coding prompts with AI agents
45535
46205
  - ping: Test the connection, response time and quota of one harness and model
45536
46206
  - server: Start a long-running coder server with a kanban web UI
@@ -45543,17 +46213,14 @@
45543
46213
  $initializeCoderGenerateBoilerplatesCommand(coderCommand);
45544
46214
  $initializeCoderFindRefactorCandidatesCommand(coderCommand);
45545
46215
  $initializeCoderFindUnwrittenCommand(coderCommand);
46216
+ $initializeCoderListCommand(coderCommand);
45546
46217
  $initializeCoderRunCommand(coderCommand);
45547
46218
  $initializeCoderPingCommand(coderCommand);
45548
46219
  $initializeCoderServerCommand(coderCommand);
45549
46220
  $initializeCoderVerifyCommand(coderCommand);
45550
46221
  $initializeCoderFindFreshEmojiTagCommand(coderCommand);
45551
46222
  // If no subcommand is provided, show help
45552
- coderCommand.action(() => {
45553
- console.info(colors__default["default"].yellow('Please specify a subcommand.'));
45554
- console.info('');
45555
- coderCommand.help();
45556
- });
46223
+ $requireCliSubcommand(coderCommand);
45557
46224
  }
45558
46225
  // Note: [🟡] Code for CLI command [coder](src/cli/cli-commands/coder.ts) should never be published outside of `@promptbook/cli`
45559
46226
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -56143,7 +56810,9 @@
56143
56810
  * @private internal function of `promptbookCli`
56144
56811
  */
56145
56812
  function $initializeRunCommand(program) {
56146
- const runCommand = program.command('run', { isDefault: true });
56813
+ // Note: `run` is deprecated, so it is intentionally NOT the default command of `ptbk`
56814
+ // and it must be asked for explicitly
56815
+ const runCommand = program.command('run');
56147
56816
  configureRunCommand(runCommand);
56148
56817
  runCommand.action(handleActionErrors((pipelineSource, cliOptions) => runCommandAction(pipelineSource, cliOptions)));
56149
56818
  }
@@ -57982,9 +58651,24 @@
57982
58651
  command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
57983
58652
  }
57984
58653
 
58654
+ /**
58655
+ * Keeps one already registered CLI command out of the help of its parent command while the command itself stays
58656
+ * fully usable - it can still be run, it still has its own help and it is still reachable through `ptbk help <command>`.
58657
+ *
58658
+ * Note: `$` is used to indicate that this function is not a pure function - it changes how the CLI renders its help
58659
+ *
58660
+ * @private utility of CLI
58661
+ */
58662
+ function $hideCliCommandFromHelp(command) {
58663
+ command._hidden = true;
58664
+ }
58665
+
57985
58666
  /**
57986
58667
  * Marks one CLI command as deprecated while keeping it available for existing callers.
57987
58668
  *
58669
+ * Deprecated commands are not advertised anymore - they are hidden from the list of commands in the help of their
58670
+ * parent command, they warn when they are used and they explain the deprecation in their own help.
58671
+ *
57988
58672
  * @private utility of CLI
57989
58673
  */
57990
58674
  function $deprecateCliCommand(command, deprecationMessage) {
@@ -57996,6 +58680,7 @@
57996
58680
  command.hook('preAction', () => {
57997
58681
  console.warn(colors__default["default"].yellow(createDeprecatedCliCommandWarning(command, deprecationMessage)));
57998
58682
  });
58683
+ $hideCliCommandFromHelp(command);
57999
58684
  }
58000
58685
  /**
58001
58686
  * Creates the runtime warning printed before a deprecated CLI command action starts.
@@ -58063,10 +58748,17 @@
58063
58748
  program.version(PROMPTBOOK_ENGINE_VERSION);
58064
58749
  program.description(CLAIM);
58065
58750
  // Note: These options are valid for all commands
58751
+ // Note: Commands are listed in the help in the order they are registered,
58752
+ // `coder` is the most used one so it goes first and the deprecated ones,
58753
+ // which `$deprecateTopLevelCommands` hides from the help entirely, go last
58754
+ $initializeCoderCommand(program);
58755
+ $initializeAgentCommand(program);
58756
+ $initializeAgentFolderCommand(program);
58757
+ $initializeAgentsServerCommand(program);
58066
58758
  $initializeAboutCommand(program);
58759
+ $initializeHelloCommand(program);
58067
58760
  $initializeRunCommand(program);
58068
58761
  $initializeLoginCommand(program);
58069
- $initializeHelloCommand(program);
58070
58762
  $initializeMakeCommand(program);
58071
58763
  $initializePrettifyCommand(program);
58072
58764
  $initializeTestCommand(program);
@@ -58074,16 +58766,13 @@
58074
58766
  $initializeListScrapersCommand(program);
58075
58767
  $initializeStartAgentsServerCommand(program);
58076
58768
  $initializeStartPipelinesServerCommand(program);
58077
- $initializeAgentCommand(program);
58078
- $initializeAgentFolderCommand(program);
58079
- $initializeAgentsServerCommand(program);
58080
- $initializeCoderCommand(program);
58081
58769
  $deprecateTopLevelCommands(program);
58082
58770
  // TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
58083
58771
  program.commands.forEach($addGlobalOptionsToCommand);
58772
+ // Note: There is no default command, so `ptbk` without a subcommand asks for one
58773
+ // instead of running the deprecated `ptbk run`
58084
58774
  if (commandLineArguments.length === 0) {
58085
- program.outputHelp();
58086
- return process.exit(0);
58775
+ return $reportMissingCliSubcommand(program);
58087
58776
  }
58088
58777
  program.parse(process.argv);
58089
58778
  }
@@ -58097,7 +58786,7 @@
58097
58786
  return firstCommandLineArgument !== undefined && VERSION_OPTION_ARGUMENTS.has(firstCommandLineArgument);
58098
58787
  }
58099
58788
  /**
58100
- * Adds one deprecation notice to each configured top-level legacy command.
58789
+ * Marks each configured top-level legacy command as deprecated, which keeps it usable but takes it out of the help.
58101
58790
  */
58102
58791
  function $deprecateTopLevelCommands(program) {
58103
58792
  for (const command of program.commands) {
@@ -58108,7 +58797,6 @@
58108
58797
  }
58109
58798
  }
58110
58799
  // Note: [🟡] Code for CLI program [promptbookCli](src/cli/promptbookCli.ts) should never be published outside of `@promptbook/cli`
58111
- // TODO: [🧠] Maybe `run` command the default, instead of `ptbk run ./foo.book` -> `ptbk ./foo.book`
58112
58800
  // TODO: [🥠] Do not export, its just for CLI script
58113
58801
  // TODO: [🕌] When more functionalities, rename
58114
58802
  // Note: 11:11
@@ -59220,7 +59908,9 @@
59220
59908
  */
59221
59909
  function createExecutionToolsFromVercelProvider(options) {
59222
59910
  let { title, description } = options;
59223
- const { vercelProvider, availableModels, userId, additionalChatSettings = {} } = options;
59911
+ const { vercelProvider, availableModels, additionalChatSettings = {} } = options;
59912
+ // <- Note: `userId` is not forwarded, because Vercel AI SDK moved provider-specific settings like `user` from
59913
+ // `vercelProvider.chat(modelName, settings)` into `providerOptions` which are keyed by the provider
59224
59914
  if (!/Vercel/i.test(title)) {
59225
59915
  title = `${title} (through Vercel)`;
59226
59916
  // <- TODO: [🧈] Maybe standartize the suffix
@@ -59261,10 +59951,7 @@
59261
59951
 
59262
59952
  `));
59263
59953
  }
59264
- const model = await vercelProvider.chat(modelName, {
59265
- user: (userId === null || userId === void 0 ? void 0 : userId.toString()) || undefined,
59266
- ...additionalChatSettings,
59267
- });
59954
+ const model = await vercelProvider.chat(modelName);
59268
59955
  const rawPromptContent = templateParameters(content, { ...parameters, modelName });
59269
59956
  // Support for passing a chat thread (multi-message conversation)
59270
59957
  let promptMessages;
@@ -59302,14 +59989,9 @@
59302
59989
  }
59303
59990
  const rawRequest = {
59304
59991
  // <- TODO: [☂]
59305
- inputFormat: 'messages',
59306
- mode: {
59307
- type: 'regular',
59308
- tools: [
59309
- /* <- TODO: Pass the tools */
59310
- ],
59311
- },
59312
59992
  prompt: promptMessages,
59993
+ // <- TODO: Pass the tools via `tools`
59994
+ ...additionalChatSettings,
59313
59995
  };
59314
59996
  const start = $getCurrentDate();
59315
59997
  if (options.isVerbose) {
@@ -59326,28 +60008,31 @@
59326
60008
  if (options.isVerbose) {
59327
60009
  console.info(colors__default["default"].bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
59328
60010
  }
59329
- if (rawResponse.text === undefined) {
60011
+ // Note: Vercel AI SDK returns ordered content parts, the chat message is made only of the text parts
60012
+ const responseTextParts = rawResponse.content.flatMap((contentPart) => contentPart.type === 'text' ? [contentPart.text] : []);
60013
+ if (responseTextParts.length === 0) {
59330
60014
  throw new PipelineExecutionError('No response message');
59331
60015
  }
60016
+ const responseText = responseTextParts.join('');
59332
60017
  const complete = $getCurrentDate();
59333
60018
  const duration = uncertainNumber((new Date(complete).getTime() - new Date(start).getTime()) / 1000);
59334
60019
  const usage = {
59335
60020
  price: UNCERTAIN_ZERO_VALUE,
59336
60021
  duration,
59337
60022
  input: {
59338
- tokensCount: uncertainNumber(rawResponse.usage.promptTokens),
60023
+ tokensCount: uncertainNumber(rawResponse.usage.inputTokens),
59339
60024
  ...computeUsageCounts(rawPromptContent),
59340
60025
  },
59341
60026
  output: {
59342
- tokensCount: uncertainNumber(rawResponse.usage.completionTokens),
59343
- ...computeUsageCounts(rawResponse.text),
60027
+ tokensCount: uncertainNumber(rawResponse.usage.outputTokens),
60028
+ ...computeUsageCounts(responseText),
59344
60029
  },
59345
60030
  };
59346
60031
  return exportJson({
59347
60032
  name: 'promptResult',
59348
60033
  message: `Result of \`createExecutionToolsFromVercelProvider.callChatModel\``,
59349
60034
  value: {
59350
- content: rawResponse.text,
60035
+ content: responseText,
59351
60036
  modelName,
59352
60037
  timing: {
59353
60038
  start,
@@ -74652,13 +75337,15 @@
74652
75337
  }
74653
75338
 
74654
75339
  /**
74655
- * Checks whether a prompt is unrestricted or matches the selected harness/model.
75340
+ * Checks whether a prompt is unrestricted or matches the selected harness, model or Book agent.
74656
75341
  *
74657
- * A prompt status line can contain one or more backtick-delimited model or harness
74658
- * names. Matching is intentionally based on normalized substrings so a token such as
74659
- * `gpt` selects any `gpt-*` model and `opus` selects a `claude-opus-*` model.
75342
+ * A prompt status line can contain one or more backtick-delimited model, harness or agent
75343
+ * references. Matching is intentionally based on normalized substrings so a token such as
75344
+ * `gpt` selects any `gpt-*` model, `developer` selects an agent Book named `developer.book`,
75345
+ * and `opus` selects a `claude-opus-*` model.
74660
75346
  */
74661
75347
  function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
75348
+ var _a;
74662
75349
  if (promptRunnerIdentity === undefined) {
74663
75350
  return true;
74664
75351
  }
@@ -74667,7 +75354,11 @@
74667
75354
  if (requiredRunnerTokens.length === 0) {
74668
75355
  return true;
74669
75356
  }
74670
- const normalizedRunnerNames = [promptRunnerIdentity.harnessName, promptRunnerIdentity.modelName]
75357
+ const normalizedRunnerNames = [
75358
+ promptRunnerIdentity.harnessName,
75359
+ promptRunnerIdentity.modelName,
75360
+ ...((_a = promptRunnerIdentity.agentReferences) !== null && _a !== void 0 ? _a : []),
75361
+ ]
74671
75362
  .filter((name) => name !== undefined && name.trim() !== '')
74672
75363
  .map((name) => normalizeToKebabCase(name))
74673
75364
  .filter((name) => name !== '');
@@ -74678,7 +75369,7 @@
74678
75369
  });
74679
75370
  }
74680
75371
  /**
74681
- * Extracts model and harness tokens from a prompt status line.
75372
+ * Extracts model, harness and Book-agent tokens from a prompt status line.
74682
75373
  */
74683
75374
  function extractPromptRunnerTokens(statusLine) {
74684
75375
  return Array.from(statusLine.matchAll(/`([^`]+)`/gu))
@@ -74693,7 +75384,7 @@
74693
75384
  *
74694
75385
  * @private internal constant of `parsePromptFile`
74695
75386
  */
74696
- const PROMPT_STATUS_MARKER_PATTERN = /^\[(?: |-|[xX]|\^|!)\]/u;
75387
+ const PROMPT_STATUS_LINE_PATTERN = /^\[(?<marker> |-|\.|[xX]|\^|!)\](?<details>.*)$/u;
74697
75388
  /**
74698
75389
  * Parses a prompt markdown file into sections and metadata.
74699
75390
  */
@@ -74717,7 +75408,7 @@
74717
75408
  if (firstNonEmptyLine !== undefined) {
74718
75409
  const statusLine = (lines[firstNonEmptyLine] || '').trim();
74719
75410
  const parsedStatus = parseStatusLine(statusLine);
74720
- const status = (_a = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.status) !== null && _a !== void 0 ? _a : (hasPromptStatusMarker(statusLine) ? 'not-ready' : 'todo');
75411
+ const status = (_a = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.status) !== null && _a !== void 0 ? _a : (PROMPT_STATUS_LINE_PATTERN.test(statusLine) ? 'not-ready' : 'todo');
74721
75412
  const priority = (_b = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.priority) !== null && _b !== void 0 ? _b : 0;
74722
75413
  sections.push({
74723
75414
  index,
@@ -74745,51 +75436,47 @@
74745
75436
  * For [x] done, [!] failed and [^] in-progress prompts, allow metadata after the status marker.
74746
75437
  */
74747
75438
  function parseStatusLine(line) {
74748
- var _a, _b, _c, _d;
74749
- // For done prompts [x], allow any content after (for cost/time metadata)
74750
- const doneMatch = line.match(/^\[(?<status>[xX])\]/);
74751
- if (doneMatch) {
75439
+ var _a, _b, _c, _d, _e;
75440
+ const statusLineMatch = line.match(PROMPT_STATUS_LINE_PATTERN);
75441
+ const marker = (_a = statusLineMatch === null || statusLineMatch === void 0 ? void 0 : statusLineMatch.groups) === null || _a === void 0 ? void 0 : _a.marker;
75442
+ const details = (_c = (_b = statusLineMatch === null || statusLineMatch === void 0 ? void 0 : statusLineMatch.groups) === null || _b === void 0 ? void 0 : _b.details) !== null && _c !== void 0 ? _c : '';
75443
+ if (marker === 'x' || marker === 'X') {
75444
+ // For done prompts [x], allow any content after (for cost/time metadata).
74752
75445
  return { status: 'done', priority: 0 };
74753
75446
  }
74754
- // For failed prompts [!], allow any content after (for failure metadata)
74755
- const failedMatch = line.match(/^\[(?<status>!)\]/);
74756
- if (failedMatch) {
75447
+ if (marker === '!') {
75448
+ // For failed prompts [!], allow any content after (for failure metadata).
74757
75449
  return { status: 'failed', priority: 0 };
74758
75450
  }
74759
- // For in-progress prompts [^], allow any content after (for the steps recorded so far)
74760
- const inProgressMatch = line.match(/^\[(?<status>\^)\]/);
74761
- if (inProgressMatch) {
75451
+ if (marker === '^') {
75452
+ // For in-progress prompts [^], allow any content after (for the steps recorded so far).
74762
75453
  return { status: 'in-progress', priority: 0 };
74763
75454
  }
74764
- // For not-ready [-], keep the historical clean-line syntax.
74765
- if (/^\[-\]\s*!*\s*$/u.test(line)) {
75455
+ // Treat [.] as the alternative spelling of not-ready [-], preserving the historical clean-line syntax.
75456
+ if ((marker === '-' || marker === '.') && /^[!\s]*$/u.test(details)) {
74766
75457
  return { status: 'not-ready', priority: 0 };
74767
75458
  }
74768
- // Todo [ ] may contain backtick-delimited model/harness tokens and priority markers
74769
- // before or after those tokens. Other trailing text remains an invalid status line.
74770
- const todoMatch = line.match(/^\[ \](?<details>.*)$/u);
74771
- if (!todoMatch) {
75459
+ if (marker !== ' ') {
74772
75460
  return undefined;
74773
75461
  }
74774
- const details = (_b = (_a = todoMatch.groups) === null || _a === void 0 ? void 0 : _a.details) !== null && _b !== void 0 ? _b : '';
75462
+ // Todo [ ] may contain backtick-delimited model/harness tokens and priority markers
75463
+ // before or after those tokens. Other trailing text remains an invalid status line.
74775
75464
  const isPriorityOnly = /^[!\s]*$/u.test(details);
74776
75465
  const hasPromptRunnerTokens = extractPromptRunnerTokens(line).length > 0;
74777
75466
  if (details.trim() !== '' && !isPriorityOnly && !hasPromptRunnerTokens) {
74778
75467
  return undefined;
74779
75468
  }
74780
- return { status: 'todo', priority: (_d = (_c = details.match(/!/gu)) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0 };
75469
+ return { status: 'todo', priority: (_e = (_d = details.match(/!/gu)) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0 };
74781
75470
  }
75471
+
74782
75472
  /**
74783
- * Checks whether a line starts with one of the supported prompt status markers.
75473
+ * HTML comment which excludes a Markdown file from all `ptbk coder` queues.
74784
75474
  *
74785
- * @private internal utility of `parsePromptFile`
75475
+ * @private internal constant of `loadPromptFiles`
74786
75476
  */
74787
- function hasPromptStatusMarker(line) {
74788
- return PROMPT_STATUS_MARKER_PATTERN.test(line);
74789
- }
74790
-
75477
+ const PTBK_CODER_IGNORE_MARKER = '<!--ptbk-coder-ignore-->';
74791
75478
  /**
74792
- * Loads and parses prompt files from the prompts directory.
75479
+ * Loads and parses prompt files from the prompts directory, excluding files marked for `ptbk coder` to ignore.
74793
75480
  */
74794
75481
  async function loadPromptFiles(promptsDir) {
74795
75482
  const entries = await promises.readdir(promptsDir, { withFileTypes: true });
@@ -74800,6 +75487,9 @@
74800
75487
  const promptFiles = [];
74801
75488
  for (const filePath of files) {
74802
75489
  const content = await promises.readFile(filePath, 'utf-8');
75490
+ if (content.includes(PTBK_CODER_IGNORE_MARKER)) {
75491
+ continue;
75492
+ }
74803
75493
  promptFiles.push(parsePromptFile(filePath, content));
74804
75494
  }
74805
75495
  return promptFiles;
@@ -74910,22 +75600,158 @@
74910
75600
  });
74911
75601
 
74912
75602
  /**
74913
- * Formats one unknown error-like value into its readable message without the stack trace.
75603
+ * Reads an optional agent `.book` file and prepares the references that route prompts to it.
74914
75604
  *
74915
- * Use this instead of `formatUnknownErrorDetails` whenever the text is shown to the user, for example
74916
- * inside a branded error, where the stack of the wrapped error would only bury the actual cause.
75605
+ * Returns `undefined` when no agent path is provided.
74917
75606
  */
74918
- function formatUnknownErrorMessage(error) {
74919
- if (error instanceof Error) {
74920
- return error.message;
75607
+ async function resolveCoderAgentBook(agentBookReference, currentWorkingDirectory) {
75608
+ const normalizedAgentBookReference = agentBookReference === null || agentBookReference === void 0 ? void 0 : agentBookReference.trim();
75609
+ if (!normalizedAgentBookReference) {
75610
+ return undefined;
74921
75611
  }
74922
- if (typeof error === 'string') {
74923
- return error;
75612
+ const resolvedAgentBookPath = path.resolve(currentWorkingDirectory, normalizedAgentBookReference);
75613
+ const agentSource = (await promises.readFile(resolvedAgentBookPath, 'utf-8').catch((error) => {
75614
+ if (error.code === 'ENOENT' || error.code === 'EISDIR') {
75615
+ throw new NotFoundError(spaceTrim(`
75616
+ Agent book \`${normalizedAgentBookReference}\` was not found or is not a file.
75617
+
75618
+ Pass a path to a \`.book\` file in \`--agent\`.
75619
+ `));
75620
+ }
75621
+ throw error;
75622
+ }));
75623
+ return {
75624
+ agentSource,
75625
+ agentReferences: createCoderAgentReferences({
75626
+ agentBookReference: normalizedAgentBookReference,
75627
+ resolvedAgentBookPath,
75628
+ currentWorkingDirectory,
75629
+ agentSource,
75630
+ }),
75631
+ };
75632
+ }
75633
+ /**
75634
+ * Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
75635
+ *
75636
+ * Returns `undefined` when no agent path is provided.
75637
+ */
75638
+ async function resolveCoderAgent(agentBookReference, currentWorkingDirectory) {
75639
+ const resolvedAgentBook = await resolveCoderAgentBook(agentBookReference, currentWorkingDirectory);
75640
+ if (resolvedAgentBook === undefined) {
75641
+ return undefined;
74924
75642
  }
74925
- const serializedError = JSON.stringify(error, null, 2);
74926
- return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
75643
+ return {
75644
+ ...resolvedAgentBook,
75645
+ systemMessage: await createAgentRunnerSystemMessage(resolvedAgentBook.agentSource),
75646
+ };
75647
+ }
75648
+ /**
75649
+ * Creates the path and Book-title aliases which a prompt can use to target one selected agent.
75650
+ *
75651
+ * @private internal utility of `resolveCoderAgentBook`
75652
+ */
75653
+ function createCoderAgentReferences(options) {
75654
+ const relativeAgentBookPath = path.relative(options.currentWorkingDirectory, options.resolvedAgentBookPath).replaceAll('\\', '/');
75655
+ const agentBookFileName = path.basename(options.resolvedAgentBookPath);
75656
+ const agentBookName = path.basename(options.resolvedAgentBookPath, path.extname(options.resolvedAgentBookPath));
75657
+ const agentNameFromBook = parseAgentSource(options.agentSource).agentName;
75658
+ return Array.from(new Set([
75659
+ options.agentBookReference,
75660
+ relativeAgentBookPath,
75661
+ agentBookFileName,
75662
+ agentBookName,
75663
+ agentNameFromBook,
75664
+ ].filter((agentReference) => agentReference.trim() !== '')));
75665
+ }
75666
+
75667
+ var resolveCoderAgent$1 = /*#__PURE__*/Object.freeze({
75668
+ __proto__: null,
75669
+ resolveCoderAgentBook: resolveCoderAgentBook,
75670
+ resolveCoderAgent: resolveCoderAgent
75671
+ });
75672
+
75673
+ /**
75674
+ * Lists todo prompts that are ready to run (no authoring placeholders).
75675
+ */
75676
+ function listRunnablePrompts(files, priorityFilter = {}, promptRunnerIdentity) {
75677
+ return listTodoPrompts(files).filter((prompt) => !isPromptToBeWritten(prompt.file, prompt.section) &&
75678
+ isPromptInPriorityFilter(prompt.section, priorityFilter) &&
75679
+ isPromptCompatibleWithRunner(prompt.file, prompt.section, promptRunnerIdentity));
75680
+ }
75681
+
75682
+ /**
75683
+ * Lists upcoming tasks that are ready to run (no authoring placeholders).
75684
+ */
75685
+ function listUpcomingTasks(files, priorityFilter = {}, promptRunnerIdentity) {
75686
+ return listRunnablePrompts(files, priorityFilter, promptRunnerIdentity).map(({ file, section }) => ({
75687
+ label: buildPromptLabelForDisplay(file, section),
75688
+ summary: buildPromptSummary(file, section),
75689
+ priority: section.priority,
75690
+ }));
75691
+ }
75692
+
75693
+ /**
75694
+ * Groups upcoming tasks by priority, high to low.
75695
+ */
75696
+ function groupUpcomingTasksByPriority(tasks) {
75697
+ const grouped = new Map();
75698
+ for (const task of tasks) {
75699
+ const group = grouped.get(task.priority);
75700
+ if (group) {
75701
+ group.push(task);
75702
+ }
75703
+ else {
75704
+ grouped.set(task.priority, [task]);
75705
+ }
75706
+ }
75707
+ return Array.from(grouped.entries())
75708
+ .sort((a, b) => b[0] - a[0])
75709
+ .map(([priority, groupedTasks]) => ({ priority, tasks: groupedTasks }));
75710
+ }
75711
+
75712
+ /**
75713
+ * Prints upcoming tasks grouped by priority.
75714
+ */
75715
+ function printUpcomingTasks(tasks) {
75716
+ if (tasks.length === 0) {
75717
+ console.info(colors__default["default"].green('No upcoming tasks.'));
75718
+ return;
75719
+ }
75720
+ console.info(colors__default["default"].cyan('Upcoming tasks (grouped by priority):'));
75721
+ for (const group of groupUpcomingTasksByPriority(tasks)) {
75722
+ console.info(colors__default["default"].cyan(`Priority ${group.priority}:`));
75723
+ for (const [index, task] of group.tasks.entries()) {
75724
+ const summary = task.summary ? ` - ${task.summary}` : '';
75725
+ console.info(` ${index + 1}. ${task.label}${summary}`);
75726
+ }
75727
+ }
75728
+ }
75729
+
75730
+ /**
75731
+ * Directory containing the prompt queue of the current project.
75732
+ *
75733
+ * @private internal constant of `listCoderPrompts`
75734
+ */
75735
+ const PROMPTS_DIRECTORY_PATH = path.join(process.cwd(), 'prompts');
75736
+ /**
75737
+ * Lists ready, fully authored coding prompts in descending priority groups without starting a coding harness.
75738
+ *
75739
+ * @returns The number of prompts printed.
75740
+ * @public exported from `@promptbook/cli`
75741
+ */
75742
+ async function listCoderPrompts(options = {}) {
75743
+ const priorityFilter = normalizePriorityFilter(options);
75744
+ const promptFiles = await loadPromptFiles(PROMPTS_DIRECTORY_PATH);
75745
+ const upcomingTasks = listUpcomingTasks(promptFiles, priorityFilter, options.promptRunnerIdentity);
75746
+ printUpcomingTasks(upcomingTasks);
75747
+ return upcomingTasks.length;
74927
75748
  }
74928
75749
 
75750
+ var listCoderPrompts$1 = /*#__PURE__*/Object.freeze({
75751
+ __proto__: null,
75752
+ listCoderPrompts: listCoderPrompts
75753
+ });
75754
+
74929
75755
  /**
74930
75756
  * Builds a normalized temporary shell script path for prompt runners.
74931
75757
  */
@@ -75224,7 +76050,7 @@
75224
76050
  /**
75225
76051
  * CLI usage text for this script.
75226
76052
  */
75227
- const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--context <context-or-file>] [--test <test-command...>] [--test-before <no|yes-and-fail|yes-and-fix>] [--preserve-logs] [--isolate] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--min-priority <minimum-priority>] [--max-priority <maximum-priority>] [--limit <run-count>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--git-changes <fail|ignore|continue>] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
76053
+ const USAGE = 'Usage: run-codex-prompts [--dry-run] [--harness <harness-name>] [--model <model>] [--agent <agent-book-path>] [--context <context-or-file>] [--test <test-command...>] [--test-before <no|yes-and-fail|yes-and-fix>] [--preserve-logs] [--isolate] [--no-ui] [--thinking-level <thinking-level>] [--priority <minimum-priority>] [--min-priority <minimum-priority>] [--max-priority <maximum-priority>] [--limit <run-count>] [--allow-credits] [--auto-migrate] [--allow-destructive-auto-migrate] [--wait-after-prompt <duration>] [--wait-between-prompts <duration>] [--wait-after-error <duration>] [--no-auto] [--no-commit] [--git-changes <fail|ignore|continue>] [--no-normalize-line-endings] [--auto-push] [--auto-pull]';
75228
76054
  /**
75229
76055
  * Top-level flags supported by this command.
75230
76056
  */
@@ -75232,6 +76058,7 @@
75232
76058
  '--dry-run',
75233
76059
  '--harness',
75234
76060
  '--model',
76061
+ '--agent',
75235
76062
  '--context',
75236
76063
  '--test',
75237
76064
  '--test-before',
@@ -75270,6 +76097,7 @@
75270
76097
  }
75271
76098
  }
75272
76099
  const model = readOptionValue(args, '--model');
76100
+ const agent = readOptionValue(args, '--agent');
75273
76101
  const context = readOptionValue(args, '--context');
75274
76102
  const hasTestCommandFlag = args.includes('--test');
75275
76103
  const testCommand = readVariadicOptionValue(args, '--test');
@@ -75346,6 +76174,7 @@
75346
76174
  noUi,
75347
76175
  agentName,
75348
76176
  model,
76177
+ agent,
75349
76178
  context,
75350
76179
  testCommand,
75351
76180
  testBefore,
@@ -75780,30 +76609,43 @@
75780
76609
  // Note: [🟡] Code in this file is only used from `@promptbook/cli` orchestration scripts and is not shipped to other consumers.
75781
76610
 
75782
76611
  /**
75783
- * Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
76612
+ * Creates the guard which watches the free disk space while one `ptbk coder` run is in progress.
75784
76613
  *
75785
- * Returns `undefined` when no agent path is provided.
76614
+ * The guard runs at every pause checkpoint of the run. When the disk became critically full it reports the
76615
+ * warning and requests a pause, so the run stops at that checkpoint instead of failing in the middle of a
76616
+ * coding round, and continues once the user has freed some space and pressed `P`.
76617
+ *
76618
+ * @private internal utility of `ptbk coder` free disk space handling
75786
76619
  */
75787
- async function resolveCoderAgent(agentPath, currentWorkingDirectory) {
75788
- if (!agentPath) {
75789
- return undefined;
75790
- }
75791
- const resolvedAgentPath = path.resolve(currentWorkingDirectory, agentPath);
75792
- const agentSource = await promises.readFile(resolvedAgentPath, 'utf-8').catch((error) => {
75793
- if (error.code === 'ENOENT' || error.code === 'EISDIR') {
75794
- throw new NotFoundError(spaceTrim(`
75795
- Agent book \`${agentPath}\` was not found or is not a file.
76620
+ function createFreeDiskSpaceGuard(options) {
76621
+ const { inspectedPath, isAskingQuestionsEnabled } = options;
76622
+ let lastMeasurementTimeMs;
76623
+ return async () => {
76624
+ if (lastMeasurementTimeMs !== undefined &&
76625
+ Date.now() - lastMeasurementTimeMs < FREE_DISK_SPACE_RECHECK_INTERVAL_MS) {
76626
+ return;
76627
+ }
76628
+ lastMeasurementTimeMs = Date.now();
76629
+ const freeDiskSpaceStatus = await $readFreeDiskSpaceStatus(inspectedPath);
76630
+ if (freeDiskSpaceStatus === null || freeDiskSpaceStatus.level !== 'critical') {
76631
+ return;
76632
+ }
76633
+ console.warn(colors__default["default"].yellow(_spaceTrim.spaceTrim((block) => `
76634
+ The disk is running out of space while \`ptbk coder\` is working.
75796
76635
 
75797
- Pass a path to a \`.book\` file in \`--agent\`.
75798
- `));
76636
+ ${block(formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus))}
76637
+ - Press \`P\` to continue once the disk space is freed, or start the run with \`--no-questions\` to never pause on a full disk.
76638
+ `)));
76639
+ if (!isAskingQuestionsEnabled) {
76640
+ // Note: `--no-questions` forbids stopping and waiting for somebody who would free the disk space,
76641
+ // so the run keeps going and the warning above is the only thing which is reported
76642
+ return;
75799
76643
  }
75800
- throw error;
75801
- });
75802
- return {
75803
- agentSource: agentSource,
75804
- systemMessage: await createAgentRunnerSystemMessage(agentSource),
76644
+ announcePauseTargetLabel(`continuing with only ${formatFreeDiskSpaceBytes(freeDiskSpaceStatus.availableBytes)} of free disk space left`);
76645
+ requestPause();
75805
76646
  };
75806
76647
  }
76648
+ // Note: [🟡] Code for `ptbk coder` free disk space handling [createFreeDiskSpaceGuard](scripts/run-codex-prompts/common/createFreeDiskSpaceGuard.ts) should never be published outside of `@promptbook/cli`
75807
76649
 
75808
76650
  /**
75809
76651
  * Refresh interval used by the countdown display while waiting.
@@ -77774,6 +78616,169 @@
77774
78616
  await promises.writeFile(file.path, content, 'utf-8');
77775
78617
  }
77776
78618
 
78619
+ /**
78620
+ * Creates a safe markdown fenced code block even when content contains backticks.
78621
+ *
78622
+ * @param content - Raw code content.
78623
+ * @param language - Optional info-string language label.
78624
+ * @returns Fenced code block.
78625
+ *
78626
+ * @private internal utility of `createStandaloneBookLanguageMarkdown` and of the `ptbk coder` run traces
78627
+ */
78628
+ function getSafeCodeBlock(content, language = 'markdown') {
78629
+ const maxBacktickCount = Math.max(0, ...(content.match(/`+/g) || []).map((match) => match.length));
78630
+ const fence = '`'.repeat(Math.max(3, maxBacktickCount + 1));
78631
+ return `${fence}${language}\n${content}\n${fence}`;
78632
+ }
78633
+
78634
+ /**
78635
+ * Renders the markdown run trace of one finished or failed prompt round.
78636
+ *
78637
+ * The trace pairs the metadata of the round - which harness, model and thinking level ran it, how long each of
78638
+ * its steps took and what it cost - with the untouched runtime log, so a finished round can still be analyzed
78639
+ * after its temporary artifacts are cleaned up.
78640
+ */
78641
+ function buildPromptRunTraceContent(options) {
78642
+ const sections = [
78643
+ buildPromptRunTraceSummarySection(options),
78644
+ buildPromptRunTraceFailureSection(options.outcome),
78645
+ buildPromptRunTraceRuntimeLogSection(options.runtimeLog),
78646
+ ].filter((section) => section !== undefined);
78647
+ return `${sections.join('\n\n')}\n`;
78648
+ }
78649
+ /**
78650
+ * Renders the heading and the metadata bullet list of one run trace.
78651
+ */
78652
+ function buildPromptRunTraceSummarySection(options) {
78653
+ const { file, section, outcome } = options;
78654
+ const runnerSignature = formatRunnerSignature(options.runnerName, options.modelName, options.thinkingLevel);
78655
+ const loginMethodLabel = outcome.kind === 'succeeded' ? formatCodexLoginMethod(outcome.loginMethod) : undefined;
78656
+ const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
78657
+ const detailLines = [
78658
+ `- **Prompt:** ${buildPromptSummary(file, section)}`,
78659
+ ...buildPromptRunTraceSectionLines(file, section),
78660
+ `- **Outcome:** ${formatPromptRunTraceOutcomeLabel(outcome)}`,
78661
+ `- **Runner:** ${runnerSignature}${loginMethodSuffix}`,
78662
+ `- **Attempts:** ${options.attemptCount}`,
78663
+ ...buildPromptRunTraceStepsLines(outcome),
78664
+ ...buildPromptRunTraceTestCommandLines(options.testCommand),
78665
+ `- **Started:** ${options.startedDate.toISOString()}`,
78666
+ `- **Finished:** ${options.finishedDate.toISOString()}`,
78667
+ `- **Duration:** ${moment__default["default"].duration(options.finishedDate.diff(options.startedDate)).humanize()}`,
78668
+ ];
78669
+ return _spaceTrim.spaceTrim((block) => `
78670
+ # Run trace of \`${buildPromptLabelForDisplay(file, section)}\`
78671
+
78672
+ ${block(detailLines.join('\n'))}
78673
+ `);
78674
+ }
78675
+ /**
78676
+ * Renders which section of its prompt file the round implemented.
78677
+ *
78678
+ * A prompt file holding exactly one section says nothing by naming it, exactly like the section suffix of its
78679
+ * generated artifact names, so only a file with more than one section reports the section it traced.
78680
+ */
78681
+ function buildPromptRunTraceSectionLines(file, section) {
78682
+ if (file.sections.length <= 1) {
78683
+ return [];
78684
+ }
78685
+ return [`- **Prompt section:** ${section.index + 1} of ${file.sections.length}`];
78686
+ }
78687
+ /**
78688
+ * Renders the per-step usage breakdown of a successful round, omitted for a round which never finished one.
78689
+ */
78690
+ function buildPromptRunTraceStepsLines(outcome) {
78691
+ if (outcome.kind !== 'succeeded') {
78692
+ return [];
78693
+ }
78694
+ const stepsSummary = formatCoderRunSteps(outcome.steps);
78695
+ if (stepsSummary === '') {
78696
+ return [];
78697
+ }
78698
+ return [`- **Steps:** ${stepsSummary}`];
78699
+ }
78700
+ /**
78701
+ * Renders the verification command of the round, omitted when the round ran without one.
78702
+ */
78703
+ function buildPromptRunTraceTestCommandLines(testCommand) {
78704
+ if (!testCommand) {
78705
+ return [];
78706
+ }
78707
+ return [`- **Verification command:** \`${testCommand}\``];
78708
+ }
78709
+ /**
78710
+ * Renders the failure details of a failed round, omitted for a successful one.
78711
+ */
78712
+ function buildPromptRunTraceFailureSection(outcome) {
78713
+ if (outcome.kind !== 'failed') {
78714
+ return undefined;
78715
+ }
78716
+ return `## Failure\n\n${getSafeCodeBlock(formatUnknownErrorDetails(outcome.error), 'text')}`;
78717
+ }
78718
+ /**
78719
+ * Renders the untouched runtime log of the round, or says that the round produced none.
78720
+ */
78721
+ function buildPromptRunTraceRuntimeLogSection(runtimeLog) {
78722
+ if (runtimeLog.trim() === '') {
78723
+ return '## Runtime log\n\n_This round has produced no readable runtime log._';
78724
+ }
78725
+ return `## Runtime log\n\n${getSafeCodeBlock(runtimeLog.replace(/\r\n/gu, '\n').trimEnd(), 'text')}`;
78726
+ }
78727
+ /**
78728
+ * Formats the outcome of the round as the human-readable label of its trace.
78729
+ */
78730
+ function formatPromptRunTraceOutcomeLabel(outcome) {
78731
+ return outcome.kind === 'succeeded' ? 'Succeeded' : 'Failed';
78732
+ }
78733
+
78734
+ /**
78735
+ * Name of the directory which keeps the run traces inside the prompts directory.
78736
+ */
78737
+ const PROMPT_RUN_TRACES_DIRECTORY_NAME = 'traces';
78738
+ /**
78739
+ * Builds the path of the run trace which belongs to one prompt section.
78740
+ *
78741
+ * The trace is stored in the `traces` directory of the very same prompts directory the prompt itself lives in,
78742
+ * and it carries the name of its prompt file. A prompt file holding more than one section appends the section
78743
+ * suffix, exactly like the temporary scripts and the isolation worktrees of the same section do.
78744
+ */
78745
+ function buildPromptRunTracePath(file, section) {
78746
+ const promptFileBaseName = file.name.replace(/\.[^.]+$/u, '');
78747
+ const traceFileName = `${promptFileBaseName}${buildPromptSectionSuffix(file, section)}.md`;
78748
+ return path.join(path.dirname(file.path), PROMPT_RUN_TRACES_DIRECTORY_NAME, traceFileName);
78749
+ }
78750
+
78751
+ /**
78752
+ * Writes the run trace of one prompt round into the `traces` directory of the prompts directory.
78753
+ *
78754
+ * Returns the path of the written trace so callers can include it in a commit.
78755
+ */
78756
+ async function writePromptRunTrace(options) {
78757
+ const { logPath, ...contentOptions } = options;
78758
+ const tracePath = buildPromptRunTracePath(options.file, options.section);
78759
+ const runtimeLog = await readPromptRuntimeLog(logPath);
78760
+ await promises.mkdir(path.dirname(tracePath), { recursive: true });
78761
+ await promises.writeFile(tracePath, buildPromptRunTraceContent({ ...contentOptions, runtimeLog }), 'utf-8');
78762
+ return tracePath;
78763
+ }
78764
+ /**
78765
+ * Reads the live runtime log of one round.
78766
+ *
78767
+ * A round which never produced a readable log still gets its trace, because the metadata of the round is worth
78768
+ * keeping on its own - so a missing log is reported as empty instead of failing the round which has just passed.
78769
+ */
78770
+ async function readPromptRuntimeLog(logPath) {
78771
+ if (!logPath) {
78772
+ return '';
78773
+ }
78774
+ try {
78775
+ return await promises.readFile(logPath, 'utf-8');
78776
+ }
78777
+ catch (_a) {
78778
+ return '';
78779
+ }
78780
+ }
78781
+
77777
78782
  /**
77778
78783
  * Refreshes optional harness subscription usage in the shared coder-run UI state.
77779
78784
  *
@@ -77896,7 +78901,9 @@
77896
78901
  catch (error) {
77897
78902
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
77898
78903
  lastError = error;
77899
- if (errorRetryAttempt >= MAX_RETRY_ATTEMPTS_AFTER_ERROR) {
78904
+ // Note: A harness which is not logged in answers every retry the same way, so the user gets
78905
+ // the sign-in instructions right away instead of after every retry has waited its delay
78906
+ if (error instanceof AuthenticationError || errorRetryAttempt >= MAX_RETRY_ATTEMPTS_AFTER_ERROR) {
77900
78907
  break;
77901
78908
  }
77902
78909
  await waitAfterErrorBeforeRetry({
@@ -77918,6 +78925,7 @@
77918
78925
  attemptCount,
77919
78926
  error: lastError,
77920
78927
  options,
78928
+ logPath,
77921
78929
  roundCommitScope,
77922
78930
  uiHandle,
77923
78931
  waitForRequestedPause,
@@ -78034,6 +79042,17 @@
78034
79042
  // Note: The prompt status is always written into the original project, an isolated round transports
78035
79043
  // its own changes back through the merge instead
78036
79044
  await writePromptFile(nextPrompt.file);
79045
+ // Note: Written before the round is committed, so the trace of the round lands in the very same commit
79046
+ // as the prompt it describes, and before the live runtime log it is built from is deleted
79047
+ await recordPromptRoundTrace({
79048
+ options: runOptions,
79049
+ nextPrompt,
79050
+ runnerMetadata,
79051
+ promptExecutionStartedDate,
79052
+ attemptCount: result.attemptCount,
79053
+ logPath,
79054
+ outcome: { kind: 'succeeded', steps: result.steps, loginMethod: result.loginMethod },
79055
+ });
78037
79056
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78038
79057
  await recordPromptDurationInEstimateCache({
78039
79058
  options: runOptions,
@@ -78081,7 +79100,7 @@
78081
79100
  * Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
78082
79101
  */
78083
79102
  async function finalizeFailedPromptRound(options) {
78084
- const { nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
79103
+ const { nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, attemptCount, error, options: runOptions, logPath, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
78085
79104
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
78086
79105
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
78087
79106
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
@@ -78107,8 +79126,42 @@
78107
79126
  modelName: runnerMetadata.modelName,
78108
79127
  error,
78109
79128
  });
79129
+ // Note: A failed round is exactly the round whose trace is worth reading, so it is written before the live
79130
+ // runtime log it is built from is deleted
79131
+ await recordPromptRoundTrace({
79132
+ options: runOptions,
79133
+ nextPrompt,
79134
+ runnerMetadata,
79135
+ promptExecutionStartedDate,
79136
+ attemptCount,
79137
+ logPath,
79138
+ outcome: { kind: 'failed', error },
79139
+ });
78110
79140
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78111
79141
  }
79142
+ /**
79143
+ * Persists the run trace of one prompt round, so the round can still be analyzed after its temporary
79144
+ * artifacts are gone.
79145
+ *
79146
+ * This is the single place where both the successful and the failed round record what they did, which harness,
79147
+ * model and thinking level did it and everything that harness has written while doing it.
79148
+ */
79149
+ async function recordPromptRoundTrace(options) {
79150
+ const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, logPath, outcome, } = options;
79151
+ await writePromptRunTrace({
79152
+ file: nextPrompt.file,
79153
+ section: nextPrompt.section,
79154
+ runnerName: runnerMetadata.runnerName,
79155
+ modelName: runnerMetadata.modelName,
79156
+ thinkingLevel: runOptions.thinkingLevel,
79157
+ testCommand: runOptions.testCommand,
79158
+ attemptCount,
79159
+ startedDate: promptExecutionStartedDate,
79160
+ finishedDate: moment__default["default"](),
79161
+ outcome,
79162
+ logPath,
79163
+ });
79164
+ }
78112
79165
  /**
78113
79166
  * Waits for the optional user confirmation immediately before creating the commit.
78114
79167
  */
@@ -78757,7 +79810,13 @@
78757
79810
  await commitChanges(buildCoderIsolationMergeFailureCommitMessage(worktree), {
78758
79811
  autoPush: options.options.autoPush,
78759
79812
  projectPath: worktree.projectPath,
78760
- relevantPaths: [nextPrompt.file.path, errorLogPath].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
79813
+ // Note: The round itself has already succeeded, so it has left its run trace in the original project.
79814
+ // It belongs to this commit, which is the only one this task still gets.
79815
+ relevantPaths: [
79816
+ nextPrompt.file.path,
79817
+ errorLogPath,
79818
+ buildPromptRunTracePath(nextPrompt.file, nextPrompt.section),
79819
+ ].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
78761
79820
  });
78762
79821
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(mergeFailureError.message);
78763
79822
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Merging \`${worktree.taskName}\` failed, worktree kept for a manual merge`);
@@ -78781,15 +79840,6 @@
78781
79840
  return path.relative(projectPath, path$1).replace(/\\/gu, '/');
78782
79841
  }
78783
79842
 
78784
- /**
78785
- * Lists todo prompts that are ready to run (no authoring placeholders).
78786
- */
78787
- function listRunnablePrompts(files, priorityFilter = {}, promptRunnerIdentity) {
78788
- return listTodoPrompts(files).filter((prompt) => !isPromptToBeWritten(prompt.file, prompt.section) &&
78789
- isPromptInPriorityFilter(prompt.section, priorityFilter) &&
78790
- isPromptCompatibleWithRunner(prompt.file, prompt.section, promptRunnerIdentity));
78791
- }
78792
-
78793
79843
  /**
78794
79844
  * Selects the next runnable prompt based on priority.
78795
79845
  */
@@ -78803,54 +79853,6 @@
78803
79853
  return nextPrompt;
78804
79854
  }
78805
79855
 
78806
- /**
78807
- * Lists upcoming tasks that are ready to run (no authoring placeholders).
78808
- */
78809
- function listUpcomingTasks(files, priorityFilter = {}, promptRunnerIdentity) {
78810
- return listRunnablePrompts(files, priorityFilter, promptRunnerIdentity).map(({ file, section }) => ({
78811
- label: buildPromptLabelForDisplay(file, section),
78812
- summary: buildPromptSummary(file, section),
78813
- priority: section.priority,
78814
- }));
78815
- }
78816
-
78817
- /**
78818
- * Groups upcoming tasks by priority, high to low.
78819
- */
78820
- function groupUpcomingTasksByPriority(tasks) {
78821
- const grouped = new Map();
78822
- for (const task of tasks) {
78823
- const group = grouped.get(task.priority);
78824
- if (group) {
78825
- group.push(task);
78826
- }
78827
- else {
78828
- grouped.set(task.priority, [task]);
78829
- }
78830
- }
78831
- return Array.from(grouped.entries())
78832
- .sort((a, b) => b[0] - a[0])
78833
- .map(([priority, groupedTasks]) => ({ priority, tasks: groupedTasks }));
78834
- }
78835
-
78836
- /**
78837
- * Prints upcoming tasks grouped by priority.
78838
- */
78839
- function printUpcomingTasks(tasks) {
78840
- if (tasks.length === 0) {
78841
- console.info(colors__default["default"].green('No upcoming tasks.'));
78842
- return;
78843
- }
78844
- console.info(colors__default["default"].cyan('Upcoming tasks (grouped by priority):'));
78845
- for (const group of groupUpcomingTasksByPriority(tasks)) {
78846
- console.info(colors__default["default"].cyan(`Priority ${group.priority}:`));
78847
- for (const [index, task] of group.tasks.entries()) {
78848
- const summary = task.summary ? ` - ${task.summary}` : '';
78849
- console.info(` ${index + 1}. ${task.label}${summary}`);
78850
- }
78851
- }
78852
- }
78853
-
78854
79856
  /**
78855
79857
  * Builds a label that identifies the prompt section by index.
78856
79858
  */
@@ -79089,12 +80091,23 @@
79089
80091
  * @public exported from `@promptbook/cli`
79090
80092
  */
79091
80093
  async function runCodexPrompts(providedOptions) {
80094
+ var _a;
79092
80095
  const options = normalizeRunOptions(providedOptions !== null && providedOptions !== void 0 ? providedOptions : parseRunOptions(process.argv.slice(2)));
79093
80096
  validateRunCodexPromptOptions(options);
79094
80097
  resetCoderRunControls();
79095
80098
  const runStartDate = moment__default["default"]();
79096
80099
  const { isRichUiEnabled, progressDisplay, uiHandle } = createRunDisplays(options, runStartDate);
79097
- const waitForRequestedPause = createPauseWaiter({ isRichUiEnabled, progressDisplay, uiHandle });
80100
+ const waitForRequestedPause = createPauseWaiter({
80101
+ isRichUiEnabled,
80102
+ progressDisplay,
80103
+ uiHandle,
80104
+ // Note: Every pause checkpoint of the whole run goes through this one waiter, so watching the free disk
80105
+ // space here covers each round, each verification and each runner without repeating the check
80106
+ guardFreeDiskSpace: createFreeDiskSpaceGuard({
80107
+ inspectedPath: process.cwd(),
80108
+ isAskingQuestionsEnabled: (_a = options.isAskingQuestionsEnabled) !== null && _a !== void 0 ? _a : true,
80109
+ }),
80110
+ });
79098
80111
  startPauseListenerIfNeeded(isRichUiEnabled);
79099
80112
  try {
79100
80113
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
@@ -79107,6 +80120,7 @@
79107
80120
  const promptRunnerIdentity = {
79108
80121
  harnessName: options.agentName,
79109
80122
  modelName: actualRunnerModel,
80123
+ agentReferences: resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.agentReferences,
79110
80124
  };
79111
80125
  console.info(colors__default["default"].green(`Running prompts with ${runner.name}`));
79112
80126
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
@@ -79543,11 +80557,14 @@
79543
80557
  * Creates a pause waiter that keeps the progress display and rich UI in sync.
79544
80558
  */
79545
80559
  function createPauseWaiter(options) {
79546
- const { isRichUiEnabled, progressDisplay, uiHandle } = options;
80560
+ const { isRichUiEnabled, progressDisplay, uiHandle, guardFreeDiskSpace } = options;
79547
80561
  return async (checkpoint) => {
79548
80562
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase(checkpoint.phase);
79549
80563
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(checkpoint.statusMessage);
79550
80564
  announcePauseTargetLabel(checkpoint.checkpointLabel);
80565
+ // Note: Runs after the checkpoint label is announced, so a pause requested because of a full disk
80566
+ // replaces that label and says why the run is standing still
80567
+ await guardFreeDiskSpace();
79551
80568
  await checkPause({
79552
80569
  silent: isRichUiEnabled,
79553
80570
  onPaused: () => {
@@ -79666,7 +80683,7 @@
79666
80683
  return false;
79667
80684
  }
79668
80685
  if (promptQueueSnapshot.stats.forAgent > 0) {
79669
- announceRunCompletion('No prompts match the selected harness or model.', colors__default["default"].yellow, isRichUiEnabled, uiHandle);
80686
+ announceRunCompletion('No prompts match the selected harness, model or agent.', colors__default["default"].yellow, isRichUiEnabled, uiHandle);
79670
80687
  }
79671
80688
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79672
80689
  announceRunCompletion('No prompts ready for agent.', colors__default["default"].yellow, isRichUiEnabled, uiHandle);
@@ -79711,7 +80728,7 @@
79711
80728
  function announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle) {
79712
80729
  let message;
79713
80730
  if (promptQueueSnapshot.stats.forAgent > 0) {
79714
- message = 'No prompts match the selected harness or model. Watching for changes...';
80731
+ message = 'No prompts match the selected harness, model or agent. Watching for changes...';
79715
80732
  }
79716
80733
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79717
80734
  message = 'No prompts ready for agent. Watching for changes...';