@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/esm/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import colors from 'colors';
2
2
  import commander, { Option } from 'commander';
3
3
  import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
- import { writeFile, stat, mkdir, readFile, readdir, rm, cp, lstat, symlink, rename, unlink, appendFile, realpath, copyFile, access, constants, watch, rmdir } from 'fs/promises';
4
+ import { writeFile, stat, readdir, mkdir, readFile, rm, cp, lstat, symlink, rename, unlink, appendFile, realpath, copyFile, statfs, access, constants, watch, rmdir } from 'fs/promises';
5
5
  import { join, delimiter, relative, basename, resolve, dirname, isAbsolute, extname } from 'path';
6
6
  import { createHash, randomBytes } from 'crypto';
7
7
  import { spawn, spawnSync, execFile } from 'child_process';
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-32';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-34';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1708,6 +1708,35 @@ function $initializeAboutCommand(program) {
1708
1708
  // TODO: [🗽] Unite branding and make single place for it
1709
1709
  // Note: [💞] Ignore a discrepancy between file name and entity name
1710
1710
 
1711
+ /**
1712
+ * Message printed when one CLI command which only groups subcommands is run without picking any of them.
1713
+ *
1714
+ * @private internal constant of `$requireCliSubcommand`
1715
+ */
1716
+ const MISSING_CLI_SUBCOMMAND_MESSAGE = 'Please specify a subcommand.';
1717
+ /**
1718
+ * Makes one CLI command which only groups subcommands ask for a subcommand instead of doing anything on its own.
1719
+ *
1720
+ * Note: `$` is used to indicate that this function is not a pure function - it registers an action in the CLI
1721
+ *
1722
+ * @private utility of CLI
1723
+ */
1724
+ function $requireCliSubcommand(command) {
1725
+ command.action(() => $reportMissingCliSubcommand(command));
1726
+ }
1727
+ /**
1728
+ * Asks for a subcommand of one CLI command, prints its help and exits the process.
1729
+ *
1730
+ * Note: `$` is used to indicate that this function is not a pure function - it writes to the console and exits the process
1731
+ *
1732
+ * @private utility of CLI
1733
+ */
1734
+ function $reportMissingCliSubcommand(command) {
1735
+ console.info(colors.yellow(MISSING_CLI_SUBCOMMAND_MESSAGE));
1736
+ console.info('');
1737
+ return command.help();
1738
+ }
1739
+
1711
1740
  // Note: [💞] Ignore a discrepancy between file name and entity name
1712
1741
  /**
1713
1742
  * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
@@ -1812,20 +1841,20 @@ const PROMPT_RUNNER_HARNESS_NAMES = CLI_AGENT_HARNESS_NAMES;
1812
1841
  */
1813
1842
  const PROMPT_RUNNER_DESCRIPTION = spaceTrim$1(`
1814
1843
  Runners:
1815
- - openai-codex: OpenAI Codex integration (requires --model)
1844
+ - openai-codex: OpenAI Codex integration (requires --model when executing)
1816
1845
  - github-copilot: GitHub Copilot CLI integration
1817
1846
  - cline: Cline CLI integration
1818
1847
  - claude-code: Claude Code integration
1819
1848
  - opencode: Opencode integration
1820
- - gemini: Google Gemini CLI integration (requires --model)
1821
- - qwen-code: Qwen Code CLI integration (requires --model)
1849
+ - gemini: Google Gemini CLI integration (requires --model when executing)
1850
+ - qwen-code: Qwen Code CLI integration (requires --model when executing)
1822
1851
  `);
1823
1852
  /**
1824
1853
  * Commander description for the `--harness` option.
1825
1854
  *
1826
1855
  * @private internal utility of `promptbookCli`
1827
1856
  */
1828
- const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = `Select runner: ${PROMPT_RUNNER_HARNESS_NAMES.join(', ')} (required for non-dry-run)`;
1857
+ const PROMPT_RUNNER_HARNESS_OPTION_DESCRIPTION = `Select runner: ${PROMPT_RUNNER_HARNESS_NAMES.join(', ')} (required when executing prompts)`;
1829
1858
  /**
1830
1859
  * Runner harness names listed as alternatives of the `--harness` option in error messages.
1831
1860
  *
@@ -1838,7 +1867,7 @@ const PROMPT_RUNNER_HARNESS_OPTION_HINT = `--harness <${PROMPT_RUNNER_HARNESS_NA
1838
1867
  * @private internal utility of `promptbookCli`
1839
1868
  */
1840
1869
  const PROMPT_RUNNER_MODEL_OPTION_DESCRIPTION = spaceTrim$1(`
1841
- Model to use (required for openai-codex, gemini and qwen-code)
1870
+ Model to use or filter by (required when executing with openai-codex, gemini and qwen-code)
1842
1871
 
1843
1872
  OpenAI examples: gpt-5.2-codex, default
1844
1873
  Gemini examples: gemini-3-flash-preview, default
@@ -2103,11 +2132,7 @@ function $initializeAgentCommand(program) {
2103
2132
  `));
2104
2133
  $initializeAgentChatCommand(agentCommand);
2105
2134
  $initializeAgentExecCommand(agentCommand);
2106
- agentCommand.action(() => {
2107
- console.info(colors.yellow('Please specify a subcommand.'));
2108
- console.info('');
2109
- agentCommand.help();
2110
- });
2135
+ $requireCliSubcommand(agentCommand);
2111
2136
  }
2112
2137
  // Note: [🟡] Code for CLI command [agent](src/cli/cli-commands/agent.ts) should never be published outside of `@promptbook/cli`
2113
2138
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -2265,6 +2290,23 @@ async function ensureDirectory(projectPath, relativeDirectoryPath) {
2265
2290
  }
2266
2291
  return 'unchanged';
2267
2292
  }
2293
+ /**
2294
+ * Checks whether a project-relative directory is missing or contains no entries.
2295
+ *
2296
+ * @private internal utility of `coder init` command
2297
+ */
2298
+ async function isDirectoryEmpty(projectPath, relativeDirectoryPath) {
2299
+ const directoryPath = join(projectPath, relativeDirectoryPath);
2300
+ try {
2301
+ return (await readdir(directoryPath)).length === 0;
2302
+ }
2303
+ catch (error) {
2304
+ if (isNodeJsErrorWithCode$1(error, 'ENOENT')) {
2305
+ return true;
2306
+ }
2307
+ throw error;
2308
+ }
2309
+ }
2268
2310
  /**
2269
2311
  * Checks whether a path exists and is a directory.
2270
2312
  */
@@ -2276,6 +2318,12 @@ async function isExistingDirectory$2(path) {
2276
2318
  return false;
2277
2319
  }
2278
2320
  }
2321
+ /**
2322
+ * Checks whether a caught value is a Node.js error with the given error code.
2323
+ */
2324
+ function isNodeJsErrorWithCode$1(error, code) {
2325
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
2326
+ }
2279
2327
  // Note: [🟡] Code for coder init directory creation [ensureDirectory](src/cli/cli-commands/coder/ensureDirectory.ts) should never be published outside of `@promptbook/cli`
2280
2328
 
2281
2329
  /**
@@ -2563,11 +2611,7 @@ function $initializeAgentFolderCommand(program) {
2563
2611
  $initializeAgentTickCommand(agentFolderCommand);
2564
2612
  $initializeAgentRunCommand(agentFolderCommand);
2565
2613
  $initializeAgentRunMultipleCommand(agentFolderCommand);
2566
- agentFolderCommand.action(() => {
2567
- console.info(colors.yellow('Please specify a subcommand.'));
2568
- console.info('');
2569
- agentFolderCommand.help();
2570
- });
2614
+ $requireCliSubcommand(agentFolderCommand);
2571
2615
  }
2572
2616
  // Note: [🟡] Code for CLI command [agent-folder](src/cli/cli-commands/agent-folder.ts) should never be published outside of `@promptbook/cli`
2573
2617
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -21418,10 +21462,30 @@ function normalizeRelativePath$3(relativePath) {
21418
21462
  return relativePath.replace(/\\/gu, '/');
21419
21463
  }
21420
21464
 
21465
+ /**
21466
+ * AuthenticationError is thrown from login function which is dependency of remote server
21467
+ *
21468
+ * @public exported from `@promptbook/core`
21469
+ */
21470
+ class AuthenticationError extends Error {
21471
+ constructor(message) {
21472
+ super(message);
21473
+ this.name = 'AuthenticationError';
21474
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
21475
+ }
21476
+ }
21477
+
21421
21478
  /**
21422
21479
  * Formats one unknown error-like value into readable text for logs and feedback.
21480
+ *
21481
+ * An `AuthenticationError` is reported without its stack, because its message is written for the user - it
21482
+ * says which harness has to be signed in again and how - and the frames of a failure nobody can debug would
21483
+ * only push those instructions out of sight.
21423
21484
  */
21424
21485
  function formatUnknownErrorDetails(error) {
21486
+ if (error instanceof AuthenticationError) {
21487
+ return error.message;
21488
+ }
21425
21489
  if (error instanceof Error) {
21426
21490
  return error.stack || error.message;
21427
21491
  }
@@ -24138,6 +24202,7 @@ const HARNESS_DEFINITIONS = {
24138
24202
  commandName: 'codex',
24139
24203
  npmPackageName: '@openai/codex',
24140
24204
  projectGitignoreRules: ['.codex'],
24205
+ authenticationMethod: { command: 'codex login' },
24141
24206
  // Note: The official standalone installer keeps its package in `~/.codex` and links it onto the `PATH`
24142
24207
  standaloneInstallation: {
24143
24208
  directoryNames: ['.codex'],
@@ -24150,6 +24215,7 @@ const HARNESS_DEFINITIONS = {
24150
24215
  commandName: 'copilot',
24151
24216
  npmPackageName: '@github/copilot',
24152
24217
  projectGitignoreRules: ['.github/copilot/settings.local.json'],
24218
+ authenticationMethod: { command: 'copilot', interactiveStep: 'running the `/login` command inside it' },
24153
24219
  // Note: The GitHub Copilot CLI downloads its native binary in a postinstall script
24154
24220
  npmInstallEnvironment: { npm_config_ignore_scripts: 'false' },
24155
24221
  },
@@ -24159,6 +24225,7 @@ const HARNESS_DEFINITIONS = {
24159
24225
  commandName: 'cline',
24160
24226
  npmPackageName: 'cline',
24161
24227
  projectGitignoreRules: ['.cline'],
24228
+ authenticationMethod: { command: 'cline', interactiveStep: 'signing in to the provider inside it' },
24162
24229
  },
24163
24230
  'claude-code': {
24164
24231
  harnessName: 'claude-code',
@@ -24166,6 +24233,7 @@ const HARNESS_DEFINITIONS = {
24166
24233
  commandName: 'claude',
24167
24234
  npmPackageName: '@anthropic-ai/claude-code',
24168
24235
  projectGitignoreRules: ['.claude'],
24236
+ authenticationMethod: { command: 'claude', interactiveStep: 'running the `/login` command inside it' },
24169
24237
  },
24170
24238
  opencode: {
24171
24239
  harnessName: 'opencode',
@@ -24173,6 +24241,7 @@ const HARNESS_DEFINITIONS = {
24173
24241
  commandName: 'opencode',
24174
24242
  npmPackageName: 'opencode-ai',
24175
24243
  projectGitignoreRules: ['.opencode'],
24244
+ authenticationMethod: { command: 'opencode auth login' },
24176
24245
  },
24177
24246
  gemini: {
24178
24247
  harnessName: 'gemini',
@@ -24180,6 +24249,7 @@ const HARNESS_DEFINITIONS = {
24180
24249
  commandName: 'gemini',
24181
24250
  npmPackageName: '@google/gemini-cli',
24182
24251
  projectGitignoreRules: ['.gemini'],
24252
+ authenticationMethod: { command: 'gemini', interactiveStep: 'running the `/auth` command inside it' },
24183
24253
  },
24184
24254
  'qwen-code': {
24185
24255
  harnessName: 'qwen-code',
@@ -24187,6 +24257,7 @@ const HARNESS_DEFINITIONS = {
24187
24257
  commandName: 'qwen',
24188
24258
  npmPackageName: '@qwen-code/qwen-code',
24189
24259
  projectGitignoreRules: ['.qwen'],
24260
+ authenticationMethod: { command: 'qwen', interactiveStep: 'running the `/auth` command inside it' },
24190
24261
  },
24191
24262
  };
24192
24263
  /**
@@ -26220,6 +26291,183 @@ function formatClaudeCodeSessionIdForDisplay(sessionId) {
26220
26291
  return `${sessionId.slice(0, 8)}...`;
26221
26292
  }
26222
26293
 
26294
+ /**
26295
+ * Formats one unknown error-like value into its readable message without the stack trace.
26296
+ *
26297
+ * Use this instead of `formatUnknownErrorDetails` whenever the text is shown to the user, for example
26298
+ * inside a branded error, where the stack of the wrapped error would only bury the actual cause.
26299
+ */
26300
+ function formatUnknownErrorMessage(error) {
26301
+ if (error instanceof Error) {
26302
+ return error.message;
26303
+ }
26304
+ if (typeof error === 'string') {
26305
+ return error;
26306
+ }
26307
+ const serializedError = JSON.stringify(error, null, 2);
26308
+ return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
26309
+ }
26310
+
26311
+ /**
26312
+ * Builds the branded error which tells the user that the selected harness has to be signed in again.
26313
+ */
26314
+ function buildHarnessAuthenticationError(options) {
26315
+ const { harnessName, reason } = options;
26316
+ const { label, authenticationMethod } = getHarnessDefinition(harnessName);
26317
+ return new AuthenticationError(spaceTrim((block) => `
26318
+ The **${label}** harness is not authenticated, so the prompt was not run.
26319
+
26320
+ ${label} reported:
26321
+ > ${reason}
26322
+
26323
+ ${block(buildSignInInstruction(authenticationMethod))}
26324
+
26325
+ Once you are signed in, start the very same \`ptbk coder\` command again.
26326
+ `));
26327
+ }
26328
+ /**
26329
+ * Builds the sentence which tells the user how to sign in to one harness again.
26330
+ */
26331
+ function buildSignInInstruction(authenticationMethod) {
26332
+ const { command, interactiveStep } = authenticationMethod;
26333
+ if (interactiveStep === undefined) {
26334
+ return `Sign in again by running \`${command}\`.`;
26335
+ }
26336
+ return `Sign in again by running \`${command}\` and then ${interactiveStep}.`;
26337
+ }
26338
+
26339
+ /**
26340
+ * Maximum length of the harness reason quoted back to the user.
26341
+ */
26342
+ const MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH = 300;
26343
+ /**
26344
+ * Keys of machine-readable harness output whose string value carries the human-readable failure reason.
26345
+ *
26346
+ * Harnesses which stream JSON events - like Claude Code with its `stream-json` output - report the reason only
26347
+ * inside such a value, surrounded by hundreds of characters of session metadata which must never be shown to
26348
+ * the user. The keys are ordered from the most to the least conclusive one.
26349
+ */
26350
+ const HARNESS_FAILURE_MESSAGE_KEYS = ['result', 'error_message', 'message', 'text'];
26351
+ /**
26352
+ * Patterns which extract one JSON string value reported under a harness failure message key.
26353
+ */
26354
+ const HARNESS_FAILURE_MESSAGE_PATTERNS = HARNESS_FAILURE_MESSAGE_KEYS.map((messageKey) => new RegExp(`"${messageKey}"\\s*:\\s*("(?:[^"\\\\]|\\\\.)*")`, 'g'));
26355
+ /**
26356
+ * Signatures which mean that the harness refused to work because it is not logged in.
26357
+ *
26358
+ * They are deliberately narrow phrases instead of single words like `login`, because the output of a harness
26359
+ * routinely mentions its own `/login` command among the commands it offers, and such a mention must never be
26360
+ * read as a failed login.
26361
+ */
26362
+ const AUTHENTICATION_FAILURE_PATTERNS = [
26363
+ /\bauthentication[ _-]?failed\b/i,
26364
+ /\bfailed to authenticate\b/i,
26365
+ /\bnot authenticated\b/i,
26366
+ /\bnot logged[ -]?in\b/i,
26367
+ /\bnot signed[ -]?in\b/i,
26368
+ /\bno authentication information found\b/i,
26369
+ /\bauthentication (?:is )?required\b/i,
26370
+ /\blogin (?:is )?required\b/i,
26371
+ /\bplease (?:log|sign) in\b/i,
26372
+ /\bunauthorized\b/i,
26373
+ /\b(?:oauth|session|token|credentials?|login)\b[^.]{0,60}?\bexpired\b/i,
26374
+ /\bexpired\b[^.]{0,60}?\b(?:oauth|session|token|credentials?|login)\b/i,
26375
+ /\binvalid api[ _-]?key\b/i,
26376
+ /\bmissing api[ _-]?key\b/i,
26377
+ /\binvalid credentials?\b/i,
26378
+ ];
26379
+ /**
26380
+ * Reads the reason why a harness refused to work because it is not logged in, from its raw CLI output.
26381
+ *
26382
+ * Messages the harness reports in machine-readable output are preferred over its raw output lines, so the
26383
+ * reason is the sentence the harness wrote for a human and not the JSON event which transported it.
26384
+ *
26385
+ * @returns One short single-line reason, or `undefined` when the failure was not caused by authentication.
26386
+ */
26387
+ function extractHarnessAuthenticationFailureReason(harnessOutput) {
26388
+ const reportedMessages = [...extractHarnessReportedMessages(harnessOutput), ...harnessOutput.split(/\r?\n/)];
26389
+ for (const reportedMessage of reportedMessages) {
26390
+ const reason = reportedMessage.replace(/\s+/g, ' ').trim();
26391
+ if (isAuthenticationFailureReason(reason)) {
26392
+ return limitAuthenticationFailureReason(reason);
26393
+ }
26394
+ }
26395
+ return undefined;
26396
+ }
26397
+ /**
26398
+ * Lists the human-readable messages a harness reported in its machine-readable output.
26399
+ */
26400
+ function extractHarnessReportedMessages(harnessOutput) {
26401
+ const reportedMessages = [];
26402
+ for (const messagePattern of HARNESS_FAILURE_MESSAGE_PATTERNS) {
26403
+ for (const match of harnessOutput.matchAll(messagePattern)) {
26404
+ const reportedMessage = parseSerializedJsonString(match[1]);
26405
+ if (reportedMessage !== undefined) {
26406
+ reportedMessages.push(reportedMessage);
26407
+ }
26408
+ }
26409
+ }
26410
+ return reportedMessages;
26411
+ }
26412
+ /**
26413
+ * Reads one JSON string literal back into plain text, tolerating output which only looks like JSON.
26414
+ */
26415
+ function parseSerializedJsonString(serializedString) {
26416
+ if (serializedString === undefined) {
26417
+ return undefined;
26418
+ }
26419
+ try {
26420
+ const parsedString = JSON.parse(serializedString);
26421
+ return typeof parsedString === 'string' ? parsedString : undefined;
26422
+ }
26423
+ catch (_a) {
26424
+ return undefined;
26425
+ }
26426
+ }
26427
+ /**
26428
+ * Returns true when one message of the harness says that it is not logged in.
26429
+ */
26430
+ function isAuthenticationFailureReason(reportedMessage) {
26431
+ return AUTHENTICATION_FAILURE_PATTERNS.some((pattern) => pattern.test(reportedMessage));
26432
+ }
26433
+ /**
26434
+ * Shortens a very long reason, so the guidance which follows it stays visible in the terminal.
26435
+ */
26436
+ function limitAuthenticationFailureReason(reason) {
26437
+ if (reason.length <= MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH) {
26438
+ return reason;
26439
+ }
26440
+ return `${reason.slice(0, MAXIMUM_AUTHENTICATION_FAILURE_REASON_LENGTH)}...`;
26441
+ }
26442
+
26443
+ /**
26444
+ * Wraps one prompt runner so a failure caused by a missing or expired harness login is reported as a branded
26445
+ * `AuthenticationError` which tells the user how to sign in again.
26446
+ *
26447
+ * Each harness reports its own failures differently, but all of them report them by failing `runPrompt` with
26448
+ * the raw CLI output as the message. Translating the failure here therefore keeps exactly one place which has
26449
+ * to recognize an unauthenticated harness, for every harness and for every command which runs prompts.
26450
+ */
26451
+ function createAuthenticationAwarePromptRunner(runner, harnessName) {
26452
+ var _a;
26453
+ return {
26454
+ name: runner.name,
26455
+ runPrompt: async (options) => {
26456
+ try {
26457
+ return await runner.runPrompt(options);
26458
+ }
26459
+ catch (error) {
26460
+ const reason = extractHarnessAuthenticationFailureReason(formatUnknownErrorMessage(error));
26461
+ if (reason === undefined) {
26462
+ throw error;
26463
+ }
26464
+ throw buildHarnessAuthenticationError({ harnessName, reason });
26465
+ }
26466
+ },
26467
+ getSubscriptionUsage: (_a = runner.getSubscriptionUsage) === null || _a === void 0 ? void 0 : _a.bind(runner),
26468
+ };
26469
+ }
26470
+
26223
26471
  /**
26224
26472
  * Creates a temporary script file, runs it, and cleans it up unless preservation is requested or the run fails.
26225
26473
  */
@@ -27959,6 +28207,18 @@ function resolvePromptRunner(options) {
27959
28207
  if (!agentName) {
27960
28208
  throw new Error('Missing --harness in non-dry run mode');
27961
28209
  }
28210
+ const resolution = resolveHarnessPromptRunner(agentName, options);
28211
+ return {
28212
+ ...resolution,
28213
+ // Note: Every harness is wrapped here, so a harness which is not logged in is reported the same clear
28214
+ // way no matter which harness it is and which command has resolved the runner
28215
+ runner: createAuthenticationAwarePromptRunner(resolution.runner, agentName),
28216
+ };
28217
+ }
28218
+ /**
28219
+ * Creates the runner of one selected harness together with its status-line metadata.
28220
+ */
28221
+ function resolveHarnessPromptRunner(agentName, options) {
27962
28222
  if (agentName === 'openai-codex') {
27963
28223
  return createOpenAiCodexRunnerResolution(options);
27964
28224
  }
@@ -28426,11 +28686,19 @@ const MIN_FRAME_WIDTH = 56;
28426
28686
  * Maximum width used for the rich coder-run frame.
28427
28687
  */
28428
28688
  const MAX_FRAME_WIDTH = 96;
28689
+ /**
28690
+ * Number of terminal columns deliberately left unused by the rich coder-run frame.
28691
+ *
28692
+ * A row which fills the final physical terminal column can auto-wrap. The incremental terminal
28693
+ * updater moves by logical rows, so an auto-wrapped row would put its cursor out of sync with the
28694
+ * frame and let a later animated avatar redraw overwrite dashboard content.
28695
+ */
28696
+ const TERMINAL_AUTO_WRAP_RESERVED_COLUMNS = 1;
28429
28697
  /**
28430
28698
  * Builds the complete boxed terminal frame for the rich `ptbk coder run` UI.
28431
28699
  */
28432
28700
  function buildCoderRunUiFrame(options) {
28433
- const totalWidth = Math.max(MIN_FRAME_WIDTH, Math.min(options.terminalWidth, MAX_FRAME_WIDTH));
28701
+ const totalWidth = resolveCoderRunUiFrameWidth(options.terminalWidth);
28434
28702
  const isPromptActive = options.phase === 'running' || options.phase === 'verifying' || options.phase === 'loading';
28435
28703
  const promptStatusPrefix = isPromptActive ? `${colors.yellow(`${options.spinner} `)}` : '';
28436
28704
  const pausePresentation = buildPausePresentation(options.phase, options.pauseState, options.pauseTargetLabel, options.statusMessage);
@@ -28467,6 +28735,16 @@ function buildCoderRunUiFrame(options) {
28467
28735
  frame.push(...renderBox('Controls', controlsBoxLines, totalWidth, colors.white.bold));
28468
28736
  return frame;
28469
28737
  }
28738
+ /**
28739
+ * Resolves the safe width of the rich terminal frame from the available physical terminal columns.
28740
+ *
28741
+ * The reserved trailing column keeps every boxed row from auto-wrapping, which makes the frame's
28742
+ * logical row count match its physical terminal row count during animated incremental redraws.
28743
+ */
28744
+ function resolveCoderRunUiFrameWidth(terminalWidth) {
28745
+ const availableFrameWidth = terminalWidth - TERMINAL_AUTO_WRAP_RESERVED_COLUMNS;
28746
+ return Math.max(MIN_FRAME_WIDTH, Math.min(availableFrameWidth, MAX_FRAME_WIDTH));
28747
+ }
28470
28748
  /**
28471
28749
  * Builds the header visual above the dashboard boxes.
28472
28750
  *
@@ -29943,16 +30221,14 @@ const SPINNER_FRAMES = [
29943
30221
  */
29944
30222
  const DEFAULT_TERMINAL_WIDTH = 80;
29945
30223
  /**
29946
- * Maximum terminal width supported by the boxed runner UI.
29947
- */
29948
- const MAX_TERMINAL_WIDTH = 96;
29949
- /**
29950
- * Returns the usable terminal width, capped at 96.
30224
+ * Returns the number of columns reported by the current terminal.
30225
+ *
30226
+ * The frame builder owns clamping and reserves its trailing auto-wrap safety column.
29951
30227
  *
29952
30228
  * @private internal utility of coder run UI
29953
30229
  */
29954
- function getTerminalWidth() {
29955
- return Math.min(process.stdout.columns || DEFAULT_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH);
30230
+ function getTerminalColumnCount() {
30231
+ return process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
29956
30232
  }
29957
30233
  /**
29958
30234
  * Boots the ANSI terminal UI for `ptbk coder run`.
@@ -30059,7 +30335,7 @@ function renderCoderRunUi(startTime, options = {}) {
30059
30335
  */
30060
30336
  function buildFrameLines() {
30061
30337
  return buildFrameLinesFromState({
30062
- terminalWidth: getTerminalWidth(),
30338
+ terminalWidth: getTerminalColumnCount(),
30063
30339
  animationFrame: spinnerFrame,
30064
30340
  animationTimeMs: Date.now() - animationStartTimeMs,
30065
30341
  spinner: SPINNER_FRAMES[spinnerFrame],
@@ -33790,19 +34066,6 @@ class CsvFormatError extends AbstractFormatError {
33790
34066
  }
33791
34067
  }
33792
34068
 
33793
- /**
33794
- * AuthenticationError is thrown from login function which is dependency of remote server
33795
- *
33796
- * @public exported from `@promptbook/core`
33797
- */
33798
- class AuthenticationError extends Error {
33799
- constructor(message) {
33800
- super(message);
33801
- this.name = 'AuthenticationError';
33802
- Object.setPrototypeOf(this, AuthenticationError.prototype);
33803
- }
33804
- }
33805
-
33806
34069
  /**
33807
34070
  * This error indicates that the pipeline collection cannot be properly loaded
33808
34071
  *
@@ -42031,11 +42294,7 @@ function $initializeAgentsServerCommand(program) {
42031
42294
  $initializeAgentsServerDevCommand(agentsServerCommand);
42032
42295
  $initializeAgentsServerInitCommand(agentsServerCommand);
42033
42296
  $initializeAgentsServerStartCommand(agentsServerCommand);
42034
- agentsServerCommand.action(() => {
42035
- console.info(colors.yellow('Please specify a subcommand.'));
42036
- console.info('');
42037
- agentsServerCommand.help();
42038
- });
42297
+ $requireCliSubcommand(agentsServerCommand);
42039
42298
  }
42040
42299
  // Note: [🟡] Code for CLI command [agents-server](src/cli/cli-commands/agents-server.ts) should never be published outside of `@promptbook/cli`
42041
42300
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -42125,24 +42384,82 @@ function normalizeCoderGitSyncCliOptions(cliOptions) {
42125
42384
  // Note: [🟡] Code for CLI git synchronization options [coderGitSyncCliOptions](src/cli/cli-commands/common/coderGitSyncCliOptions.ts) should never be published outside of `@promptbook/cli`
42126
42385
  // Note: [💞] Ignore a discrepancy between file name and exported helper names
42127
42386
 
42387
+ /**
42388
+ * Description block shared by the `ptbk coder` commands which can ask interactive questions.
42389
+ *
42390
+ * @private internal utility of `promptbookCli`
42391
+ */
42392
+ const QUESTIONS_DESCRIPTION = spaceTrim$1(`
42393
+ Interactive questions:
42394
+ - Steps which need an answer to continue are asked in the terminal
42395
+ - --no-questions never asks: an optional step is skipped with a note how to do it manually, a required one fails right away
42396
+ `);
42397
+ /**
42398
+ * Registers the shared `--no-questions` option on a `ptbk coder` command which can ask interactive questions.
42399
+ *
42400
+ * @private internal utility of `promptbookCli`
42401
+ */
42402
+ function addQuestionsOption(command) {
42403
+ command.option('--no-questions', 'Never ask an interactive question and skip every optional step which needs an answer to continue');
42404
+ }
42405
+ /**
42406
+ * Converts the Commander questions flag into the normalized interactive-questions option.
42407
+ *
42408
+ * @private internal utility of `promptbookCli`
42409
+ */
42410
+ function normalizeQuestionsCliOptions(cliOptions) {
42411
+ return {
42412
+ isAskingQuestionsEnabled: cliOptions.questions,
42413
+ };
42414
+ }
42415
+ /**
42416
+ * Asserts that a command which waits for a user confirmation before each prompt is allowed to ask for it.
42417
+ *
42418
+ * @throws {NotAllowed} when `--no-auto` is combined with `--no-questions`
42419
+ * @private internal utility of `promptbookCli`
42420
+ */
42421
+ function assertUserConfirmationIsAllowed(options) {
42422
+ const { isAskingQuestionsEnabled, isWaitingForUser } = options;
42423
+ if (!isWaitingForUser || isAskingQuestionsEnabled) {
42424
+ return;
42425
+ }
42426
+ throw new NotAllowed(spaceTrim$1(`
42427
+ Flag \`--no-auto\` can not be used together with \`--no-questions\`.
42428
+
42429
+ **There is nobody who could confirm each prompt when no question may be asked.**
42430
+
42431
+ Actionable hints:
42432
+ - Keep \`--no-auto\` for a supervised run, for example \`ptbk coder run --harness claude-code --no-auto\`.
42433
+ - Keep \`--no-questions\` for an unattended run, for example \`ptbk coder run --harness claude-code --no-questions\`.
42434
+ `));
42435
+ }
42436
+ // Note: [🟡] Code for CLI interactive questions options [questionsCliOptions](src/cli/cli-commands/common/questionsCliOptions.ts) should never be published outside of `@promptbook/cli`
42437
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
42438
+
42128
42439
  /**
42129
42440
  * Relative path to the root prompts directory used by Promptbook coder utilities.
42130
42441
  *
42131
42442
  * @private internal utility of `ptbk coder`
42132
42443
  */
42133
- const PROMPTS_DIRECTORY_PATH = 'prompts';
42444
+ const PROMPTS_DIRECTORY_PATH$1 = 'prompts';
42134
42445
  /**
42135
42446
  * Relative path to the archive directory used by `coder verify`.
42136
42447
  *
42137
42448
  * @private internal utility of `ptbk coder`
42138
42449
  */
42139
- const PROMPTS_DONE_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH, 'done');
42450
+ const PROMPTS_DONE_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH$1, 'done');
42140
42451
  /**
42141
42452
  * Relative path to the project-owned boilerplate templates directory.
42142
42453
  *
42143
42454
  * @private internal utility of `ptbk coder`
42144
42455
  */
42145
- const PROMPTS_TEMPLATES_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH, 'templates');
42456
+ const PROMPTS_TEMPLATES_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH$1, 'templates');
42457
+ /**
42458
+ * Relative path to the project-agnostic `common` boilerplate template referenced by the default coder scripts.
42459
+ *
42460
+ * @private internal utility of `ptbk coder`
42461
+ */
42462
+ const COMMON_PROMPT_TEMPLATE_FILE_PATH = join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md');
42146
42463
  /**
42147
42464
  * Built-in boilerplate templates available to `coder generate-boilerplates`.
42148
42465
  *
@@ -42151,7 +42468,7 @@ const PROMPTS_TEMPLATES_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH, 'templates
42151
42468
  const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
42152
42469
  {
42153
42470
  id: 'common',
42154
- relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md'),
42471
+ relativeFilePath: COMMON_PROMPT_TEMPLATE_FILE_PATH,
42155
42472
  slugPrefix: null,
42156
42473
  content: spaceTrim$1(`
42157
42474
  - @@@
@@ -42197,30 +42514,17 @@ function getDefaultCoderPromptTemplateDefinition(template) {
42197
42514
  return definition;
42198
42515
  }
42199
42516
  /**
42200
- * Ensures the default project-owned coder template files exist without overwriting user customizations.
42517
+ * Ensures one project-owned coder template file exists without overwriting user customizations.
42201
42518
  *
42202
42519
  * @private internal utility of `ptbk coder`
42203
42520
  */
42204
- async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
42205
- const ensuredTemplateFiles = [];
42206
- for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
42207
- const absoluteTemplatePath = join(projectPath, definition.relativeFilePath);
42208
- if (await isExistingFile$3(absoluteTemplatePath)) {
42209
- ensuredTemplateFiles.push({
42210
- id: definition.id,
42211
- relativeFilePath: definition.relativeFilePath,
42212
- status: 'unchanged',
42213
- });
42214
- continue;
42215
- }
42216
- await writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
42217
- ensuredTemplateFiles.push({
42218
- id: definition.id,
42219
- relativeFilePath: definition.relativeFilePath,
42220
- status: 'created',
42221
- });
42521
+ async function ensureDefaultCoderPromptTemplateFile(projectPath, definition) {
42522
+ const absoluteTemplatePath = join(projectPath, definition.relativeFilePath);
42523
+ if (await isExistingFile$3(absoluteTemplatePath)) {
42524
+ return 'unchanged';
42222
42525
  }
42223
- return ensuredTemplateFiles;
42526
+ await writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
42527
+ return 'created';
42224
42528
  }
42225
42529
  /**
42226
42530
  * Resolves the template requested by `coder generate-boilerplates`.
@@ -42376,6 +42680,8 @@ function $initializeCoderAddCommand(program) {
42376
42680
  - \`ptbk coder add\`
42377
42681
 
42378
42682
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
42683
+
42684
+ ${block(QUESTIONS_DESCRIPTION)}
42379
42685
  `));
42380
42686
  command.argument('[description]', 'Plain-language description of the feature or task to implement');
42381
42687
  command.option('--priority <priority>', 'Priority of the new prompt — higher priorities run first (rendered as trailing `!` markers)', parsePriorityOption, 0);
@@ -42387,11 +42693,13 @@ function $initializeCoderAddCommand(program) {
42387
42693
  .join(', ')}) or a markdown file path relative to the current project root.
42388
42694
  `));
42389
42695
  addCoderGitSyncOptions(command);
42696
+ addQuestionsOption(command);
42390
42697
  command.action(handleActionErrors(async (descriptionArgument, cliOptions) => {
42391
42698
  const { priority, template: templateOption } = cliOptions;
42392
42699
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
42700
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
42393
42701
  const projectPath = process.cwd();
42394
- const description = await resolveCoderPromptDescription(descriptionArgument);
42702
+ const description = await resolveCoderPromptDescription(descriptionArgument, questionsOptions);
42395
42703
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
42396
42704
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
42397
42705
  const commitScope = await $startCoderGitSync({ gitSync, projectPath });
@@ -42426,7 +42734,7 @@ async function addCoderPrompt({ projectPath, description, priority, templateOpti
42426
42734
  // Note: Import these dynamically to avoid circular dependencies and keep the CLI fast
42427
42735
  const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
42428
42736
  const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
42429
- const promptsDirectory = join(projectPath, PROMPTS_DIRECTORY_PATH);
42737
+ const promptsDirectory = join(projectPath, PROMPTS_DIRECTORY_PATH$1);
42430
42738
  mkdirSync(promptsDirectory, { recursive: true });
42431
42739
  const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
42432
42740
  const promptNumbering = await getPromptNumbering({
@@ -42450,7 +42758,7 @@ async function addCoderPrompt({ projectPath, description, priority, templateOpti
42450
42758
  });
42451
42759
  const slug = buildAddPromptSlug(promptTemplate.slugPrefix, title);
42452
42760
  const filename = buildPromptFilename(promptNumbering.datePrefix, promptNumbering.startNumber, slug);
42453
- const filePath = join(PROMPTS_DIRECTORY_PATH, filename);
42761
+ const filePath = join(PROMPTS_DIRECTORY_PATH$1, filename);
42454
42762
  const absoluteFilePath = join(projectPath, filePath);
42455
42763
  writeFileSync(absoluteFilePath, `${section}\n`, 'utf-8');
42456
42764
  console.info(colors.green(`✓ Added prompt ${emojiTag} in ${filePath}`));
@@ -42461,7 +42769,7 @@ async function addCoderPrompt({ projectPath, description, priority, templateOpti
42461
42769
  *
42462
42770
  * @private internal utility of `coder add` command
42463
42771
  */
42464
- async function resolveCoderPromptDescription(descriptionArgument) {
42772
+ async function resolveCoderPromptDescription(descriptionArgument, { isAskingQuestionsEnabled }) {
42465
42773
  if (descriptionArgument !== undefined && descriptionArgument.trim() !== '') {
42466
42774
  return descriptionArgument.trim();
42467
42775
  }
@@ -42479,6 +42787,14 @@ async function resolveCoderPromptDescription(descriptionArgument) {
42479
42787
  }
42480
42788
  return standardInputDescription;
42481
42789
  }
42790
+ if (!isAskingQuestionsEnabled) {
42791
+ throw new ParseError(spaceTrim$1(`
42792
+ Cannot add a prompt without a description.
42793
+
42794
+ The description can not be typed interactively because \`--no-questions\` is used — provide it as an argument or pipe it through stdin instead:
42795
+ - \`ptbk coder add --no-questions "some new feature"\`
42796
+ `));
42797
+ }
42482
42798
  const { default: prompts } = await loadPromptsModule();
42483
42799
  const response = await prompts({
42484
42800
  type: 'text',
@@ -43042,10 +43358,10 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
43042
43358
  const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
43043
43359
  const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
43044
43360
  console.info(`🚀 Generate prompt boilerplate files`);
43045
- mkdirSync(join(projectPath, PROMPTS_DIRECTORY_PATH), { recursive: true });
43361
+ mkdirSync(join(projectPath, PROMPTS_DIRECTORY_PATH$1), { recursive: true });
43046
43362
  const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
43047
43363
  const promptNumbering = await getPromptNumbering({
43048
- promptsDir: join(projectPath, PROMPTS_DIRECTORY_PATH),
43364
+ promptsDir: join(projectPath, PROMPTS_DIRECTORY_PATH$1),
43049
43365
  step: 10,
43050
43366
  ignoreGlobs: ['**/node_modules/**'],
43051
43367
  });
@@ -43069,7 +43385,7 @@ async function generatePromptBoilerplate({ projectPath, boilerplateCount, templa
43069
43385
  const title = titles[i % titles.length];
43070
43386
  const emojiTag = formatPromptEmojiTag(selectedEmojis[i]);
43071
43387
  const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(promptTemplate.slugPrefix, title));
43072
- const filepath = join(PROMPTS_DIRECTORY_PATH, filename);
43388
+ const filepath = join(PROMPTS_DIRECTORY_PATH$1, filename);
43073
43389
  const absoluteFilepath = join(projectPath, filepath);
43074
43390
  const content = buildBoilerplatePromptFileContent({
43075
43391
  emojiTag,
@@ -43129,10 +43445,15 @@ function buildPromptSlug$1(templateSlugPrefix, title) {
43129
43445
  *
43130
43446
  * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin.
43131
43447
  *
43132
- * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive.
43448
+ * @returns `true` when the user confirms, `false` when the user declines, the terminal is not interactive
43449
+ * or the questions are disabled by `--no-questions`.
43133
43450
  * @private internal utility of `promptbookCli`
43134
43451
  */
43135
- async function $askForConfirmation(question) {
43452
+ async function $askForConfirmation(question, { isAskingQuestionsEnabled }) {
43453
+ if (!isAskingQuestionsEnabled) {
43454
+ // Note: `--no-questions` forbids asking, so the optional change is declined without blocking the run.
43455
+ return false;
43456
+ }
43136
43457
  if (!process.stdin.isTTY) {
43137
43458
  // Note: In non-interactive environments like CI there is nobody who could confirm the change.
43138
43459
  return false;
@@ -43155,11 +43476,12 @@ async function $askForConfirmation(question) {
43155
43476
  *
43156
43477
  * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin
43157
43478
  *
43158
- * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive
43479
+ * @returns `true` when the user confirms, `false` when the user declines, the terminal is not interactive
43480
+ * or the questions are disabled by `--no-questions`
43159
43481
  * @private internal utility of `promptbookCli`
43160
43482
  */
43161
- async function $askForNpmPackageInstallationApproval(question) {
43162
- return $askForConfirmation(question);
43483
+ async function $askForNpmPackageInstallationApproval(question, questionsOptions) {
43484
+ return $askForConfirmation(question, questionsOptions);
43163
43485
  }
43164
43486
  // 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`
43165
43487
 
@@ -43349,7 +43671,7 @@ function resolveHarnessUpdatePlan(definition, installationMethod) {
43349
43671
  *
43350
43672
  * @private internal utility of `promptbookCli`
43351
43673
  */
43352
- async function $applyHarnessInstallationStatus(status) {
43674
+ async function $applyHarnessInstallationStatus(status, questionsOptions) {
43353
43675
  const { definition, installationState, installedVersion } = status;
43354
43676
  if (installationState === 'up-to-date') {
43355
43677
  console.info(colors.gray(`✔ ${definition.label} ${installedVersion} is up to date.`));
@@ -43365,17 +43687,17 @@ async function $applyHarnessInstallationStatus(status) {
43365
43687
  }
43366
43688
  console.warn(colors.yellow(formatHarnessInstallationWarning(status)));
43367
43689
  if (installationState === 'not-installed') {
43368
- await $applyMissingHarnessInstallation(definition);
43690
+ await $applyMissingHarnessInstallation(definition, questionsOptions);
43369
43691
  return;
43370
43692
  }
43371
- await $applyOutdatedHarnessInstallation(status);
43693
+ await $applyOutdatedHarnessInstallation(status, questionsOptions);
43372
43694
  }
43373
43695
  /**
43374
43696
  * Offers to install a missing harness globally through npm.
43375
43697
  */
43376
- async function $applyMissingHarnessInstallation(definition) {
43698
+ async function $applyMissingHarnessInstallation(definition, questionsOptions) {
43377
43699
  const installCommand = buildHarnessInstallCommand(definition);
43378
- const isInstallationApproved = await $askForNpmPackageInstallationApproval(`Install ${definition.label} globally now?`);
43700
+ const isInstallationApproved = await $askForNpmPackageInstallationApproval(`Install ${definition.label} globally now?`, questionsOptions);
43379
43701
  if (!isInstallationApproved) {
43380
43702
  console.info(colors.gray(`Skipped, run \`${installCommand}\` to do it manually.`));
43381
43703
  return;
@@ -43390,14 +43712,14 @@ async function $applyMissingHarnessInstallation(definition) {
43390
43712
  /**
43391
43713
  * Offers to update an outdated harness exactly where it is installed.
43392
43714
  */
43393
- async function $applyOutdatedHarnessInstallation(status) {
43715
+ async function $applyOutdatedHarnessInstallation(status, questionsOptions) {
43394
43716
  const { definition, latestVersion, installationOrigin } = status;
43395
43717
  const updatePlan = resolveHarnessUpdatePlan(definition, installationOrigin.installationMethod);
43396
43718
  if (updatePlan.command === null || !updatePlan.isRunnableByPromptbook) {
43397
43719
  console.info(colors.gray(formatHarnessManualUpdateInstruction(status, updatePlan)));
43398
43720
  return;
43399
43721
  }
43400
- const isUpdateApproved = await $askForNpmPackageInstallationApproval(`Update ${definition.label} to ${latestVersion} with \`${updatePlan.command}\` now?`);
43722
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval(`Update ${definition.label} to ${latestVersion} with \`${updatePlan.command}\` now?`, questionsOptions);
43401
43723
  if (!isUpdateApproved) {
43402
43724
  console.info(colors.gray(`Skipped, run \`${updatePlan.command}\` to do it manually.`));
43403
43725
  return;
@@ -43833,7 +44155,7 @@ function resolveHarnessInstallationState(installedVersion, latestVersion, isHarn
43833
44155
  // Note: [🟡] Code for CLI harness installation check [$checkHarnessInstallation](src/cli/cli-commands/common/harness/$checkHarnessInstallation.ts) should never be published outside of `@promptbook/cli`
43834
44156
 
43835
44157
  /**
43836
- * Checks that every given CLI coding harness is installed globally and, when enabled, up to date.
44158
+ * Checks that every given CLI coding harness is installed globally and, when the questions are enabled, up to date.
43837
44159
  * Offers to install missing harnesses and update outdated ones.
43838
44160
  *
43839
44161
  * Harness names which are `undefined`, for example when no `--harness` is selected in a dry run,
@@ -43843,15 +44165,18 @@ function resolveHarnessInstallationState(installedVersion, latestVersion, isHarn
43843
44165
  *
43844
44166
  * @private internal utility of `promptbookCli`
43845
44167
  */
43846
- async function $ensureHarnessInstallations(harnessNames, isHarnessUpdateCheckEnabled = true) {
44168
+ async function $ensureHarnessInstallations(harnessNames, questionsOptions) {
43847
44169
  const definitions = resolveCheckedHarnessDefinitions(harnessNames);
43848
44170
  if (definitions.length === 0) {
43849
44171
  return;
43850
44172
  }
44173
+ // Note: An outdated harness can be updated only after the user confirms it, so `--no-questions`
44174
+ // skips the slower npm registry lookup entirely and only reports what is installed.
44175
+ const isHarnessUpdateCheckEnabled = questionsOptions.isAskingQuestionsEnabled;
43851
44176
  // Note: Detection of all harnesses runs in parallel, the questions have to be asked one by one
43852
44177
  const statuses = await Promise.all(definitions.map((definition) => $checkHarnessInstallation(definition, isHarnessUpdateCheckEnabled)));
43853
44178
  for (const status of statuses) {
43854
- await $applyHarnessInstallationStatus(status);
44179
+ await $applyHarnessInstallationStatus(status, questionsOptions);
43855
44180
  }
43856
44181
  }
43857
44182
  /**
@@ -43863,48 +44188,6 @@ function resolveCheckedHarnessDefinitions(harnessNames) {
43863
44188
  }
43864
44189
  // Note: [🟡] Code for CLI harness installation orchestration [$ensureHarnessInstallations](src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts) should never be published outside of `@promptbook/cli`
43865
44190
 
43866
- /**
43867
- * Registers the shared `--no-harness-update` option on a `ptbk coder` command that uses a coding harness.
43868
- *
43869
- * @private internal utility of `promptbookCli`
43870
- */
43871
- function addHarnessUpdateOption(command) {
43872
- command.option('--no-harness-update', 'Skip checking whether the installed coding harnesses are up to date');
43873
- }
43874
- /**
43875
- * Converts the Commander harness-update flag into the normalized harness-check option.
43876
- *
43877
- * @private internal utility of `promptbookCli`
43878
- */
43879
- function normalizeHarnessUpdateCliOptions(cliOptions) {
43880
- return {
43881
- isHarnessUpdateCheckEnabled: cliOptions.harnessUpdate,
43882
- };
43883
- }
43884
- // Note: [🟡] Code for CLI harness update options [harnessUpdateCliOptions](src/cli/cli-commands/common/harnessUpdateCliOptions.ts) should never be published outside of `@promptbook/cli`
43885
- // Note: [💞] Ignore a discrepancy between file name and exported helper names
43886
-
43887
- /**
43888
- * Relative path to the Promptbook Coder quick-reference file initialized in project roots.
43889
- *
43890
- * @private internal utility of `ptbk coder`
43891
- */
43892
- const AGENT_CODING_FILE_PATH = 'AGENT_CODING.md';
43893
- /**
43894
- * Returns the default coder `AGENT_CODING.md` quick-reference content.
43895
- *
43896
- * @private internal utility of `ptbk coder`
43897
- */
43898
- function getDefaultCoderAgentCodingFileContent() {
43899
- return spaceTrim$1(`
43900
- # ✨ Promptbook Coder agent coding
43901
-
43902
- This project is using [Promptbook Coder](https://coder.ptbk.io) or run \`ptbk coder\`!
43903
- `);
43904
- }
43905
- // Note: [🟡] Code for coder AGENT_CODING file boilerplate [agentCodingFile](src/cli/cli-commands/coder/agentCodingFile.ts) should never be published outside of `@promptbook/cli`
43906
- // Note: [💞] Ignore a discrepancy between file name and exported helper names
43907
-
43908
44191
  /**
43909
44192
  * Relative path to the shared coder context file initialized in project roots.
43910
44193
  *
@@ -44006,6 +44289,51 @@ function formatDisplayPath(relativePath) {
44006
44289
  }
44007
44290
  // Note: [🟡] Code for coder path formatting [formatDisplayPath](src/cli/cli-commands/coder/formatDisplayPath.ts) should never be published outside of `@promptbook/cli`
44008
44291
 
44292
+ /**
44293
+ * Creates the artifact definition of one project-owned coder prompt template.
44294
+ */
44295
+ function createCoderPromptTemplateArtifactDefinition(definition) {
44296
+ return {
44297
+ relativeFilePath: definition.relativeFilePath,
44298
+ ensureArtifactFile: (projectPath) => ensureDefaultCoderPromptTemplateFile(projectPath, definition),
44299
+ };
44300
+ }
44301
+ /**
44302
+ * Every artifact which the default coder scripts can reference.
44303
+ */
44304
+ const CODER_REFERENCED_ARTIFACT_DEFINITIONS = [
44305
+ {
44306
+ relativeFilePath: CODER_DEVELOPER_AGENT_FILE_PATH,
44307
+ ensureArtifactFile: ensureCoderDeveloperAgentFile,
44308
+ },
44309
+ {
44310
+ relativeFilePath: AGENTS_FILE_PATH,
44311
+ ensureArtifactFile: (projectPath) => ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent()),
44312
+ },
44313
+ ...getDefaultCoderProjectPromptTemplateDefinitions().map(createCoderPromptTemplateArtifactDefinition),
44314
+ ];
44315
+ /**
44316
+ * Creates only those referenced artifacts which the freshly added coder scripts point at.
44317
+ *
44318
+ * An artifact of a script which the project already defines is intentionally **not** created - the project-owned
44319
+ * script is kept as is, so it does not reference the default artifact in the first place.
44320
+ *
44321
+ * @private function of `initializeCoderProjectConfiguration`
44322
+ */
44323
+ async function ensureCoderReferencedArtifacts(projectPath, referencedArtifactPaths) {
44324
+ const ensuredArtifacts = [];
44325
+ for (const { relativeFilePath, ensureArtifactFile } of CODER_REFERENCED_ARTIFACT_DEFINITIONS) {
44326
+ if (!referencedArtifactPaths.has(relativeFilePath)) {
44327
+ ensuredArtifacts.push({ relativeFilePath, status: 'not-referenced' });
44328
+ continue;
44329
+ }
44330
+ ensuredArtifacts.push({ relativeFilePath, status: await ensureArtifactFile(projectPath) });
44331
+ }
44332
+ return ensuredArtifacts;
44333
+ }
44334
+ // Note: [🟡] Code for coder init referenced artifacts [coderReferencedArtifacts](src/cli/cli-commands/coder/coderReferencedArtifacts.ts) should never be published outside of `@promptbook/cli`
44335
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44336
+
44009
44337
  /**
44010
44338
  * Fallback `.env` content used when no required variables need to be appended.
44011
44339
  */
@@ -44100,26 +44428,79 @@ async function ensureCoderGitignoreRules(projectPath, rules) {
44100
44428
  }
44101
44429
  // Note: [🟡] Code for coder init gitignore bootstrapping [ensureCoderGitignoreFile](src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts) should never be published outside of `@promptbook/cli`
44102
44430
 
44431
+ /**
44432
+ * Name of the npm script which `ptbk coder run` uses to verify the project.
44433
+ */
44434
+ const CODER_TEST_SCRIPT_NAME = 'test-for-ptbk-coder';
44103
44435
  /**
44104
44436
  * Default npm scripts initialized by `ptbk coder init`.
44437
+ *
44438
+ * Note: Using NPX because `ptbk` can be installed globally or locally, and NPX will resolve it correctly in either case.
44105
44439
  */
44106
- const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
44107
- // Note: Using NPX because `ptbk` can be installed globally or locally, and NPX will resolve it correctly in either case.
44108
- 'coder:generate-boilerplates': `npx ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ./prompts/templates/common.md`,
44109
- 'coder:add': 'npx ptbk coder add --template ./prompts/templates/common.md',
44110
- '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',
44111
- // 'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
44112
- 'coder:verify': 'npx ptbk coder verify',
44113
- };
44440
+ const DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS = [
44441
+ {
44442
+ scriptName: 'coder:generate-boilerplates',
44443
+ scriptCommand: `npx ptbk coder generate-boilerplates --count ${DEFAULT_BOILERPLATE_COUNT_OPTION_VALUE} --template ${formatCoderScriptFilePath(COMMON_PROMPT_TEMPLATE_FILE_PATH)}`,
44444
+ referencedArtifactPaths: [COMMON_PROMPT_TEMPLATE_FILE_PATH],
44445
+ },
44446
+ {
44447
+ scriptName: 'coder:add',
44448
+ scriptCommand: `npx ptbk coder add --template ${formatCoderScriptFilePath(COMMON_PROMPT_TEMPLATE_FILE_PATH)}`,
44449
+ referencedArtifactPaths: [COMMON_PROMPT_TEMPLATE_FILE_PATH],
44450
+ },
44451
+ {
44452
+ scriptName: 'coder:run',
44453
+ scriptCommand: [
44454
+ 'npx ptbk coder run --harness openai-codex --model gpt-5.6-terra --thinking-level max',
44455
+ `--agent ${formatDisplayPath(CODER_DEVELOPER_AGENT_FILE_PATH)}`,
44456
+ `--context ${formatDisplayPath(AGENTS_FILE_PATH)}`,
44457
+ `--test "npm run ${CODER_TEST_SCRIPT_NAME}" --test-before yes-and-fix`,
44458
+ ].join(' '),
44459
+ referencedArtifactPaths: [CODER_DEVELOPER_AGENT_FILE_PATH, AGENTS_FILE_PATH],
44460
+ },
44461
+ // { scriptName: 'coder:find-refactor-candidates', scriptCommand: 'npx ptbk coder find-refactor-candidates', referencedArtifactPaths: [] },
44462
+ {
44463
+ scriptName: 'coder:verify',
44464
+ scriptCommand: 'npx ptbk coder verify',
44465
+ referencedArtifactPaths: [],
44466
+ },
44467
+ {
44468
+ // Note: The verification command of `coder:run` is a project-owned script, so every project can decide
44469
+ // what "verified" means without touching the `coder:run` script itself
44470
+ scriptName: CODER_TEST_SCRIPT_NAME,
44471
+ scriptCommand: 'npm test',
44472
+ referencedArtifactPaths: [],
44473
+ },
44474
+ ];
44114
44475
  /**
44115
44476
  * Lists the default npm scripts initialized by `ptbk coder init`.
44116
44477
  *
44117
44478
  * @private internal utility of `coder init` command
44118
44479
  */
44119
44480
  function getDefaultCoderPackageJsonScripts() {
44120
- return DEFAULT_CODER_PACKAGE_JSON_SCRIPTS;
44481
+ return Object.fromEntries(DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS.map(({ scriptName, scriptCommand }) => [
44482
+ scriptName,
44483
+ scriptCommand,
44484
+ ]));
44485
+ }
44486
+ /**
44487
+ * Collects the project-relative artifact paths referenced by the given default coder scripts.
44488
+ *
44489
+ * @private internal utility of `coder init` command
44490
+ */
44491
+ function resolveCoderPackageJsonScriptReferencedArtifactPaths(scriptNames) {
44492
+ return new Set(DEFAULT_CODER_PACKAGE_JSON_SCRIPT_DEFINITIONS.filter(({ scriptName }) => scriptNames.includes(scriptName))
44493
+ .map(({ referencedArtifactPaths }) => referencedArtifactPaths)
44494
+ .flat());
44495
+ }
44496
+ /**
44497
+ * Formats one project-relative path as an explicitly project-rooted path usable inside a shell command.
44498
+ */
44499
+ function formatCoderScriptFilePath(relativeFilePath) {
44500
+ return `./${formatDisplayPath(relativeFilePath)}`;
44121
44501
  }
44122
44502
  // Note: [🟡] Code for coder init package scripts [getDefaultCoderPackageJsonScripts](src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts) should never be published outside of `@promptbook/cli`
44503
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44123
44504
 
44124
44505
  /**
44125
44506
  * Loads the TypeScript runtime used for parsing JSONC-style project files.
@@ -44152,6 +44533,9 @@ const DEFAULT_JSON_FILE_NEWLINE = '\n';
44152
44533
  /**
44153
44534
  * Ensures one JSON object field contains the provided string-record entries.
44154
44535
  *
44536
+ * Entries which the project already defines are **never** overridden - only missing keys are added,
44537
+ * so hand-tuned scripts and settings survive every repeated `ptbk coder init`.
44538
+ *
44155
44539
  * @private function of `initializeCoderProjectConfiguration`
44156
44540
  */
44157
44541
  async function mergeStringRecordJsonFile({ projectPath, relativeFilePath, fieldPath, nextEntries, ensureParentDirectoryPath, }) {
@@ -44163,21 +44547,24 @@ async function mergeStringRecordJsonFile({ projectPath, relativeFilePath, fieldP
44163
44547
  const formatting = detectJsonFileFormatting(fileContent);
44164
44548
  const jsonObject = fileContent === undefined ? {} : await parseJsonObjectFile(relativeFilePath, fileContent);
44165
44549
  const existingEntries = getStringRecordOrDefault(jsonObject[fieldPath], relativeFilePath, fieldPath);
44166
- let hasChanges = fileContent === undefined;
44550
+ const addedEntryKeys = [];
44167
44551
  const mergedEntries = { ...existingEntries };
44168
44552
  for (const [entryKey, entryValue] of Object.entries(nextEntries)) {
44169
- if (mergedEntries[entryKey] !== entryValue) {
44170
- mergedEntries[entryKey] = entryValue;
44171
- hasChanges = true;
44553
+ if (Object.prototype.hasOwnProperty.call(mergedEntries, entryKey)) {
44554
+ // Note: The project already defines this entry, keep its own value untouched
44555
+ continue;
44172
44556
  }
44557
+ mergedEntries[entryKey] = entryValue;
44558
+ addedEntryKeys.push(entryKey);
44173
44559
  }
44560
+ const hasChanges = fileContent === undefined || addedEntryKeys.length > 0;
44174
44561
  if (!hasChanges) {
44175
- return 'unchanged';
44562
+ return { status: 'unchanged', addedEntryKeys };
44176
44563
  }
44177
44564
  const nextJsonObject = { ...jsonObject };
44178
44565
  nextJsonObject[fieldPath] = mergedEntries;
44179
44566
  await writeFile(absoluteFilePath, serializeJsonObject(nextJsonObject, formatting), 'utf-8');
44180
- return fileContent === undefined ? 'created' : 'updated';
44567
+ return { status: fileContent === undefined ? 'created' : 'updated', addedEntryKeys };
44181
44568
  }
44182
44569
  /**
44183
44570
  * Parses one JSON object file while accepting VS Code style comments and trailing commas.
@@ -44266,6 +44653,8 @@ const PACKAGE_JSON_FILE_PATH = 'package.json';
44266
44653
  /**
44267
44654
  * Ensures `package.json` contains the standalone Promptbook coder helper scripts.
44268
44655
  *
44656
+ * Scripts which the project already defines are kept as they are, only missing ones are added.
44657
+ *
44269
44658
  * @private function of `initializeCoderProjectConfiguration`
44270
44659
  */
44271
44660
  async function ensureCoderPackageJsonFile(projectPath) {
@@ -44319,17 +44708,20 @@ const VSCODE_DIRECTORY_PATH = '.vscode';
44319
44708
  /**
44320
44709
  * Ensures VS Code routes pasted prompt images into `prompts/screenshots`.
44321
44710
  *
44711
+ * Settings which the project already defines are kept as they are, only missing ones are added.
44712
+ *
44322
44713
  * @private function of `initializeCoderProjectConfiguration`
44323
44714
  */
44324
44715
  async function ensureCoderVscodeSettingsFile(projectPath) {
44325
44716
  const [fieldPath, nextEntries] = resolveDefaultCoderVscodeSettingsEntry();
44326
- return mergeStringRecordJsonFile({
44717
+ const { status } = await mergeStringRecordJsonFile({
44327
44718
  projectPath,
44328
44719
  relativeFilePath: VSCODE_SETTINGS_FILE_PATH,
44329
44720
  fieldPath,
44330
44721
  nextEntries,
44331
44722
  ensureParentDirectoryPath: VSCODE_DIRECTORY_PATH,
44332
44723
  });
44724
+ return status;
44333
44725
  }
44334
44726
  /**
44335
44727
  * Resolves the default string-record entry that `coder init` merges into VS Code settings.
@@ -44346,34 +44738,32 @@ function resolveDefaultCoderVscodeSettingsEntry() {
44346
44738
  /**
44347
44739
  * Creates or updates all coder configuration artifacts required in the current project.
44348
44740
  *
44741
+ * Nothing the project already owns is ever overwritten - existing scripts, settings and files are kept as they are,
44742
+ * and the artifacts they would reference are created only together with the scripts which actually reference them.
44743
+ *
44349
44744
  * @private internal utility of `coder init` command
44350
44745
  */
44351
44746
  async function initializeCoderProjectConfiguration(projectPath) {
44352
- const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
44747
+ const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH$1);
44353
44748
  const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
44354
44749
  const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
44355
- const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
44356
44750
  const agentsDirectoryStatus = await ensureDirectory(projectPath, CODER_AGENTS_DIRECTORY_PATH);
44357
- const developerAgentFileStatus = await ensureCoderDeveloperAgentFile(projectPath);
44358
- const agentsFileStatus = await ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent());
44359
- const agentCodingFileStatus = await ensureCoderMarkdownFile(projectPath, AGENT_CODING_FILE_PATH, getDefaultCoderAgentCodingFileContent());
44360
44751
  const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
44361
44752
  const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
44362
- const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
44753
+ const { status: packageJsonFileStatus, addedEntryKeys: addedPackageJsonScriptNames } = await ensureCoderPackageJsonFile(projectPath);
44363
44754
  const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
44755
+ const referencedArtifactStatuses = await ensureCoderReferencedArtifacts(projectPath, resolveCoderPackageJsonScriptReferencedArtifactPaths(addedPackageJsonScriptNames));
44364
44756
  return {
44365
44757
  promptsDirectoryStatus,
44366
44758
  promptsDoneDirectoryStatus,
44367
44759
  promptsTemplatesDirectoryStatus,
44368
- promptTemplateFileStatuses,
44369
44760
  agentsDirectoryStatus,
44370
- developerAgentFileStatus,
44371
- agentsFileStatus,
44372
- agentCodingFileStatus,
44373
44761
  envFileStatus,
44374
44762
  gitignoreFileStatus,
44375
44763
  packageJsonFileStatus,
44376
44764
  vscodeSettingsFileStatus,
44765
+ addedPackageJsonScriptNames,
44766
+ referencedArtifactStatuses,
44377
44767
  initializedEnvVariableNames,
44378
44768
  };
44379
44769
  }
@@ -44389,17 +44779,20 @@ function printInitializationSummary(summary) {
44389
44779
  printInitializationStatusLine('prompts/', summary.promptsDirectoryStatus);
44390
44780
  printInitializationStatusLine('prompts/done/', summary.promptsDoneDirectoryStatus);
44391
44781
  printInitializationStatusLine('prompts/templates/', summary.promptsTemplatesDirectoryStatus);
44392
- for (const templateFileStatus of summary.promptTemplateFileStatuses) {
44393
- printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
44394
- }
44395
44782
  printInitializationStatusLine('agents/', summary.agentsDirectoryStatus);
44396
- printInitializationStatusLine(CODER_DEVELOPER_AGENT_FILE_PATH, summary.developerAgentFileStatus);
44397
- printInitializationStatusLine(AGENTS_FILE_PATH, summary.agentsFileStatus);
44398
- printInitializationStatusLine(AGENT_CODING_FILE_PATH, summary.agentCodingFileStatus);
44399
44783
  printInitializationStatusLine('.env', summary.envFileStatus);
44400
44784
  printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
44401
44785
  printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
44402
44786
  printInitializationStatusLine('.vscode/settings.json', summary.vscodeSettingsFileStatus);
44787
+ for (const referencedArtifactStatus of summary.referencedArtifactStatuses) {
44788
+ printInitializationStatusLine(formatDisplayPath(referencedArtifactStatus.relativeFilePath), referencedArtifactStatus.status);
44789
+ }
44790
+ if (summary.addedPackageJsonScriptNames.length > 0) {
44791
+ printInitializationNote(`Added npm scripts: ${summary.addedPackageJsonScriptNames.join(', ')}`, colors.cyan);
44792
+ }
44793
+ else {
44794
+ printInitializationNote('All Promptbook coder npm scripts are already present.', colors.gray);
44795
+ }
44403
44796
  if (summary.initializedEnvVariableNames.length > 0) {
44404
44797
  printInitializationNote(`Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`, colors.cyan);
44405
44798
  }
@@ -44417,6 +44810,9 @@ function formatInitializationStatus(status) {
44417
44810
  if (status === 'updated') {
44418
44811
  return 'updated';
44419
44812
  }
44813
+ if (status === 'not-referenced') {
44814
+ return 'not referenced by the added scripts, kept as is';
44815
+ }
44420
44816
  return 'unchanged';
44421
44817
  }
44422
44818
  /**
@@ -44459,39 +44855,48 @@ function $initializeCoderInitCommand(program) {
44459
44855
  ${block(listDefaultCoderProjectPromptTemplateDisplayPaths())}
44460
44856
  - ${CODER_DEVELOPER_AGENT_FILE_PATH}
44461
44857
  - ${AGENTS_FILE_PATH}
44462
- - ${AGENT_CODING_FILE_PATH}
44463
44858
  - .gitignore with local artifacts from every supported harness
44464
44859
  - package.json
44465
44860
  - .vscode/settings.json
44466
44861
 
44862
+ Never overwrites what the project already owns:
44863
+ - Existing package.json scripts and .vscode/settings.json settings are kept, only missing ones are added
44864
+ - Files referenced by a script, like the agent and context of coder:run, are created only together
44865
+ with the script which references them
44866
+
44467
44867
  Ensures required coding-agent environment variables in .env:
44468
44868
  - CODING_AGENT_GIT_NAME
44469
44869
  - CODING_AGENT_GIT_EMAIL
44470
44870
  - CODING_AGENT_GIT_SIGNING_KEY
44471
44871
 
44472
- Checks that the coding harnesses are installed and up to date unless \`--no-harness-update\` is used:
44872
+ Checks that the coding harnesses are installed and up to date unless \`--no-questions\` is used:
44473
44873
  ${block(listCheckedHarnessLabels())}
44474
44874
 
44475
44875
  ${block(CODER_GIT_SYNC_DESCRIPTION)}
44876
+
44877
+ ${block(QUESTIONS_DESCRIPTION)}
44476
44878
  `));
44477
44879
  addCoderGitSyncOptions(command);
44478
- addHarnessUpdateOption(command);
44880
+ addQuestionsOption(command);
44479
44881
  command.action(handleActionErrors(async (cliOptions) => {
44480
44882
  const gitSync = normalizeCoderGitSyncCliOptions(cliOptions);
44481
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
44883
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
44482
44884
  const projectPath = process.cwd();
44483
44885
  // Note: Import the git synchronization dynamically to keep the CLI fast for runs without `--commit`
44484
44886
  const { $commitCoderChanges, $startCoderGitSync } = await Promise.resolve().then(function () { return coderGitSync; });
44485
44887
  const commitScope = await $startCoderGitSync({ gitSync, projectPath });
44888
+ const isPromptsDirectoryEmpty = await isDirectoryEmpty(projectPath, PROMPTS_DIRECTORY_PATH$1);
44486
44889
  const summary = await initializeCoderProjectConfiguration(projectPath);
44487
44890
  printInitializationSummary(summary);
44488
- await generatePromptBoilerplate({ projectPath, boilerplateCount: DEFAULT_BOILERPLATE_COUNT });
44891
+ if (isPromptsDirectoryEmpty) {
44892
+ await generatePromptBoilerplate({ projectPath, boilerplateCount: DEFAULT_BOILERPLATE_COUNT });
44893
+ }
44489
44894
  await $commitCoderChanges({
44490
44895
  gitSync,
44491
44896
  commitScope,
44492
44897
  commitMessage: 'Initialize Promptbook Coder',
44493
44898
  });
44494
- await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, isHarnessUpdateCheckEnabled);
44899
+ await $ensureHarnessInstallations(CODER_INIT_CHECKED_HARNESS_NAMES, questionsOptions);
44495
44900
  }));
44496
44901
  }
44497
44902
  /**
@@ -44511,6 +44916,112 @@ function listDefaultCoderProjectPromptTemplateDisplayPaths() {
44511
44916
  // Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
44512
44917
  // Note: [💞] Ignore a discrepancy between file name and entity name
44513
44918
 
44919
+ /**
44920
+ * Creates a Commander argument parser that accepts only non-negative integers.
44921
+ *
44922
+ * The returned parser is meant to be passed as the coercion callback of `command.option(...)`.
44923
+ * It throws a branded `NotAllowed` error with a clear message referencing the given option name
44924
+ * when the provided value is not a non-negative integer.
44925
+ *
44926
+ * @private internal utility of `promptbookCli`
44927
+ */
44928
+ function createNonNegativeIntegerOptionParser(optionName) {
44929
+ return (value) => {
44930
+ const parsedValue = Number(value);
44931
+ if (!Number.isInteger(parsedValue) || parsedValue < 0) {
44932
+ throw new NotAllowed(spaceTrim$1(`
44933
+ Invalid value for \`${optionName}\`: \`${value}\`.
44934
+
44935
+ Use a non-negative integer.
44936
+ `));
44937
+ }
44938
+ return parsedValue;
44939
+ };
44940
+ }
44941
+ // Note: [🟡] Code for CLI option parser [createNonNegativeIntegerOptionParser](src/cli/cli-commands/common/createNonNegativeIntegerOptionParser.ts) should never be published outside of `@promptbook/cli`
44942
+
44943
+ /**
44944
+ * Registers the shared prompt-priority filter options on a queue-based `ptbk coder` command.
44945
+ *
44946
+ * @private internal utility of `promptbookCli`
44947
+ */
44948
+ function addPromptPriorityOptions(command) {
44949
+ command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
44950
+ command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
44951
+ command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
44952
+ }
44953
+ // Note: [🟡] Code for CLI prompt priority options [promptPriorityCliOptions](src/cli/cli-commands/common/promptPriorityCliOptions.ts) should never be published outside of `@promptbook/cli`
44954
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44955
+
44956
+ /**
44957
+ * Description shared by coder commands which accept an agent Book.
44958
+ *
44959
+ * @private internal utility of `ptbk coder`
44960
+ */
44961
+ const CODER_AGENT_OPTION_DESCRIPTION = 'Path to a .book file used to personalize coding prompts and select agent-specific tasks';
44962
+ /**
44963
+ * Registers the shared `--agent` option for coder commands.
44964
+ *
44965
+ * @private internal utility of `ptbk coder`
44966
+ */
44967
+ function addCoderAgentOption(command) {
44968
+ command.option('--agent <agent-book-path>', CODER_AGENT_OPTION_DESCRIPTION);
44969
+ }
44970
+ // Note: [🟡] Code for CLI coder agent options should never be published outside of `@promptbook/cli`
44971
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
44972
+
44973
+ /**
44974
+ * Initializes `coder list` command for Promptbook CLI utilities.
44975
+ *
44976
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI.
44977
+ *
44978
+ * @private internal function of `promptbookCli`
44979
+ */
44980
+ function $initializeCoderListCommand(program) {
44981
+ const command = program.command('list');
44982
+ command.description(spaceTrim$1(`
44983
+ List ready coding prompts by priority without executing them
44984
+
44985
+ ${PROMPT_RUNNER_DESCRIPTION}
44986
+
44987
+ Features:
44988
+ - Lists only ready, fully authored prompts
44989
+ - Groups prompts from highest to lowest priority
44990
+ - Optional --harness, --model and --agent filters show only prompts compatible with that selection
44991
+ - Does not start a coding harness or modify prompt files
44992
+ `));
44993
+ addPromptRunnerSelectionOptions(command);
44994
+ addCoderAgentOption(command);
44995
+ addPromptPriorityOptions(command);
44996
+ command.action(handleActionErrors(async (cliOptions) => {
44997
+ const { agent, priority, minPriority: minimumPriority, maxPriority: maximumPriority, } = cliOptions;
44998
+ const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: false });
44999
+ const resolvedCoderAgentBook = agent === undefined
45000
+ ? undefined
45001
+ : await (await Promise.resolve().then(function () { return resolveCoderAgent$1; }))
45002
+ .resolveCoderAgentBook(agent, process.cwd());
45003
+ const promptRunnerIdentity = runnerOptions.agentName === undefined &&
45004
+ runnerOptions.model === undefined &&
45005
+ resolvedCoderAgentBook === undefined
45006
+ ? undefined
45007
+ : {
45008
+ harnessName: runnerOptions.agentName,
45009
+ modelName: runnerOptions.model,
45010
+ agentReferences: resolvedCoderAgentBook === null || resolvedCoderAgentBook === void 0 ? void 0 : resolvedCoderAgentBook.agentReferences,
45011
+ };
45012
+ // Note: Import dynamically to avoid loading prompt parsing dependencies until this command is used.
45013
+ const { listCoderPrompts } = await Promise.resolve().then(function () { return listCoderPrompts$1; });
45014
+ await listCoderPrompts({
45015
+ priority,
45016
+ minimumPriority,
45017
+ maximumPriority,
45018
+ promptRunnerIdentity,
45019
+ });
45020
+ }));
45021
+ }
45022
+ // Note: [🟡] Code for CLI command [list](src/cli/cli-commands/coder/list.ts) should never be published outside of `@promptbook/cli`
45023
+ // Note: [💞] Ignore a discrepancy between file name and entity name
45024
+
44514
45025
  /**
44515
45026
  * Default wait duration applied before retrying a prompt round after an error (10 minutes).
44516
45027
  *
@@ -44564,7 +45075,7 @@ function parseOptionalPeriodDuration(optionName, value) {
44564
45075
  *
44565
45076
  * @private internal utility of `ptbk coder`
44566
45077
  */
44567
- async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName) {
45078
+ async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName, questionsOptions) {
44568
45079
  if (harnessName === undefined) {
44569
45080
  return;
44570
45081
  }
@@ -44575,7 +45086,7 @@ async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName) {
44575
45086
  const { label } = getHarnessDefinition(harnessName);
44576
45087
  const formattedRules = formatGitignoreRules(missingRules);
44577
45088
  const entryLabel = missingRules.length === 1 ? 'entry' : 'entries';
44578
- const isAdditionApproved = await $askForConfirmation(`Add the missing ${label} ignore ${entryLabel} ${formattedRules} to \`.gitignore\` now?`);
45089
+ const isAdditionApproved = await $askForConfirmation(`Add the missing ${label} ignore ${entryLabel} ${formattedRules} to \`.gitignore\` now?`, questionsOptions);
44579
45090
  if (!isAdditionApproved) {
44580
45091
  console.info(colors.gray(`Skipped, add ${formattedRules} to \`.gitignore\` manually.`));
44581
45092
  return;
@@ -44600,22 +45111,24 @@ function formatGitignoreRules(rules) {
44600
45111
  */
44601
45112
  function $initializeCoderPingCommand(program) {
44602
45113
  const command = program.command('ping');
44603
- command.description(spaceTrim$1(`
44604
- Send one tiny dummy prompt to a harness and model to measure and warm them up
45114
+ command.description(spaceTrim$1((block) => `
45115
+ Send one tiny dummy prompt to a harness and model to measure and warm them up
44605
45116
 
44606
- ${PROMPT_RUNNER_DESCRIPTION}
45117
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
44607
45118
 
44608
- Features:
44609
- - Verifies that the selected harness, model, thinking level and authentication really work
44610
- - Reports the answer of the harness, the response time and the reported usage
44611
- - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
44612
- - Optional --period keeps the quota window refreshing by pinging once per period until stopped
44613
- - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
44614
- - Checks that the selected harness is installed and up to date unless --no-harness-update is used
44615
- - Use --no-ui to stream the raw harness output instead of only the compact result
44616
- `));
45119
+ ${block(QUESTIONS_DESCRIPTION)}
45120
+
45121
+ Features:
45122
+ - Verifies that the selected harness, model, thinking level and authentication really work
45123
+ - Reports the answer of the harness, the response time and the reported usage
45124
+ - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
45125
+ - Optional --period keeps the quota window refreshing by pinging once per period until stopped
45126
+ - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
45127
+ - Checks that the selected harness is installed and up to date unless --no-questions is used
45128
+ - Use --no-ui to stream the raw harness output instead of only the compact result
45129
+ `));
44617
45130
  addPromptRunnerSelectionOptions(command);
44618
- addHarnessUpdateOption(command);
45131
+ addQuestionsOption(command);
44619
45132
  addPromptRunnerRuntimeOptions(command);
44620
45133
  command.option('--period <duration>', spaceTrim$1(`
44621
45134
  Keep pinging once per period instead of pinging only once.
@@ -44624,11 +45137,11 @@ function $initializeCoderPingCommand(program) {
44624
45137
  command.action(handleActionErrors(async (cliOptions) => {
44625
45138
  const { period: periodValue } = cliOptions;
44626
45139
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: true });
44627
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45140
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
44628
45141
  // Note: The period is validated before the harness installation check, so a mistyped duration fails fast
44629
45142
  const periodMs = parseOptionalPeriodDuration('--period', periodValue);
44630
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
44631
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45143
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45144
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
44632
45145
  const pingOptions = {
44633
45146
  agentName: runnerOptions.agentName,
44634
45147
  model: runnerOptions.model,
@@ -44652,28 +45165,157 @@ function $initializeCoderPingCommand(program) {
44652
45165
  // Note: [💞] Ignore a discrepancy between file name and entity name
44653
45166
 
44654
45167
  /**
44655
- * Creates a Commander argument parser that accepts only non-negative integers.
45168
+ * Free disk space below which `ptbk coder` refuses to start a new run.
44656
45169
  *
44657
- * The returned parser is meant to be passed as the coercion callback of `command.option(...)`.
44658
- * It throws a branded `NotAllowed` error with a clear message referencing the given option name
44659
- * when the provided value is not a non-negative integer.
45170
+ * A coding round writes prompt and log artifacts, lets the harness rewrite the project, may install npm packages,
45171
+ * may create a git worktree for `--isolate` and usually runs a build, so a run started with less than this
45172
+ * would very likely die somewhere in the middle with a filesystem error.
44660
45173
  *
44661
45174
  * @private internal utility of `promptbookCli`
44662
45175
  */
44663
- function createNonNegativeIntegerOptionParser(optionName) {
44664
- return (value) => {
44665
- const parsedValue = Number(value);
44666
- if (!Number.isInteger(parsedValue) || parsedValue < 0) {
44667
- throw new NotAllowed(spaceTrim$1(`
44668
- Invalid value for \`${optionName}\`: \`${value}\`.
45176
+ const LOW_FREE_DISK_SPACE_BYTES = 2 * 1024 * 1024 * 1024;
45177
+ /**
45178
+ * Free disk space below which a started `ptbk coder` run stops at its next checkpoint and waits until space is freed.
45179
+ *
45180
+ * This limit is lower than `LOW_FREE_DISK_SPACE_BYTES`, because interrupting a run which is already in progress
45181
+ * is more expensive than not starting one at all.
45182
+ *
45183
+ * @private internal utility of `promptbookCli`
45184
+ */
45185
+ const CRITICALLY_LOW_FREE_DISK_SPACE_BYTES = 1 * 1024 * 1024 * 1024;
45186
+ /**
45187
+ * How long one free disk space measurement is reused before the filesystem is measured again during a run.
45188
+ *
45189
+ * Pause checkpoints are passed very often - for example on every tick of a timed wait - so without this
45190
+ * interval the filesystem would be measured many times per second.
45191
+ *
45192
+ * @private internal utility of `promptbookCli`
45193
+ */
45194
+ const FREE_DISK_SPACE_RECHECK_INTERVAL_MS = 60 * 1000;
45195
+ // 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`
45196
+ // Note: [💞] Ignore a discrepancy between file name and entity name
44669
45197
 
44670
- Use a non-negative integer.
44671
- `));
45198
+ /**
45199
+ * Classifies one measured amount of free disk space against the `ptbk coder` free disk space limits.
45200
+ *
45201
+ * @private internal utility of `promptbookCli`
45202
+ */
45203
+ function resolveFreeDiskSpaceLevel(availableBytes) {
45204
+ if (availableBytes < CRITICALLY_LOW_FREE_DISK_SPACE_BYTES) {
45205
+ return 'critical';
45206
+ }
45207
+ if (availableBytes < LOW_FREE_DISK_SPACE_BYTES) {
45208
+ return 'low';
45209
+ }
45210
+ return 'sufficient';
45211
+ }
45212
+ // 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`
45213
+
45214
+ /**
45215
+ * Measures the free disk space on the filesystem which hosts the given path.
45216
+ *
45217
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the state of the filesystem
45218
+ *
45219
+ * @returns the measurement, or `null` when the filesystem can not be measured, for example on a platform which
45220
+ * does not report filesystem statistics - a run is never stopped by a measurement which could not be taken
45221
+ * @private internal utility of `promptbookCli`
45222
+ */
45223
+ async function $readFreeDiskSpaceStatus(inspectedPath) {
45224
+ try {
45225
+ const fileSystemStats = await statfs(inspectedPath);
45226
+ const blockSizeBytes = fileSystemStats.bsize;
45227
+ const availableBytes = fileSystemStats.bavail * blockSizeBytes;
45228
+ const totalBytes = fileSystemStats.blocks * blockSizeBytes;
45229
+ if (!Number.isFinite(availableBytes) || availableBytes < 0) {
45230
+ return null;
44672
45231
  }
44673
- return parsedValue;
44674
- };
45232
+ return {
45233
+ inspectedPath,
45234
+ availableBytes,
45235
+ totalBytes,
45236
+ level: resolveFreeDiskSpaceLevel(availableBytes),
45237
+ };
45238
+ }
45239
+ catch (_a) {
45240
+ // Note: A path or a platform which can not report its free disk space must not stop `ptbk coder` from running
45241
+ return null;
45242
+ }
44675
45243
  }
44676
- // Note: [🟡] Code for CLI option parser [createNonNegativeIntegerOptionParser](src/cli/cli-commands/common/createNonNegativeIntegerOptionParser.ts) should never be published outside of `@promptbook/cli`
45244
+ // 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`
45245
+
45246
+ /**
45247
+ * Byte units used when one amount of disk space is written for a human.
45248
+ */
45249
+ const FREE_DISK_SPACE_BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
45250
+ /**
45251
+ * Writes one byte count as a compact human-readable amount of disk space, for example `812 MB` or `1.5 GB`.
45252
+ *
45253
+ * @private internal utility of `promptbookCli`
45254
+ */
45255
+ function formatFreeDiskSpaceBytes(bytes) {
45256
+ if (!Number.isFinite(bytes) || bytes <= 0) {
45257
+ return '0 B';
45258
+ }
45259
+ const unitIndex = Math.min(FREE_DISK_SPACE_BYTE_UNITS.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
45260
+ const value = bytes / Math.pow(1024, unitIndex);
45261
+ // Note: One decimal place only matters for small values, `476 GB` is more readable than `476.3 GB`
45262
+ const decimalPlaceCount = value >= 10 || unitIndex === 0 ? 0 : 1;
45263
+ // Note: An empty decimal place only adds noise, `2 GB` is more readable than `2.0 GB`
45264
+ const displayedValue = value.toFixed(decimalPlaceCount).replace(/\.0$/, '');
45265
+ return `${displayedValue} ${FREE_DISK_SPACE_BYTE_UNITS[unitIndex]}`;
45266
+ }
45267
+ // 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`
45268
+
45269
+ /**
45270
+ * Writes the shared warning which explains one low free disk space measurement and how to get out of it.
45271
+ *
45272
+ * The very same wording is used by the check which refuses to start a run and by the check which pauses
45273
+ * a run that ran out of disk space while it was working.
45274
+ *
45275
+ * @private internal utility of `promptbookCli`
45276
+ */
45277
+ function formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus) {
45278
+ const { inspectedPath } = freeDiskSpaceStatus;
45279
+ const availableSpace = formatFreeDiskSpaceBytes(freeDiskSpaceStatus.availableBytes);
45280
+ const totalSpace = formatFreeDiskSpaceBytes(freeDiskSpaceStatus.totalBytes);
45281
+ const requiredSpaceToStart = formatFreeDiskSpaceBytes(LOW_FREE_DISK_SPACE_BYTES);
45282
+ const requiredSpaceToContinue = formatFreeDiskSpaceBytes(CRITICALLY_LOW_FREE_DISK_SPACE_BYTES);
45283
+ return spaceTrim$1(`
45284
+ Only **${availableSpace}** of ${totalSpace} is left on the disk which hosts \`${inspectedPath}\`.
45285
+
45286
+ **\`ptbk coder\` needs at least ${requiredSpaceToStart} of free disk space to start a run and at least ${requiredSpaceToContinue} to keep one running.**
45287
+
45288
+ Actionable hints:
45289
+ - Free up disk space on the drive which hosts \`${inspectedPath}\`.
45290
+ - Remove build artifacts, package caches and \`node_modules\` folders of projects you are not working on.
45291
+ - Remove the temporary worktrees which earlier \`--isolate\` runs left behind.
45292
+ `);
45293
+ }
45294
+ // 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`
45295
+
45296
+ /**
45297
+ * Refuses to start one `ptbk coder` command when the disk which hosts the project is nearly full.
45298
+ *
45299
+ * A run which starts without enough free disk space fails somewhere in the middle, after the coding agent has
45300
+ * already rewritten a part of the project, so it is stopped before it writes anything at all.
45301
+ *
45302
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the state of the filesystem
45303
+ *
45304
+ * @throws {LimitReachedError} when there is not enough free disk space left to start
45305
+ * @private internal utility of `promptbookCli`
45306
+ */
45307
+ async function $assertSufficientFreeDiskSpace(inspectedPath) {
45308
+ const freeDiskSpaceStatus = await $readFreeDiskSpaceStatus(inspectedPath);
45309
+ if (freeDiskSpaceStatus === null || freeDiskSpaceStatus.level === 'sufficient') {
45310
+ return;
45311
+ }
45312
+ throw new LimitReachedError(spaceTrim$1((block) => `
45313
+ There is not enough free disk space to run \`ptbk coder\`.
45314
+
45315
+ ${block(formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus))}
45316
+ `));
45317
+ }
45318
+ // 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`
44677
45319
 
44678
45320
  /**
44679
45321
  * Npm packages which directly provide the `ptbk` executable.
@@ -45003,7 +45645,11 @@ function formatPromptbookCliInstallationLocation(installationLocation) {
45003
45645
  * @returns `true` when at least one installation was updated and the current coder run should stop
45004
45646
  * @private internal utility of `promptbookCli`
45005
45647
  */
45006
- async function $ensurePromptbookCliInstallations() {
45648
+ async function $ensurePromptbookCliInstallations(questionsOptions) {
45649
+ if (!questionsOptions.isAskingQuestionsEnabled) {
45650
+ // Note: `--no-questions` forbids asking for the approval, so the registry checks would be useless
45651
+ return false;
45652
+ }
45007
45653
  if (!process.stdin.isTTY) {
45008
45654
  // Note: Non-interactive runs cannot approve an update and should not wait for registry checks
45009
45655
  return false;
@@ -45015,7 +45661,7 @@ async function $ensurePromptbookCliInstallations() {
45015
45661
  return false;
45016
45662
  }
45017
45663
  console.warn(colors.yellow(formatPromptbookCliInstallationWarning(outdatedStatuses)));
45018
- const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?');
45664
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?', questionsOptions);
45019
45665
  if (!isUpdateApproved) {
45020
45666
  reportManualPromptbookCliUpdateCommands(outdatedStatuses);
45021
45667
  return false;
@@ -45082,6 +45728,17 @@ function isTestBeforeMode(value) {
45082
45728
  return TEST_BEFORE_MODE_VALUES.includes(value);
45083
45729
  }
45084
45730
 
45731
+ /**
45732
+ * Prints why a coder run has ended to the terminal.
45733
+ *
45734
+ * @private internal utility of `coder run` and `coder server` commands
45735
+ */
45736
+ function printCoderRunFailure(error) {
45737
+ console.error(colors.bgRed(`${error.name}`));
45738
+ console.error(colors.red(formatUnknownErrorDetails(error)));
45739
+ }
45740
+ // Note: [🟡] Code for coder failure reporting [printCoderRunFailure](src/cli/cli-commands/coder/printCoderRunFailure.ts) should never be published outside of `@promptbook/cli`
45741
+
45085
45742
  /**
45086
45743
  * Initializes `coder run` command for Promptbook CLI utilities
45087
45744
  *
@@ -45091,10 +45748,12 @@ function isTestBeforeMode(value) {
45091
45748
  */
45092
45749
  function $initializeCoderRunCommand(program) {
45093
45750
  const command = program.command('run');
45094
- command.description(spaceTrim$1(`
45751
+ command.description(spaceTrim$1((block) => `
45095
45752
  Execute coding prompts through selected AI agent
45096
45753
 
45097
- ${PROMPT_RUNNER_DESCRIPTION}
45754
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45755
+
45756
+ ${block(QUESTIONS_DESCRIPTION)}
45098
45757
 
45099
45758
  Features:
45100
45759
  - Automatically stages and commits changes with agent identity unless --no-commit is used
@@ -45104,9 +45763,11 @@ function $initializeCoderRunCommand(program) {
45104
45763
  - Optional post-commit git push with explicit --auto-push opt-in
45105
45764
  - Optional pre-prompt git pull with explicit --auto-pull opt-in
45106
45765
  - Optional --isolate runs every prompt in its own temporary git worktree and merges it back when verified
45766
+ - Saves the run trace of every round into prompts/traces/, named after its prompt file
45107
45767
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
45108
45768
  - Optional --no-ui keeps plain streaming console output for logging and debugging
45109
- - Checks that the selected harness is installed and up to date before the first prompt unless --no-harness-update is used
45769
+ - Refuses to start on a nearly full disk and pauses the run when the free disk space becomes critical, unless --no-questions is used
45770
+ - Checks that the selected harness is installed and up to date before the first prompt unless --no-questions is used
45110
45771
  - Offers to add missing project-local ignore rules for the selected harness
45111
45772
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
45112
45773
  - Supports GPG signing of commits
@@ -45117,8 +45778,8 @@ function $initializeCoderRunCommand(program) {
45117
45778
  `));
45118
45779
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45119
45780
  addPromptRunnerSelectionOptions(command);
45120
- addHarnessUpdateOption(command);
45121
- command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
45781
+ addQuestionsOption(command);
45782
+ addCoderAgentOption(command);
45122
45783
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45123
45784
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45124
45785
  command.addOption(new Option('--test-before <mode>', `Run tests before coding: ${TEST_BEFORE_MODE_VALUES.join(', ')} (defaults to no; uses npm test when --test is omitted)`)
@@ -45131,9 +45792,7 @@ function $initializeCoderRunCommand(program) {
45131
45792
  A verified task is merged back into the branch the coder runs on and the worktree is deleted.
45132
45793
  A task that cannot be merged is marked as failed and its worktree is kept for a manual merge.
45133
45794
  `), false);
45134
- command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
45135
- command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
45136
- command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
45795
+ addPromptPriorityOptions(command);
45137
45796
  command.option('--limit <run-count>', 'Stop after processing this many prompt runs', createPositiveIntegerOptionParser('--limit'));
45138
45797
  command.option('--wait-after-prompt <duration>', spaceTrim$1(`
45139
45798
  Wait this long after each prompt has been implemented, verified and committed before starting the next prompt.
@@ -45159,7 +45818,7 @@ function $initializeCoderRunCommand(program) {
45159
45818
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45160
45819
  isAgentRequired: !dryRun,
45161
45820
  });
45162
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45821
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45163
45822
  // [1] Parse the wait options and --no-auto:
45164
45823
  // default: run automatically through the queue (no waiting between prompts)
45165
45824
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -45167,11 +45826,15 @@ function $initializeCoderRunCommand(program) {
45167
45826
  // --wait-between-prompts: pace from start of one prompt to start of next
45168
45827
  // --wait-after-error: wait before retrying after an error (default 10m)
45169
45828
  const waitForUser = !auto;
45170
- if (await $ensurePromptbookCliInstallations()) {
45829
+ assertUserConfirmationIsAllowed({ ...questionsOptions, isWaitingForUser: waitForUser });
45830
+ // Note: Checked before anything is installed or written, because installing a harness, updating the
45831
+ // Promptbook CLI and coding itself all need the disk space which is missing
45832
+ await $assertSufficientFreeDiskSpace(process.cwd());
45833
+ if (await $ensurePromptbookCliInstallations(questionsOptions)) {
45171
45834
  return process.exit(0);
45172
45835
  }
45173
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45174
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45836
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45837
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
45175
45838
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45176
45839
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45177
45840
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45204,6 +45867,7 @@ function $initializeCoderRunCommand(program) {
45204
45867
  allowDestructiveAutoMigrate,
45205
45868
  autoPush: runnerOptions.autoPush,
45206
45869
  autoPull: runnerOptions.autoPull,
45870
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45207
45871
  };
45208
45872
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
45209
45873
  const { runCodexPrompts } = await Promise.resolve().then(function () { return runCodexPrompts$1; });
@@ -45213,8 +45877,7 @@ function $initializeCoderRunCommand(program) {
45213
45877
  }
45214
45878
  catch (error) {
45215
45879
  assertsError(error);
45216
- console.error(colors.bgRed(`${error.name}`));
45217
- console.error(colors.red(error.stack || error.message));
45880
+ printCoderRunFailure(error);
45218
45881
  return process.exit(1);
45219
45882
  }
45220
45883
  return process.exit(0);
@@ -45258,35 +45921,36 @@ const DEFAULT_CODER_SERVER_PORT = '4441';
45258
45921
  */
45259
45922
  function $initializeCoderServerCommand(program) {
45260
45923
  const command = program.command('server');
45261
- command.description(spaceTrim$1(`
45262
- Start a coder server that watches for prompts and serves a kanban web UI
45924
+ command.description(spaceTrim$1((block) => `
45925
+ Start a coder server that watches for prompts and serves a kanban web UI
45263
45926
 
45264
- ${PROMPT_RUNNER_DESCRIPTION}
45927
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45265
45928
 
45266
- Features:
45267
- - Runs the same prompt processing as \`ptbk coder run\`
45268
- - Checks that the selected harness is installed and up to date on startup unless --no-harness-update is used
45269
- - Offers to add missing project-local ignore rules for the selected harness
45270
- - Does not exit when all prompts are done; polls for new prompt files instead
45271
- - Serves a kanban board at http://localhost:<port> for visual progress tracking
45272
- - Allows editing prompt files directly from the browser (Trello-style)
45273
- - Play / pause button in the browser stays in sync with the CLI pause state
45274
- - Terminal controls match \`ptbk coder run\`: P pause/resume, S skip waiting, X end after current prompt
45275
- `));
45929
+ ${block(QUESTIONS_DESCRIPTION)}
45930
+
45931
+ Features:
45932
+ - Runs the same prompt processing as \`ptbk coder run\`
45933
+ - Refuses to start on a nearly full disk and pauses the run when the free disk space becomes critical, unless --no-questions is used
45934
+ - Checks that the selected harness is installed and up to date on startup unless --no-questions is used
45935
+ - Offers to add missing project-local ignore rules for the selected harness
45936
+ - Does not exit when all prompts are done; polls for new prompt files instead
45937
+ - Serves a kanban board at http://localhost:<port> for visual progress tracking
45938
+ - Allows editing prompt files directly from the browser (Trello-style)
45939
+ - Play / pause button in the browser stays in sync with the CLI pause state
45940
+ - Terminal controls match \`ptbk coder run\`: P pause/resume, S skip waiting, X end after current prompt
45941
+ `));
45276
45942
  command.addOption(new Option('--port <port>', 'Port to start the coder server on')
45277
45943
  .env('PTBK_CODER_SERVER_PORT')
45278
45944
  .default(DEFAULT_CODER_SERVER_PORT));
45279
45945
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45280
45946
  addPromptRunnerSelectionOptions(command);
45281
- addHarnessUpdateOption(command);
45282
- command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
45947
+ addQuestionsOption(command);
45948
+ addCoderAgentOption(command);
45283
45949
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45284
45950
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45285
45951
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
45286
45952
  addPromptRunnerExecutionOptions(command);
45287
- command.option('--priority <minimum-priority>', 'Alias for --min-priority; filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--priority'));
45288
- command.option('--min-priority <minimum-priority>', 'Filter prompts by minimum priority level', createNonNegativeIntegerOptionParser('--min-priority'));
45289
- command.option('--max-priority <maximum-priority>', 'Filter prompts by maximum priority level', createNonNegativeIntegerOptionParser('--max-priority'));
45953
+ addPromptPriorityOptions(command);
45290
45954
  command.option('--wait-after-prompt <duration>', spaceTrim$1(`
45291
45955
  Wait this long after each prompt has been implemented, verified and committed before starting the next prompt.
45292
45956
  Accepts durations like 1h, 30m, 5s. Defaults to 0 (no wait).
@@ -45310,11 +45974,15 @@ function $initializeCoderServerCommand(program) {
45310
45974
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45311
45975
  isAgentRequired: !dryRun,
45312
45976
  });
45313
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45314
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45315
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45977
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45316
45978
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
45317
45979
  const waitForUser = !auto;
45980
+ assertUserConfirmationIsAllowed({ ...questionsOptions, isWaitingForUser: waitForUser });
45981
+ // Note: Checked before anything is installed or written, because installing a harness and coding
45982
+ // itself both need the disk space which is missing
45983
+ await $assertSufficientFreeDiskSpace(process.cwd());
45984
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45985
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
45318
45986
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45319
45987
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45320
45988
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45344,6 +46012,7 @@ function $initializeCoderServerCommand(program) {
45344
46012
  allowDestructiveAutoMigrate,
45345
46013
  autoPush: runnerOptions.autoPush,
45346
46014
  autoPull: runnerOptions.autoPull,
46015
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45347
46016
  };
45348
46017
  // Note: Import dynamically to avoid loading heavy dependencies until needed
45349
46018
  const { runCodexPromptsServer } = await Promise.resolve().then(function () { return runCodexPromptsServer$1; });
@@ -45352,8 +46021,7 @@ function $initializeCoderServerCommand(program) {
45352
46021
  }
45353
46022
  catch (error) {
45354
46023
  assertsError(error);
45355
- console.error(colors.bgRed(`${error.name}`));
45356
- console.error(colors.red(error.stack || error.message));
46024
+ printCoderRunFailure(error);
45357
46025
  return process.exit(1);
45358
46026
  }
45359
46027
  return process.exit(0);
@@ -45501,6 +46169,7 @@ function collectStringOption(value, previousValues) {
45501
46169
  * - add: Add one ready-to-run prompt file to the queue
45502
46170
  * - generate-boilerplates: Generate prompt boilerplate files
45503
46171
  * - find-refactor-candidates: Find files that need refactoring
46172
+ * - list: List ready prompts in priority order without running them
45504
46173
  * - run: Run coding prompts with AI agents
45505
46174
  * - ping: Test one harness and model with a tiny dummy prompt
45506
46175
  * - verify: Verify completed prompts
@@ -45521,6 +46190,7 @@ function $initializeCoderCommand(program) {
45521
46190
  - generate-boilerplates: Generate prompt boilerplate files
45522
46191
  - find-refactor-candidates: Find files that need refactoring
45523
46192
  - find-unwritten: List prompt sections that still need to be authored
46193
+ - list: List ready prompts in priority order without running them
45524
46194
  - run: Run coding prompts with AI agents
45525
46195
  - ping: Test the connection, response time and quota of one harness and model
45526
46196
  - server: Start a long-running coder server with a kanban web UI
@@ -45533,17 +46203,14 @@ function $initializeCoderCommand(program) {
45533
46203
  $initializeCoderGenerateBoilerplatesCommand(coderCommand);
45534
46204
  $initializeCoderFindRefactorCandidatesCommand(coderCommand);
45535
46205
  $initializeCoderFindUnwrittenCommand(coderCommand);
46206
+ $initializeCoderListCommand(coderCommand);
45536
46207
  $initializeCoderRunCommand(coderCommand);
45537
46208
  $initializeCoderPingCommand(coderCommand);
45538
46209
  $initializeCoderServerCommand(coderCommand);
45539
46210
  $initializeCoderVerifyCommand(coderCommand);
45540
46211
  $initializeCoderFindFreshEmojiTagCommand(coderCommand);
45541
46212
  // If no subcommand is provided, show help
45542
- coderCommand.action(() => {
45543
- console.info(colors.yellow('Please specify a subcommand.'));
45544
- console.info('');
45545
- coderCommand.help();
45546
- });
46213
+ $requireCliSubcommand(coderCommand);
45547
46214
  }
45548
46215
  // Note: [🟡] Code for CLI command [coder](src/cli/cli-commands/coder.ts) should never be published outside of `@promptbook/cli`
45549
46216
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -56133,7 +56800,9 @@ function shouldRunInteractiveChatbot(options) {
56133
56800
  * @private internal function of `promptbookCli`
56134
56801
  */
56135
56802
  function $initializeRunCommand(program) {
56136
- const runCommand = program.command('run', { isDefault: true });
56803
+ // Note: `run` is deprecated, so it is intentionally NOT the default command of `ptbk`
56804
+ // and it must be asked for explicitly
56805
+ const runCommand = program.command('run');
56137
56806
  configureRunCommand(runCommand);
56138
56807
  runCommand.action(handleActionErrors((pipelineSource, cliOptions) => runCommandAction(pipelineSource, cliOptions)));
56139
56808
  }
@@ -57972,9 +58641,24 @@ function $addGlobalOptionsToCommand(command) {
57972
58641
  command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
57973
58642
  }
57974
58643
 
58644
+ /**
58645
+ * Keeps one already registered CLI command out of the help of its parent command while the command itself stays
58646
+ * fully usable - it can still be run, it still has its own help and it is still reachable through `ptbk help <command>`.
58647
+ *
58648
+ * Note: `$` is used to indicate that this function is not a pure function - it changes how the CLI renders its help
58649
+ *
58650
+ * @private utility of CLI
58651
+ */
58652
+ function $hideCliCommandFromHelp(command) {
58653
+ command._hidden = true;
58654
+ }
58655
+
57975
58656
  /**
57976
58657
  * Marks one CLI command as deprecated while keeping it available for existing callers.
57977
58658
  *
58659
+ * Deprecated commands are not advertised anymore - they are hidden from the list of commands in the help of their
58660
+ * parent command, they warn when they are used and they explain the deprecation in their own help.
58661
+ *
57978
58662
  * @private utility of CLI
57979
58663
  */
57980
58664
  function $deprecateCliCommand(command, deprecationMessage) {
@@ -57986,6 +58670,7 @@ function $deprecateCliCommand(command, deprecationMessage) {
57986
58670
  command.hook('preAction', () => {
57987
58671
  console.warn(colors.yellow(createDeprecatedCliCommandWarning(command, deprecationMessage)));
57988
58672
  });
58673
+ $hideCliCommandFromHelp(command);
57989
58674
  }
57990
58675
  /**
57991
58676
  * Creates the runtime warning printed before a deprecated CLI command action starts.
@@ -58053,10 +58738,17 @@ async function promptbookCli() {
58053
58738
  program.version(PROMPTBOOK_ENGINE_VERSION);
58054
58739
  program.description(CLAIM);
58055
58740
  // Note: These options are valid for all commands
58741
+ // Note: Commands are listed in the help in the order they are registered,
58742
+ // `coder` is the most used one so it goes first and the deprecated ones,
58743
+ // which `$deprecateTopLevelCommands` hides from the help entirely, go last
58744
+ $initializeCoderCommand(program);
58745
+ $initializeAgentCommand(program);
58746
+ $initializeAgentFolderCommand(program);
58747
+ $initializeAgentsServerCommand(program);
58056
58748
  $initializeAboutCommand(program);
58749
+ $initializeHelloCommand(program);
58057
58750
  $initializeRunCommand(program);
58058
58751
  $initializeLoginCommand(program);
58059
- $initializeHelloCommand(program);
58060
58752
  $initializeMakeCommand(program);
58061
58753
  $initializePrettifyCommand(program);
58062
58754
  $initializeTestCommand(program);
@@ -58064,16 +58756,13 @@ async function promptbookCli() {
58064
58756
  $initializeListScrapersCommand(program);
58065
58757
  $initializeStartAgentsServerCommand(program);
58066
58758
  $initializeStartPipelinesServerCommand(program);
58067
- $initializeAgentCommand(program);
58068
- $initializeAgentFolderCommand(program);
58069
- $initializeAgentsServerCommand(program);
58070
- $initializeCoderCommand(program);
58071
58759
  $deprecateTopLevelCommands(program);
58072
58760
  // TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
58073
58761
  program.commands.forEach($addGlobalOptionsToCommand);
58762
+ // Note: There is no default command, so `ptbk` without a subcommand asks for one
58763
+ // instead of running the deprecated `ptbk run`
58074
58764
  if (commandLineArguments.length === 0) {
58075
- program.outputHelp();
58076
- return process.exit(0);
58765
+ return $reportMissingCliSubcommand(program);
58077
58766
  }
58078
58767
  program.parse(process.argv);
58079
58768
  }
@@ -58087,7 +58776,7 @@ function isTopLevelVersionRequested(commandLineArguments) {
58087
58776
  return firstCommandLineArgument !== undefined && VERSION_OPTION_ARGUMENTS.has(firstCommandLineArgument);
58088
58777
  }
58089
58778
  /**
58090
- * Adds one deprecation notice to each configured top-level legacy command.
58779
+ * Marks each configured top-level legacy command as deprecated, which keeps it usable but takes it out of the help.
58091
58780
  */
58092
58781
  function $deprecateTopLevelCommands(program) {
58093
58782
  for (const command of program.commands) {
@@ -58098,7 +58787,6 @@ function $deprecateTopLevelCommands(program) {
58098
58787
  }
58099
58788
  }
58100
58789
  // Note: [🟡] Code for CLI program [promptbookCli](src/cli/promptbookCli.ts) should never be published outside of `@promptbook/cli`
58101
- // TODO: [🧠] Maybe `run` command the default, instead of `ptbk run ./foo.book` -> `ptbk ./foo.book`
58102
58790
  // TODO: [🥠] Do not export, its just for CLI script
58103
58791
  // TODO: [🕌] When more functionalities, rename
58104
58792
  // Note: 11:11
@@ -59210,7 +59898,9 @@ const VERCEL_PROVIDER_PROFILE = {
59210
59898
  */
59211
59899
  function createExecutionToolsFromVercelProvider(options) {
59212
59900
  let { title, description } = options;
59213
- const { vercelProvider, availableModels, userId, additionalChatSettings = {} } = options;
59901
+ const { vercelProvider, availableModels, additionalChatSettings = {} } = options;
59902
+ // <- Note: `userId` is not forwarded, because Vercel AI SDK moved provider-specific settings like `user` from
59903
+ // `vercelProvider.chat(modelName, settings)` into `providerOptions` which are keyed by the provider
59214
59904
  if (!/Vercel/i.test(title)) {
59215
59905
  title = `${title} (through Vercel)`;
59216
59906
  // <- TODO: [🧈] Maybe standartize the suffix
@@ -59251,10 +59941,7 @@ function createExecutionToolsFromVercelProvider(options) {
59251
59941
 
59252
59942
  `));
59253
59943
  }
59254
- const model = await vercelProvider.chat(modelName, {
59255
- user: (userId === null || userId === void 0 ? void 0 : userId.toString()) || undefined,
59256
- ...additionalChatSettings,
59257
- });
59944
+ const model = await vercelProvider.chat(modelName);
59258
59945
  const rawPromptContent = templateParameters(content, { ...parameters, modelName });
59259
59946
  // Support for passing a chat thread (multi-message conversation)
59260
59947
  let promptMessages;
@@ -59292,14 +59979,9 @@ function createExecutionToolsFromVercelProvider(options) {
59292
59979
  }
59293
59980
  const rawRequest = {
59294
59981
  // <- TODO: [☂]
59295
- inputFormat: 'messages',
59296
- mode: {
59297
- type: 'regular',
59298
- tools: [
59299
- /* <- TODO: Pass the tools */
59300
- ],
59301
- },
59302
59982
  prompt: promptMessages,
59983
+ // <- TODO: Pass the tools via `tools`
59984
+ ...additionalChatSettings,
59303
59985
  };
59304
59986
  const start = $getCurrentDate();
59305
59987
  if (options.isVerbose) {
@@ -59316,28 +59998,31 @@ function createExecutionToolsFromVercelProvider(options) {
59316
59998
  if (options.isVerbose) {
59317
59999
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
59318
60000
  }
59319
- if (rawResponse.text === undefined) {
60001
+ // Note: Vercel AI SDK returns ordered content parts, the chat message is made only of the text parts
60002
+ const responseTextParts = rawResponse.content.flatMap((contentPart) => contentPart.type === 'text' ? [contentPart.text] : []);
60003
+ if (responseTextParts.length === 0) {
59320
60004
  throw new PipelineExecutionError('No response message');
59321
60005
  }
60006
+ const responseText = responseTextParts.join('');
59322
60007
  const complete = $getCurrentDate();
59323
60008
  const duration = uncertainNumber((new Date(complete).getTime() - new Date(start).getTime()) / 1000);
59324
60009
  const usage = {
59325
60010
  price: UNCERTAIN_ZERO_VALUE,
59326
60011
  duration,
59327
60012
  input: {
59328
- tokensCount: uncertainNumber(rawResponse.usage.promptTokens),
60013
+ tokensCount: uncertainNumber(rawResponse.usage.inputTokens),
59329
60014
  ...computeUsageCounts(rawPromptContent),
59330
60015
  },
59331
60016
  output: {
59332
- tokensCount: uncertainNumber(rawResponse.usage.completionTokens),
59333
- ...computeUsageCounts(rawResponse.text),
60017
+ tokensCount: uncertainNumber(rawResponse.usage.outputTokens),
60018
+ ...computeUsageCounts(responseText),
59334
60019
  },
59335
60020
  };
59336
60021
  return exportJson({
59337
60022
  name: 'promptResult',
59338
60023
  message: `Result of \`createExecutionToolsFromVercelProvider.callChatModel\``,
59339
60024
  value: {
59340
- content: rawResponse.text,
60025
+ content: responseText,
59341
60026
  modelName,
59342
60027
  timing: {
59343
60028
  start,
@@ -74642,13 +75327,15 @@ function findFirstNonEmptyLineIndex(lines, startLine, endLine) {
74642
75327
  }
74643
75328
 
74644
75329
  /**
74645
- * Checks whether a prompt is unrestricted or matches the selected harness/model.
75330
+ * Checks whether a prompt is unrestricted or matches the selected harness, model or Book agent.
74646
75331
  *
74647
- * A prompt status line can contain one or more backtick-delimited model or harness
74648
- * names. Matching is intentionally based on normalized substrings so a token such as
74649
- * `gpt` selects any `gpt-*` model and `opus` selects a `claude-opus-*` model.
75332
+ * A prompt status line can contain one or more backtick-delimited model, harness or agent
75333
+ * references. Matching is intentionally based on normalized substrings so a token such as
75334
+ * `gpt` selects any `gpt-*` model, `developer` selects an agent Book named `developer.book`,
75335
+ * and `opus` selects a `claude-opus-*` model.
74650
75336
  */
74651
75337
  function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
75338
+ var _a;
74652
75339
  if (promptRunnerIdentity === undefined) {
74653
75340
  return true;
74654
75341
  }
@@ -74657,7 +75344,11 @@ function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
74657
75344
  if (requiredRunnerTokens.length === 0) {
74658
75345
  return true;
74659
75346
  }
74660
- const normalizedRunnerNames = [promptRunnerIdentity.harnessName, promptRunnerIdentity.modelName]
75347
+ const normalizedRunnerNames = [
75348
+ promptRunnerIdentity.harnessName,
75349
+ promptRunnerIdentity.modelName,
75350
+ ...((_a = promptRunnerIdentity.agentReferences) !== null && _a !== void 0 ? _a : []),
75351
+ ]
74661
75352
  .filter((name) => name !== undefined && name.trim() !== '')
74662
75353
  .map((name) => normalizeToKebabCase(name))
74663
75354
  .filter((name) => name !== '');
@@ -74668,7 +75359,7 @@ function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
74668
75359
  });
74669
75360
  }
74670
75361
  /**
74671
- * Extracts model and harness tokens from a prompt status line.
75362
+ * Extracts model, harness and Book-agent tokens from a prompt status line.
74672
75363
  */
74673
75364
  function extractPromptRunnerTokens(statusLine) {
74674
75365
  return Array.from(statusLine.matchAll(/`([^`]+)`/gu))
@@ -74683,7 +75374,7 @@ function extractPromptRunnerTokens(statusLine) {
74683
75374
  *
74684
75375
  * @private internal constant of `parsePromptFile`
74685
75376
  */
74686
- const PROMPT_STATUS_MARKER_PATTERN = /^\[(?: |-|[xX]|\^|!)\]/u;
75377
+ const PROMPT_STATUS_LINE_PATTERN = /^\[(?<marker> |-|\.|[xX]|\^|!)\](?<details>.*)$/u;
74687
75378
  /**
74688
75379
  * Parses a prompt markdown file into sections and metadata.
74689
75380
  */
@@ -74707,7 +75398,7 @@ function parsePromptFile(filePath, content) {
74707
75398
  if (firstNonEmptyLine !== undefined) {
74708
75399
  const statusLine = (lines[firstNonEmptyLine] || '').trim();
74709
75400
  const parsedStatus = parseStatusLine(statusLine);
74710
- const status = (_a = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.status) !== null && _a !== void 0 ? _a : (hasPromptStatusMarker(statusLine) ? 'not-ready' : 'todo');
75401
+ 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');
74711
75402
  const priority = (_b = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.priority) !== null && _b !== void 0 ? _b : 0;
74712
75403
  sections.push({
74713
75404
  index,
@@ -74735,51 +75426,47 @@ function parsePromptFile(filePath, content) {
74735
75426
  * For [x] done, [!] failed and [^] in-progress prompts, allow metadata after the status marker.
74736
75427
  */
74737
75428
  function parseStatusLine(line) {
74738
- var _a, _b, _c, _d;
74739
- // For done prompts [x], allow any content after (for cost/time metadata)
74740
- const doneMatch = line.match(/^\[(?<status>[xX])\]/);
74741
- if (doneMatch) {
75429
+ var _a, _b, _c, _d, _e;
75430
+ const statusLineMatch = line.match(PROMPT_STATUS_LINE_PATTERN);
75431
+ const marker = (_a = statusLineMatch === null || statusLineMatch === void 0 ? void 0 : statusLineMatch.groups) === null || _a === void 0 ? void 0 : _a.marker;
75432
+ 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 : '';
75433
+ if (marker === 'x' || marker === 'X') {
75434
+ // For done prompts [x], allow any content after (for cost/time metadata).
74742
75435
  return { status: 'done', priority: 0 };
74743
75436
  }
74744
- // For failed prompts [!], allow any content after (for failure metadata)
74745
- const failedMatch = line.match(/^\[(?<status>!)\]/);
74746
- if (failedMatch) {
75437
+ if (marker === '!') {
75438
+ // For failed prompts [!], allow any content after (for failure metadata).
74747
75439
  return { status: 'failed', priority: 0 };
74748
75440
  }
74749
- // For in-progress prompts [^], allow any content after (for the steps recorded so far)
74750
- const inProgressMatch = line.match(/^\[(?<status>\^)\]/);
74751
- if (inProgressMatch) {
75441
+ if (marker === '^') {
75442
+ // For in-progress prompts [^], allow any content after (for the steps recorded so far).
74752
75443
  return { status: 'in-progress', priority: 0 };
74753
75444
  }
74754
- // For not-ready [-], keep the historical clean-line syntax.
74755
- if (/^\[-\]\s*!*\s*$/u.test(line)) {
75445
+ // Treat [.] as the alternative spelling of not-ready [-], preserving the historical clean-line syntax.
75446
+ if ((marker === '-' || marker === '.') && /^[!\s]*$/u.test(details)) {
74756
75447
  return { status: 'not-ready', priority: 0 };
74757
75448
  }
74758
- // Todo [ ] may contain backtick-delimited model/harness tokens and priority markers
74759
- // before or after those tokens. Other trailing text remains an invalid status line.
74760
- const todoMatch = line.match(/^\[ \](?<details>.*)$/u);
74761
- if (!todoMatch) {
75449
+ if (marker !== ' ') {
74762
75450
  return undefined;
74763
75451
  }
74764
- const details = (_b = (_a = todoMatch.groups) === null || _a === void 0 ? void 0 : _a.details) !== null && _b !== void 0 ? _b : '';
75452
+ // Todo [ ] may contain backtick-delimited model/harness tokens and priority markers
75453
+ // before or after those tokens. Other trailing text remains an invalid status line.
74765
75454
  const isPriorityOnly = /^[!\s]*$/u.test(details);
74766
75455
  const hasPromptRunnerTokens = extractPromptRunnerTokens(line).length > 0;
74767
75456
  if (details.trim() !== '' && !isPriorityOnly && !hasPromptRunnerTokens) {
74768
75457
  return undefined;
74769
75458
  }
74770
- return { status: 'todo', priority: (_d = (_c = details.match(/!/gu)) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0 };
75459
+ return { status: 'todo', priority: (_e = (_d = details.match(/!/gu)) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0 };
74771
75460
  }
75461
+
74772
75462
  /**
74773
- * Checks whether a line starts with one of the supported prompt status markers.
75463
+ * HTML comment which excludes a Markdown file from all `ptbk coder` queues.
74774
75464
  *
74775
- * @private internal utility of `parsePromptFile`
75465
+ * @private internal constant of `loadPromptFiles`
74776
75466
  */
74777
- function hasPromptStatusMarker(line) {
74778
- return PROMPT_STATUS_MARKER_PATTERN.test(line);
74779
- }
74780
-
75467
+ const PTBK_CODER_IGNORE_MARKER = '<!--ptbk-coder-ignore-->';
74781
75468
  /**
74782
- * Loads and parses prompt files from the prompts directory.
75469
+ * Loads and parses prompt files from the prompts directory, excluding files marked for `ptbk coder` to ignore.
74783
75470
  */
74784
75471
  async function loadPromptFiles(promptsDir) {
74785
75472
  const entries = await readdir(promptsDir, { withFileTypes: true });
@@ -74790,6 +75477,9 @@ async function loadPromptFiles(promptsDir) {
74790
75477
  const promptFiles = [];
74791
75478
  for (const filePath of files) {
74792
75479
  const content = await readFile(filePath, 'utf-8');
75480
+ if (content.includes(PTBK_CODER_IGNORE_MARKER)) {
75481
+ continue;
75482
+ }
74793
75483
  promptFiles.push(parsePromptFile(filePath, content));
74794
75484
  }
74795
75485
  return promptFiles;
@@ -74900,22 +75590,158 @@ var findUnwrittenPrompts$1 = /*#__PURE__*/Object.freeze({
74900
75590
  });
74901
75591
 
74902
75592
  /**
74903
- * Formats one unknown error-like value into its readable message without the stack trace.
75593
+ * Reads an optional agent `.book` file and prepares the references that route prompts to it.
74904
75594
  *
74905
- * Use this instead of `formatUnknownErrorDetails` whenever the text is shown to the user, for example
74906
- * inside a branded error, where the stack of the wrapped error would only bury the actual cause.
75595
+ * Returns `undefined` when no agent path is provided.
74907
75596
  */
74908
- function formatUnknownErrorMessage(error) {
74909
- if (error instanceof Error) {
74910
- return error.message;
75597
+ async function resolveCoderAgentBook(agentBookReference, currentWorkingDirectory) {
75598
+ const normalizedAgentBookReference = agentBookReference === null || agentBookReference === void 0 ? void 0 : agentBookReference.trim();
75599
+ if (!normalizedAgentBookReference) {
75600
+ return undefined;
74911
75601
  }
74912
- if (typeof error === 'string') {
74913
- return error;
75602
+ const resolvedAgentBookPath = resolve(currentWorkingDirectory, normalizedAgentBookReference);
75603
+ const agentSource = (await readFile(resolvedAgentBookPath, 'utf-8').catch((error) => {
75604
+ if (error.code === 'ENOENT' || error.code === 'EISDIR') {
75605
+ throw new NotFoundError(spaceTrim(`
75606
+ Agent book \`${normalizedAgentBookReference}\` was not found or is not a file.
75607
+
75608
+ Pass a path to a \`.book\` file in \`--agent\`.
75609
+ `));
75610
+ }
75611
+ throw error;
75612
+ }));
75613
+ return {
75614
+ agentSource,
75615
+ agentReferences: createCoderAgentReferences({
75616
+ agentBookReference: normalizedAgentBookReference,
75617
+ resolvedAgentBookPath,
75618
+ currentWorkingDirectory,
75619
+ agentSource,
75620
+ }),
75621
+ };
75622
+ }
75623
+ /**
75624
+ * Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
75625
+ *
75626
+ * Returns `undefined` when no agent path is provided.
75627
+ */
75628
+ async function resolveCoderAgent(agentBookReference, currentWorkingDirectory) {
75629
+ const resolvedAgentBook = await resolveCoderAgentBook(agentBookReference, currentWorkingDirectory);
75630
+ if (resolvedAgentBook === undefined) {
75631
+ return undefined;
74914
75632
  }
74915
- const serializedError = JSON.stringify(error, null, 2);
74916
- return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
75633
+ return {
75634
+ ...resolvedAgentBook,
75635
+ systemMessage: await createAgentRunnerSystemMessage(resolvedAgentBook.agentSource),
75636
+ };
75637
+ }
75638
+ /**
75639
+ * Creates the path and Book-title aliases which a prompt can use to target one selected agent.
75640
+ *
75641
+ * @private internal utility of `resolveCoderAgentBook`
75642
+ */
75643
+ function createCoderAgentReferences(options) {
75644
+ const relativeAgentBookPath = relative(options.currentWorkingDirectory, options.resolvedAgentBookPath).replaceAll('\\', '/');
75645
+ const agentBookFileName = basename(options.resolvedAgentBookPath);
75646
+ const agentBookName = basename(options.resolvedAgentBookPath, extname(options.resolvedAgentBookPath));
75647
+ const agentNameFromBook = parseAgentSource(options.agentSource).agentName;
75648
+ return Array.from(new Set([
75649
+ options.agentBookReference,
75650
+ relativeAgentBookPath,
75651
+ agentBookFileName,
75652
+ agentBookName,
75653
+ agentNameFromBook,
75654
+ ].filter((agentReference) => agentReference.trim() !== '')));
75655
+ }
75656
+
75657
+ var resolveCoderAgent$1 = /*#__PURE__*/Object.freeze({
75658
+ __proto__: null,
75659
+ resolveCoderAgentBook: resolveCoderAgentBook,
75660
+ resolveCoderAgent: resolveCoderAgent
75661
+ });
75662
+
75663
+ /**
75664
+ * Lists todo prompts that are ready to run (no authoring placeholders).
75665
+ */
75666
+ function listRunnablePrompts(files, priorityFilter = {}, promptRunnerIdentity) {
75667
+ return listTodoPrompts(files).filter((prompt) => !isPromptToBeWritten(prompt.file, prompt.section) &&
75668
+ isPromptInPriorityFilter(prompt.section, priorityFilter) &&
75669
+ isPromptCompatibleWithRunner(prompt.file, prompt.section, promptRunnerIdentity));
75670
+ }
75671
+
75672
+ /**
75673
+ * Lists upcoming tasks that are ready to run (no authoring placeholders).
75674
+ */
75675
+ function listUpcomingTasks(files, priorityFilter = {}, promptRunnerIdentity) {
75676
+ return listRunnablePrompts(files, priorityFilter, promptRunnerIdentity).map(({ file, section }) => ({
75677
+ label: buildPromptLabelForDisplay(file, section),
75678
+ summary: buildPromptSummary(file, section),
75679
+ priority: section.priority,
75680
+ }));
75681
+ }
75682
+
75683
+ /**
75684
+ * Groups upcoming tasks by priority, high to low.
75685
+ */
75686
+ function groupUpcomingTasksByPriority(tasks) {
75687
+ const grouped = new Map();
75688
+ for (const task of tasks) {
75689
+ const group = grouped.get(task.priority);
75690
+ if (group) {
75691
+ group.push(task);
75692
+ }
75693
+ else {
75694
+ grouped.set(task.priority, [task]);
75695
+ }
75696
+ }
75697
+ return Array.from(grouped.entries())
75698
+ .sort((a, b) => b[0] - a[0])
75699
+ .map(([priority, groupedTasks]) => ({ priority, tasks: groupedTasks }));
75700
+ }
75701
+
75702
+ /**
75703
+ * Prints upcoming tasks grouped by priority.
75704
+ */
75705
+ function printUpcomingTasks(tasks) {
75706
+ if (tasks.length === 0) {
75707
+ console.info(colors.green('No upcoming tasks.'));
75708
+ return;
75709
+ }
75710
+ console.info(colors.cyan('Upcoming tasks (grouped by priority):'));
75711
+ for (const group of groupUpcomingTasksByPriority(tasks)) {
75712
+ console.info(colors.cyan(`Priority ${group.priority}:`));
75713
+ for (const [index, task] of group.tasks.entries()) {
75714
+ const summary = task.summary ? ` - ${task.summary}` : '';
75715
+ console.info(` ${index + 1}. ${task.label}${summary}`);
75716
+ }
75717
+ }
75718
+ }
75719
+
75720
+ /**
75721
+ * Directory containing the prompt queue of the current project.
75722
+ *
75723
+ * @private internal constant of `listCoderPrompts`
75724
+ */
75725
+ const PROMPTS_DIRECTORY_PATH = join(process.cwd(), 'prompts');
75726
+ /**
75727
+ * Lists ready, fully authored coding prompts in descending priority groups without starting a coding harness.
75728
+ *
75729
+ * @returns The number of prompts printed.
75730
+ * @public exported from `@promptbook/cli`
75731
+ */
75732
+ async function listCoderPrompts(options = {}) {
75733
+ const priorityFilter = normalizePriorityFilter(options);
75734
+ const promptFiles = await loadPromptFiles(PROMPTS_DIRECTORY_PATH);
75735
+ const upcomingTasks = listUpcomingTasks(promptFiles, priorityFilter, options.promptRunnerIdentity);
75736
+ printUpcomingTasks(upcomingTasks);
75737
+ return upcomingTasks.length;
74917
75738
  }
74918
75739
 
75740
+ var listCoderPrompts$1 = /*#__PURE__*/Object.freeze({
75741
+ __proto__: null,
75742
+ listCoderPrompts: listCoderPrompts
75743
+ });
75744
+
74919
75745
  /**
74920
75746
  * Builds a normalized temporary shell script path for prompt runners.
74921
75747
  */
@@ -75214,7 +76040,7 @@ const DEFAULT_WAIT_AFTER_ERROR_MS = 10 * 60 * 1000;
75214
76040
  /**
75215
76041
  * CLI usage text for this script.
75216
76042
  */
75217
- 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]';
76043
+ 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]';
75218
76044
  /**
75219
76045
  * Top-level flags supported by this command.
75220
76046
  */
@@ -75222,6 +76048,7 @@ const KNOWN_OPTION_FLAGS = new Set([
75222
76048
  '--dry-run',
75223
76049
  '--harness',
75224
76050
  '--model',
76051
+ '--agent',
75225
76052
  '--context',
75226
76053
  '--test',
75227
76054
  '--test-before',
@@ -75260,6 +76087,7 @@ function parseRunOptions(args) {
75260
76087
  }
75261
76088
  }
75262
76089
  const model = readOptionValue(args, '--model');
76090
+ const agent = readOptionValue(args, '--agent');
75263
76091
  const context = readOptionValue(args, '--context');
75264
76092
  const hasTestCommandFlag = args.includes('--test');
75265
76093
  const testCommand = readVariadicOptionValue(args, '--test');
@@ -75336,6 +76164,7 @@ function parseRunOptions(args) {
75336
76164
  noUi,
75337
76165
  agentName,
75338
76166
  model,
76167
+ agent,
75339
76168
  context,
75340
76169
  testCommand,
75341
76170
  testBefore,
@@ -75770,30 +76599,43 @@ async function recordPromptDurationSample(key, promptDurationMs) {
75770
76599
  // Note: [🟡] Code in this file is only used from `@promptbook/cli` orchestration scripts and is not shipped to other consumers.
75771
76600
 
75772
76601
  /**
75773
- * Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
76602
+ * Creates the guard which watches the free disk space while one `ptbk coder` run is in progress.
75774
76603
  *
75775
- * Returns `undefined` when no agent path is provided.
76604
+ * The guard runs at every pause checkpoint of the run. When the disk became critically full it reports the
76605
+ * warning and requests a pause, so the run stops at that checkpoint instead of failing in the middle of a
76606
+ * coding round, and continues once the user has freed some space and pressed `P`.
76607
+ *
76608
+ * @private internal utility of `ptbk coder` free disk space handling
75776
76609
  */
75777
- async function resolveCoderAgent(agentPath, currentWorkingDirectory) {
75778
- if (!agentPath) {
75779
- return undefined;
75780
- }
75781
- const resolvedAgentPath = resolve(currentWorkingDirectory, agentPath);
75782
- const agentSource = await readFile(resolvedAgentPath, 'utf-8').catch((error) => {
75783
- if (error.code === 'ENOENT' || error.code === 'EISDIR') {
75784
- throw new NotFoundError(spaceTrim(`
75785
- Agent book \`${agentPath}\` was not found or is not a file.
76610
+ function createFreeDiskSpaceGuard(options) {
76611
+ const { inspectedPath, isAskingQuestionsEnabled } = options;
76612
+ let lastMeasurementTimeMs;
76613
+ return async () => {
76614
+ if (lastMeasurementTimeMs !== undefined &&
76615
+ Date.now() - lastMeasurementTimeMs < FREE_DISK_SPACE_RECHECK_INTERVAL_MS) {
76616
+ return;
76617
+ }
76618
+ lastMeasurementTimeMs = Date.now();
76619
+ const freeDiskSpaceStatus = await $readFreeDiskSpaceStatus(inspectedPath);
76620
+ if (freeDiskSpaceStatus === null || freeDiskSpaceStatus.level !== 'critical') {
76621
+ return;
76622
+ }
76623
+ console.warn(colors.yellow(spaceTrim$1((block) => `
76624
+ The disk is running out of space while \`ptbk coder\` is working.
75786
76625
 
75787
- Pass a path to a \`.book\` file in \`--agent\`.
75788
- `));
76626
+ ${block(formatLowFreeDiskSpaceWarning(freeDiskSpaceStatus))}
76627
+ - Press \`P\` to continue once the disk space is freed, or start the run with \`--no-questions\` to never pause on a full disk.
76628
+ `)));
76629
+ if (!isAskingQuestionsEnabled) {
76630
+ // Note: `--no-questions` forbids stopping and waiting for somebody who would free the disk space,
76631
+ // so the run keeps going and the warning above is the only thing which is reported
76632
+ return;
75789
76633
  }
75790
- throw error;
75791
- });
75792
- return {
75793
- agentSource: agentSource,
75794
- systemMessage: await createAgentRunnerSystemMessage(agentSource),
76634
+ announcePauseTargetLabel(`continuing with only ${formatFreeDiskSpaceBytes(freeDiskSpaceStatus.availableBytes)} of free disk space left`);
76635
+ requestPause();
75795
76636
  };
75796
76637
  }
76638
+ // 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`
75797
76639
 
75798
76640
  /**
75799
76641
  * Refresh interval used by the countdown display while waiting.
@@ -77764,6 +78606,169 @@ async function writePromptFile(file) {
77764
78606
  await writeFile(file.path, content, 'utf-8');
77765
78607
  }
77766
78608
 
78609
+ /**
78610
+ * Creates a safe markdown fenced code block even when content contains backticks.
78611
+ *
78612
+ * @param content - Raw code content.
78613
+ * @param language - Optional info-string language label.
78614
+ * @returns Fenced code block.
78615
+ *
78616
+ * @private internal utility of `createStandaloneBookLanguageMarkdown` and of the `ptbk coder` run traces
78617
+ */
78618
+ function getSafeCodeBlock(content, language = 'markdown') {
78619
+ const maxBacktickCount = Math.max(0, ...(content.match(/`+/g) || []).map((match) => match.length));
78620
+ const fence = '`'.repeat(Math.max(3, maxBacktickCount + 1));
78621
+ return `${fence}${language}\n${content}\n${fence}`;
78622
+ }
78623
+
78624
+ /**
78625
+ * Renders the markdown run trace of one finished or failed prompt round.
78626
+ *
78627
+ * The trace pairs the metadata of the round - which harness, model and thinking level ran it, how long each of
78628
+ * its steps took and what it cost - with the untouched runtime log, so a finished round can still be analyzed
78629
+ * after its temporary artifacts are cleaned up.
78630
+ */
78631
+ function buildPromptRunTraceContent(options) {
78632
+ const sections = [
78633
+ buildPromptRunTraceSummarySection(options),
78634
+ buildPromptRunTraceFailureSection(options.outcome),
78635
+ buildPromptRunTraceRuntimeLogSection(options.runtimeLog),
78636
+ ].filter((section) => section !== undefined);
78637
+ return `${sections.join('\n\n')}\n`;
78638
+ }
78639
+ /**
78640
+ * Renders the heading and the metadata bullet list of one run trace.
78641
+ */
78642
+ function buildPromptRunTraceSummarySection(options) {
78643
+ const { file, section, outcome } = options;
78644
+ const runnerSignature = formatRunnerSignature(options.runnerName, options.modelName, options.thinkingLevel);
78645
+ const loginMethodLabel = outcome.kind === 'succeeded' ? formatCodexLoginMethod(outcome.loginMethod) : undefined;
78646
+ const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
78647
+ const detailLines = [
78648
+ `- **Prompt:** ${buildPromptSummary(file, section)}`,
78649
+ ...buildPromptRunTraceSectionLines(file, section),
78650
+ `- **Outcome:** ${formatPromptRunTraceOutcomeLabel(outcome)}`,
78651
+ `- **Runner:** ${runnerSignature}${loginMethodSuffix}`,
78652
+ `- **Attempts:** ${options.attemptCount}`,
78653
+ ...buildPromptRunTraceStepsLines(outcome),
78654
+ ...buildPromptRunTraceTestCommandLines(options.testCommand),
78655
+ `- **Started:** ${options.startedDate.toISOString()}`,
78656
+ `- **Finished:** ${options.finishedDate.toISOString()}`,
78657
+ `- **Duration:** ${moment.duration(options.finishedDate.diff(options.startedDate)).humanize()}`,
78658
+ ];
78659
+ return spaceTrim$1((block) => `
78660
+ # Run trace of \`${buildPromptLabelForDisplay(file, section)}\`
78661
+
78662
+ ${block(detailLines.join('\n'))}
78663
+ `);
78664
+ }
78665
+ /**
78666
+ * Renders which section of its prompt file the round implemented.
78667
+ *
78668
+ * A prompt file holding exactly one section says nothing by naming it, exactly like the section suffix of its
78669
+ * generated artifact names, so only a file with more than one section reports the section it traced.
78670
+ */
78671
+ function buildPromptRunTraceSectionLines(file, section) {
78672
+ if (file.sections.length <= 1) {
78673
+ return [];
78674
+ }
78675
+ return [`- **Prompt section:** ${section.index + 1} of ${file.sections.length}`];
78676
+ }
78677
+ /**
78678
+ * Renders the per-step usage breakdown of a successful round, omitted for a round which never finished one.
78679
+ */
78680
+ function buildPromptRunTraceStepsLines(outcome) {
78681
+ if (outcome.kind !== 'succeeded') {
78682
+ return [];
78683
+ }
78684
+ const stepsSummary = formatCoderRunSteps(outcome.steps);
78685
+ if (stepsSummary === '') {
78686
+ return [];
78687
+ }
78688
+ return [`- **Steps:** ${stepsSummary}`];
78689
+ }
78690
+ /**
78691
+ * Renders the verification command of the round, omitted when the round ran without one.
78692
+ */
78693
+ function buildPromptRunTraceTestCommandLines(testCommand) {
78694
+ if (!testCommand) {
78695
+ return [];
78696
+ }
78697
+ return [`- **Verification command:** \`${testCommand}\``];
78698
+ }
78699
+ /**
78700
+ * Renders the failure details of a failed round, omitted for a successful one.
78701
+ */
78702
+ function buildPromptRunTraceFailureSection(outcome) {
78703
+ if (outcome.kind !== 'failed') {
78704
+ return undefined;
78705
+ }
78706
+ return `## Failure\n\n${getSafeCodeBlock(formatUnknownErrorDetails(outcome.error), 'text')}`;
78707
+ }
78708
+ /**
78709
+ * Renders the untouched runtime log of the round, or says that the round produced none.
78710
+ */
78711
+ function buildPromptRunTraceRuntimeLogSection(runtimeLog) {
78712
+ if (runtimeLog.trim() === '') {
78713
+ return '## Runtime log\n\n_This round has produced no readable runtime log._';
78714
+ }
78715
+ return `## Runtime log\n\n${getSafeCodeBlock(runtimeLog.replace(/\r\n/gu, '\n').trimEnd(), 'text')}`;
78716
+ }
78717
+ /**
78718
+ * Formats the outcome of the round as the human-readable label of its trace.
78719
+ */
78720
+ function formatPromptRunTraceOutcomeLabel(outcome) {
78721
+ return outcome.kind === 'succeeded' ? 'Succeeded' : 'Failed';
78722
+ }
78723
+
78724
+ /**
78725
+ * Name of the directory which keeps the run traces inside the prompts directory.
78726
+ */
78727
+ const PROMPT_RUN_TRACES_DIRECTORY_NAME = 'traces';
78728
+ /**
78729
+ * Builds the path of the run trace which belongs to one prompt section.
78730
+ *
78731
+ * The trace is stored in the `traces` directory of the very same prompts directory the prompt itself lives in,
78732
+ * and it carries the name of its prompt file. A prompt file holding more than one section appends the section
78733
+ * suffix, exactly like the temporary scripts and the isolation worktrees of the same section do.
78734
+ */
78735
+ function buildPromptRunTracePath(file, section) {
78736
+ const promptFileBaseName = file.name.replace(/\.[^.]+$/u, '');
78737
+ const traceFileName = `${promptFileBaseName}${buildPromptSectionSuffix(file, section)}.md`;
78738
+ return join(dirname(file.path), PROMPT_RUN_TRACES_DIRECTORY_NAME, traceFileName);
78739
+ }
78740
+
78741
+ /**
78742
+ * Writes the run trace of one prompt round into the `traces` directory of the prompts directory.
78743
+ *
78744
+ * Returns the path of the written trace so callers can include it in a commit.
78745
+ */
78746
+ async function writePromptRunTrace(options) {
78747
+ const { logPath, ...contentOptions } = options;
78748
+ const tracePath = buildPromptRunTracePath(options.file, options.section);
78749
+ const runtimeLog = await readPromptRuntimeLog(logPath);
78750
+ await mkdir(dirname(tracePath), { recursive: true });
78751
+ await writeFile(tracePath, buildPromptRunTraceContent({ ...contentOptions, runtimeLog }), 'utf-8');
78752
+ return tracePath;
78753
+ }
78754
+ /**
78755
+ * Reads the live runtime log of one round.
78756
+ *
78757
+ * A round which never produced a readable log still gets its trace, because the metadata of the round is worth
78758
+ * keeping on its own - so a missing log is reported as empty instead of failing the round which has just passed.
78759
+ */
78760
+ async function readPromptRuntimeLog(logPath) {
78761
+ if (!logPath) {
78762
+ return '';
78763
+ }
78764
+ try {
78765
+ return await readFile(logPath, 'utf-8');
78766
+ }
78767
+ catch (_a) {
78768
+ return '';
78769
+ }
78770
+ }
78771
+
77767
78772
  /**
77768
78773
  * Refreshes optional harness subscription usage in the shared coder-run UI state.
77769
78774
  *
@@ -77886,7 +78891,9 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
77886
78891
  catch (error) {
77887
78892
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
77888
78893
  lastError = error;
77889
- if (errorRetryAttempt >= MAX_RETRY_ATTEMPTS_AFTER_ERROR) {
78894
+ // Note: A harness which is not logged in answers every retry the same way, so the user gets
78895
+ // the sign-in instructions right away instead of after every retry has waited its delay
78896
+ if (error instanceof AuthenticationError || errorRetryAttempt >= MAX_RETRY_ATTEMPTS_AFTER_ERROR) {
77890
78897
  break;
77891
78898
  }
77892
78899
  await waitAfterErrorBeforeRetry({
@@ -77908,6 +78915,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
77908
78915
  attemptCount,
77909
78916
  error: lastError,
77910
78917
  options,
78918
+ logPath,
77911
78919
  roundCommitScope,
77912
78920
  uiHandle,
77913
78921
  waitForRequestedPause,
@@ -78024,6 +79032,17 @@ async function finalizeSuccessfulPromptRound(options) {
78024
79032
  // Note: The prompt status is always written into the original project, an isolated round transports
78025
79033
  // its own changes back through the merge instead
78026
79034
  await writePromptFile(nextPrompt.file);
79035
+ // Note: Written before the round is committed, so the trace of the round lands in the very same commit
79036
+ // as the prompt it describes, and before the live runtime log it is built from is deleted
79037
+ await recordPromptRoundTrace({
79038
+ options: runOptions,
79039
+ nextPrompt,
79040
+ runnerMetadata,
79041
+ promptExecutionStartedDate,
79042
+ attemptCount: result.attemptCount,
79043
+ logPath,
79044
+ outcome: { kind: 'succeeded', steps: result.steps, loginMethod: result.loginMethod },
79045
+ });
78027
79046
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78028
79047
  await recordPromptDurationInEstimateCache({
78029
79048
  options: runOptions,
@@ -78071,7 +79090,7 @@ async function finalizeSuccessfulPromptRound(options) {
78071
79090
  * Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
78072
79091
  */
78073
79092
  async function finalizeFailedPromptRound(options) {
78074
- const { nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
79093
+ const { nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, attemptCount, error, options: runOptions, logPath, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
78075
79094
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
78076
79095
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
78077
79096
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
@@ -78097,8 +79116,42 @@ async function finalizeFailedPromptRound(options) {
78097
79116
  modelName: runnerMetadata.modelName,
78098
79117
  error,
78099
79118
  });
79119
+ // Note: A failed round is exactly the round whose trace is worth reading, so it is written before the live
79120
+ // runtime log it is built from is deleted
79121
+ await recordPromptRoundTrace({
79122
+ options: runOptions,
79123
+ nextPrompt,
79124
+ runnerMetadata,
79125
+ promptExecutionStartedDate,
79126
+ attemptCount,
79127
+ logPath,
79128
+ outcome: { kind: 'failed', error },
79129
+ });
78100
79130
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78101
79131
  }
79132
+ /**
79133
+ * Persists the run trace of one prompt round, so the round can still be analyzed after its temporary
79134
+ * artifacts are gone.
79135
+ *
79136
+ * This is the single place where both the successful and the failed round record what they did, which harness,
79137
+ * model and thinking level did it and everything that harness has written while doing it.
79138
+ */
79139
+ async function recordPromptRoundTrace(options) {
79140
+ const { options: runOptions, nextPrompt, runnerMetadata, promptExecutionStartedDate, attemptCount, logPath, outcome, } = options;
79141
+ await writePromptRunTrace({
79142
+ file: nextPrompt.file,
79143
+ section: nextPrompt.section,
79144
+ runnerName: runnerMetadata.runnerName,
79145
+ modelName: runnerMetadata.modelName,
79146
+ thinkingLevel: runOptions.thinkingLevel,
79147
+ testCommand: runOptions.testCommand,
79148
+ attemptCount,
79149
+ startedDate: promptExecutionStartedDate,
79150
+ finishedDate: moment(),
79151
+ outcome,
79152
+ logPath,
79153
+ });
79154
+ }
78102
79155
  /**
78103
79156
  * Waits for the optional user confirmation immediately before creating the commit.
78104
79157
  */
@@ -78747,7 +79800,13 @@ async function recordIsolationMergeFailure(options, worktree, failureDetails) {
78747
79800
  await commitChanges(buildCoderIsolationMergeFailureCommitMessage(worktree), {
78748
79801
  autoPush: options.options.autoPush,
78749
79802
  projectPath: worktree.projectPath,
78750
- relevantPaths: [nextPrompt.file.path, errorLogPath].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
79803
+ // Note: The round itself has already succeeded, so it has left its run trace in the original project.
79804
+ // It belongs to this commit, which is the only one this task still gets.
79805
+ relevantPaths: [
79806
+ nextPrompt.file.path,
79807
+ errorLogPath,
79808
+ buildPromptRunTracePath(nextPrompt.file, nextPrompt.section),
79809
+ ].map((path) => toProjectRelativeGitPath(worktree.projectPath, path)),
78751
79810
  });
78752
79811
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(mergeFailureError.message);
78753
79812
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Merging \`${worktree.taskName}\` failed, worktree kept for a manual merge`);
@@ -78771,15 +79830,6 @@ function toProjectRelativeGitPath(projectPath, path) {
78771
79830
  return relative(projectPath, path).replace(/\\/gu, '/');
78772
79831
  }
78773
79832
 
78774
- /**
78775
- * Lists todo prompts that are ready to run (no authoring placeholders).
78776
- */
78777
- function listRunnablePrompts(files, priorityFilter = {}, promptRunnerIdentity) {
78778
- return listTodoPrompts(files).filter((prompt) => !isPromptToBeWritten(prompt.file, prompt.section) &&
78779
- isPromptInPriorityFilter(prompt.section, priorityFilter) &&
78780
- isPromptCompatibleWithRunner(prompt.file, prompt.section, promptRunnerIdentity));
78781
- }
78782
-
78783
79833
  /**
78784
79834
  * Selects the next runnable prompt based on priority.
78785
79835
  */
@@ -78793,54 +79843,6 @@ function findNextTodoPrompt(files, priorityFilter = {}, promptRunnerIdentity) {
78793
79843
  return nextPrompt;
78794
79844
  }
78795
79845
 
78796
- /**
78797
- * Lists upcoming tasks that are ready to run (no authoring placeholders).
78798
- */
78799
- function listUpcomingTasks(files, priorityFilter = {}, promptRunnerIdentity) {
78800
- return listRunnablePrompts(files, priorityFilter, promptRunnerIdentity).map(({ file, section }) => ({
78801
- label: buildPromptLabelForDisplay(file, section),
78802
- summary: buildPromptSummary(file, section),
78803
- priority: section.priority,
78804
- }));
78805
- }
78806
-
78807
- /**
78808
- * Groups upcoming tasks by priority, high to low.
78809
- */
78810
- function groupUpcomingTasksByPriority(tasks) {
78811
- const grouped = new Map();
78812
- for (const task of tasks) {
78813
- const group = grouped.get(task.priority);
78814
- if (group) {
78815
- group.push(task);
78816
- }
78817
- else {
78818
- grouped.set(task.priority, [task]);
78819
- }
78820
- }
78821
- return Array.from(grouped.entries())
78822
- .sort((a, b) => b[0] - a[0])
78823
- .map(([priority, groupedTasks]) => ({ priority, tasks: groupedTasks }));
78824
- }
78825
-
78826
- /**
78827
- * Prints upcoming tasks grouped by priority.
78828
- */
78829
- function printUpcomingTasks(tasks) {
78830
- if (tasks.length === 0) {
78831
- console.info(colors.green('No upcoming tasks.'));
78832
- return;
78833
- }
78834
- console.info(colors.cyan('Upcoming tasks (grouped by priority):'));
78835
- for (const group of groupUpcomingTasksByPriority(tasks)) {
78836
- console.info(colors.cyan(`Priority ${group.priority}:`));
78837
- for (const [index, task] of group.tasks.entries()) {
78838
- const summary = task.summary ? ` - ${task.summary}` : '';
78839
- console.info(` ${index + 1}. ${task.label}${summary}`);
78840
- }
78841
- }
78842
- }
78843
-
78844
79846
  /**
78845
79847
  * Builds a label that identifies the prompt section by index.
78846
79848
  */
@@ -79079,12 +80081,23 @@ const PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE = 'test: Apply changes made by pre-
79079
80081
  * @public exported from `@promptbook/cli`
79080
80082
  */
79081
80083
  async function runCodexPrompts(providedOptions) {
80084
+ var _a;
79082
80085
  const options = normalizeRunOptions(providedOptions !== null && providedOptions !== void 0 ? providedOptions : parseRunOptions(process.argv.slice(2)));
79083
80086
  validateRunCodexPromptOptions(options);
79084
80087
  resetCoderRunControls();
79085
80088
  const runStartDate = moment();
79086
80089
  const { isRichUiEnabled, progressDisplay, uiHandle } = createRunDisplays(options, runStartDate);
79087
- const waitForRequestedPause = createPauseWaiter({ isRichUiEnabled, progressDisplay, uiHandle });
80090
+ const waitForRequestedPause = createPauseWaiter({
80091
+ isRichUiEnabled,
80092
+ progressDisplay,
80093
+ uiHandle,
80094
+ // Note: Every pause checkpoint of the whole run goes through this one waiter, so watching the free disk
80095
+ // space here covers each round, each verification and each runner without repeating the check
80096
+ guardFreeDiskSpace: createFreeDiskSpaceGuard({
80097
+ inspectedPath: process.cwd(),
80098
+ isAskingQuestionsEnabled: (_a = options.isAskingQuestionsEnabled) !== null && _a !== void 0 ? _a : true,
80099
+ }),
80100
+ });
79088
80101
  startPauseListenerIfNeeded(isRichUiEnabled);
79089
80102
  try {
79090
80103
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
@@ -79097,6 +80110,7 @@ async function runCodexPrompts(providedOptions) {
79097
80110
  const promptRunnerIdentity = {
79098
80111
  harnessName: options.agentName,
79099
80112
  modelName: actualRunnerModel,
80113
+ agentReferences: resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.agentReferences,
79100
80114
  };
79101
80115
  console.info(colors.green(`Running prompts with ${runner.name}`));
79102
80116
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
@@ -79533,11 +80547,14 @@ async function commitTestBeforeChangesIfNeeded(options) {
79533
80547
  * Creates a pause waiter that keeps the progress display and rich UI in sync.
79534
80548
  */
79535
80549
  function createPauseWaiter(options) {
79536
- const { isRichUiEnabled, progressDisplay, uiHandle } = options;
80550
+ const { isRichUiEnabled, progressDisplay, uiHandle, guardFreeDiskSpace } = options;
79537
80551
  return async (checkpoint) => {
79538
80552
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase(checkpoint.phase);
79539
80553
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(checkpoint.statusMessage);
79540
80554
  announcePauseTargetLabel(checkpoint.checkpointLabel);
80555
+ // Note: Runs after the checkpoint label is announced, so a pause requested because of a full disk
80556
+ // replaces that label and says why the run is standing still
80557
+ await guardFreeDiskSpace();
79541
80558
  await checkPause({
79542
80559
  silent: isRichUiEnabled,
79543
80560
  onPaused: () => {
@@ -79656,7 +80673,7 @@ function finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiH
79656
80673
  return false;
79657
80674
  }
79658
80675
  if (promptQueueSnapshot.stats.forAgent > 0) {
79659
- announceRunCompletion('No prompts match the selected harness or model.', colors.yellow, isRichUiEnabled, uiHandle);
80676
+ announceRunCompletion('No prompts match the selected harness, model or agent.', colors.yellow, isRichUiEnabled, uiHandle);
79660
80677
  }
79661
80678
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79662
80679
  announceRunCompletion('No prompts ready for agent.', colors.yellow, isRichUiEnabled, uiHandle);
@@ -79701,7 +80718,7 @@ function finishWhenEndAfterCurrentPromptIsRequested(options) {
79701
80718
  function announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle) {
79702
80719
  let message;
79703
80720
  if (promptQueueSnapshot.stats.forAgent > 0) {
79704
- message = 'No prompts match the selected harness or model. Watching for changes...';
80721
+ message = 'No prompts match the selected harness, model or agent. Watching for changes...';
79705
80722
  }
79706
80723
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79707
80724
  message = 'No prompts ready for agent. Watching for changes...';