@promptbook/cli 0.114.0-33 → 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 (167) hide show
  1. package/esm/index.es.js +1254 -318
  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 +1 -1
  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/mergeStringRecordJsonFile.d.ts +19 -1
  28. package/esm/src/cli/cli-commands/coder/printCoderRunFailure.d.ts +6 -0
  29. package/esm/src/cli/cli-commands/common/$askForConfirmation.d.ts +4 -2
  30. package/esm/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.d.ts +12 -0
  31. package/esm/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.test.d.ts +1 -0
  32. package/esm/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.d.ts +11 -0
  33. package/esm/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.test.d.ts +1 -0
  34. package/esm/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.d.ts +33 -0
  35. package/esm/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.d.ts +6 -0
  36. package/esm/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.test.d.ts +1 -0
  37. package/esm/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.d.ts +10 -0
  38. package/esm/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.d.ts +28 -0
  39. package/esm/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.d.ts +7 -0
  40. package/esm/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.test.d.ts +1 -0
  41. package/esm/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.d.ts +2 -1
  42. package/esm/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -2
  43. package/esm/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +26 -0
  44. package/esm/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.d.ts +4 -2
  45. package/esm/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +2 -1
  46. package/esm/src/cli/cli-commands/common/questionsCliOptions.d.ts +45 -0
  47. package/esm/src/cli/cli-commands/common/questionsCliOptions.test.d.ts +1 -0
  48. package/esm/src/cli/common/$deprecateCliCommand.d.ts +3 -0
  49. package/esm/src/cli/common/$hideCliCommandFromHelp.d.ts +11 -0
  50. package/esm/src/cli/common/$hideCliCommandFromHelp.test.d.ts +1 -0
  51. package/esm/src/cli/common/$requireCliSubcommand.d.ts +18 -0
  52. package/esm/src/cli/common/$requireCliSubcommand.test.d.ts +1 -0
  53. package/esm/src/llm-providers/vercel/VercelExecutionToolsOptions.d.ts +5 -2
  54. package/esm/src/version.d.ts +1 -1
  55. package/package.json +8 -11
  56. package/src/book-2.0/book-language-documentation/getSafeCodeBlock.ts +1 -1
  57. package/src/book-components/Chat/MockedChat/MockedChat.tsx +25 -1
  58. package/src/cli/cli-commands/agent-folder.ts +2 -6
  59. package/src/cli/cli-commands/agent.ts +2 -6
  60. package/src/cli/cli-commands/agents-server.ts +2 -6
  61. package/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.ts +3 -0
  62. package/src/cli/cli-commands/coder/add.ts +27 -2
  63. package/src/cli/cli-commands/coder/agentCliOptions.ts +32 -0
  64. package/src/cli/cli-commands/coder/boilerplateTemplates.ts +17 -44
  65. package/src/cli/cli-commands/coder/coderReferencedArtifacts.ts +106 -0
  66. package/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.ts +4 -2
  67. package/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.ts +5 -1
  68. package/src/cli/cli-commands/coder/ensureDirectory.ts +27 -1
  69. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +99 -10
  70. package/src/cli/cli-commands/coder/init.ts +23 -14
  71. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +18 -27
  72. package/src/cli/cli-commands/coder/list.ts +15 -3
  73. package/src/cli/cli-commands/coder/mergeStringRecordJsonFile.ts +31 -7
  74. package/src/cli/cli-commands/coder/ping.ts +25 -22
  75. package/src/cli/cli-commands/coder/printCoderRunFailure.ts +14 -0
  76. package/src/cli/cli-commands/coder/printInitializationSummary.ts +23 -16
  77. package/src/cli/cli-commands/coder/run.ts +34 -23
  78. package/src/cli/cli-commands/coder/server.ts +43 -32
  79. package/src/cli/cli-commands/coder.ts +2 -6
  80. package/src/cli/cli-commands/common/$askForConfirmation.ts +12 -2
  81. package/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.ts +35 -0
  82. package/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.ts +37 -0
  83. package/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.ts +39 -0
  84. package/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.ts +26 -0
  85. package/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.ts +33 -0
  86. package/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.ts +33 -0
  87. package/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.ts +21 -0
  88. package/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.ts +17 -5
  89. package/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.ts +8 -3
  90. package/src/cli/cli-commands/common/harness/HarnessDefinition.ts +36 -0
  91. package/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts +8 -3
  92. package/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.ts +13 -2
  93. package/src/cli/cli-commands/common/questionsCliOptions.ts +89 -0
  94. package/src/cli/cli-commands/run.ts +3 -1
  95. package/src/cli/common/$deprecateCliCommand.ts +5 -0
  96. package/src/cli/common/$hideCliCommandFromHelp.ts +27 -0
  97. package/src/cli/common/$requireCliSubcommand.ts +35 -0
  98. package/src/cli/promptbookCli.ts +13 -9
  99. package/src/llm-providers/vercel/VercelExecutionToolsOptions.ts +7 -2
  100. package/src/llm-providers/vercel/createExecutionToolsFromVercelProvider.ts +18 -17
  101. package/src/other/templates/getTemplatesPipelineCollection.ts +677 -881
  102. package/src/version.ts +2 -2
  103. package/src/versions.txt +1 -0
  104. package/umd/index.umd.js +1253 -317
  105. package/umd/index.umd.js.map +1 -1
  106. package/umd/scripts/run-codex-prompts/common/createFreeDiskSpaceGuard.d.ts +25 -0
  107. package/umd/scripts/run-codex-prompts/common/formatUnknownErrorDetails.d.ts +4 -0
  108. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +21 -3
  109. package/umd/scripts/run-codex-prompts/main/listCoderPrompts.d.ts +1 -1
  110. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +77 -0
  111. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTracePath.d.ts +10 -0
  112. package/umd/scripts/run-codex-prompts/prompts/isPromptCompatibleWithRunner.d.ts +11 -6
  113. package/umd/scripts/run-codex-prompts/prompts/loadPromptFiles.d.ts +1 -1
  114. package/umd/scripts/run-codex-prompts/prompts/writePromptRunTrace.d.ts +19 -0
  115. package/umd/scripts/run-codex-prompts/runners/common/buildHarnessAuthenticationError.d.ts +20 -0
  116. package/umd/scripts/run-codex-prompts/runners/common/createAuthenticationAwarePromptRunner.d.ts +11 -0
  117. package/umd/scripts/run-codex-prompts/runners/common/extractHarnessAuthenticationFailureReason.d.ts +9 -0
  118. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +3 -0
  119. package/umd/src/book-2.0/book-language-documentation/getSafeCodeBlock.d.ts +1 -1
  120. package/umd/src/book-components/Chat/MockedChat/MockedChat.test.d.ts +2 -0
  121. package/umd/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.d.ts +2 -1
  122. package/umd/src/cli/cli-commands/coder/agentCliOptions.d.ts +21 -0
  123. package/umd/src/cli/cli-commands/coder/boilerplateTemplates.d.ts +8 -21
  124. package/umd/src/cli/cli-commands/coder/coderReferencedArtifacts.d.ts +33 -0
  125. package/umd/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.d.ts +4 -2
  126. package/umd/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.d.ts +2 -0
  127. package/umd/src/cli/cli-commands/coder/ensureDirectory.d.ts +6 -0
  128. package/umd/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.d.ts +6 -0
  129. package/umd/src/cli/cli-commands/coder/init.test.d.ts +1 -0
  130. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +7 -5
  131. package/umd/src/cli/cli-commands/coder/mergeStringRecordJsonFile.d.ts +19 -1
  132. package/umd/src/cli/cli-commands/coder/printCoderRunFailure.d.ts +6 -0
  133. package/umd/src/cli/cli-commands/common/$askForConfirmation.d.ts +4 -2
  134. package/umd/src/cli/cli-commands/common/$askForConfirmation.test.d.ts +1 -0
  135. package/umd/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.d.ts +12 -0
  136. package/umd/src/cli/cli-commands/common/disk-space/$assertSufficientFreeDiskSpace.test.d.ts +1 -0
  137. package/umd/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.d.ts +11 -0
  138. package/umd/src/cli/cli-commands/common/disk-space/$readFreeDiskSpaceStatus.test.d.ts +1 -0
  139. package/umd/src/cli/cli-commands/common/disk-space/FreeDiskSpaceStatus.d.ts +33 -0
  140. package/umd/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.d.ts +6 -0
  141. package/umd/src/cli/cli-commands/common/disk-space/formatFreeDiskSpaceBytes.test.d.ts +1 -0
  142. package/umd/src/cli/cli-commands/common/disk-space/formatLowFreeDiskSpaceWarning.d.ts +10 -0
  143. package/umd/src/cli/cli-commands/common/disk-space/freeDiskSpaceConstants.d.ts +28 -0
  144. package/umd/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.d.ts +7 -0
  145. package/umd/src/cli/cli-commands/common/disk-space/resolveFreeDiskSpaceLevel.test.d.ts +1 -0
  146. package/umd/src/cli/cli-commands/common/harness/$applyHarnessInstallationStatus.d.ts +2 -1
  147. package/umd/src/cli/cli-commands/common/harness/$ensureHarnessInstallations.d.ts +3 -2
  148. package/umd/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +26 -0
  149. package/umd/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.d.ts +4 -2
  150. package/umd/src/cli/cli-commands/common/promptbook-cli/$ensurePromptbookCliInstallations.d.ts +2 -1
  151. package/umd/src/cli/cli-commands/common/questionsCliOptions.d.ts +45 -0
  152. package/umd/src/cli/cli-commands/common/questionsCliOptions.test.d.ts +1 -0
  153. package/umd/src/cli/common/$deprecateCliCommand.d.ts +3 -0
  154. package/umd/src/cli/common/$hideCliCommandFromHelp.d.ts +11 -0
  155. package/umd/src/cli/common/$hideCliCommandFromHelp.test.d.ts +1 -0
  156. package/umd/src/cli/common/$requireCliSubcommand.d.ts +18 -0
  157. package/umd/src/cli/common/$requireCliSubcommand.test.d.ts +1 -0
  158. package/umd/src/llm-providers/vercel/VercelExecutionToolsOptions.d.ts +5 -2
  159. package/umd/src/version.d.ts +1 -1
  160. package/esm/src/cli/cli-commands/coder/agentCodingFile.d.ts +0 -13
  161. package/esm/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +0 -29
  162. package/src/cli/cli-commands/coder/agentCodingFile.ts +0 -25
  163. package/src/cli/cli-commands/common/harnessUpdateCliOptions.ts +0 -46
  164. package/umd/src/cli/cli-commands/coder/agentCodingFile.d.ts +0 -13
  165. package/umd/src/cli/cli-commands/common/harnessUpdateCliOptions.d.ts +0 -29
  166. /package/esm/src/cli/cli-commands/{common/harnessUpdateCliOptions.test.d.ts → coder/init.test.d.ts} +0 -0
  167. /package/{umd/src/cli/cli-commands/common/harnessUpdateCliOptions.test.d.ts → esm/src/cli/cli-commands/common/$askForConfirmation.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-33';
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`.
@@ -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,6 +42384,58 @@ 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
  *
@@ -42143,6 +42454,12 @@ const PROMPTS_DONE_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH$1, 'done');
42143
42454
  * @private internal utility of `ptbk coder`
42144
42455
  */
42145
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$1, 'templat
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 });
@@ -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',
@@ -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
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
  /**
@@ -44548,6 +44953,23 @@ function addPromptPriorityOptions(command) {
44548
44953
  // Note: [🟡] Code for CLI prompt priority options [promptPriorityCliOptions](src/cli/cli-commands/common/promptPriorityCliOptions.ts) should never be published outside of `@promptbook/cli`
44549
44954
  // Note: [💞] Ignore a discrepancy between file name and exported helper names
44550
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
+
44551
44973
  /**
44552
44974
  * Initializes `coder list` command for Promptbook CLI utilities.
44553
44975
  *
@@ -44565,19 +44987,27 @@ function $initializeCoderListCommand(program) {
44565
44987
  Features:
44566
44988
  - Lists only ready, fully authored prompts
44567
44989
  - Groups prompts from highest to lowest priority
44568
- - Optional --harness and --model filters show only prompts compatible with that runner
44990
+ - Optional --harness, --model and --agent filters show only prompts compatible with that selection
44569
44991
  - Does not start a coding harness or modify prompt files
44570
44992
  `));
44571
44993
  addPromptRunnerSelectionOptions(command);
44994
+ addCoderAgentOption(command);
44572
44995
  addPromptPriorityOptions(command);
44573
44996
  command.action(handleActionErrors(async (cliOptions) => {
44574
- const { priority, minPriority: minimumPriority, maxPriority: maximumPriority, } = cliOptions;
44997
+ const { agent, priority, minPriority: minimumPriority, maxPriority: maximumPriority, } = cliOptions;
44575
44998
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: false });
44576
- const promptRunnerIdentity = runnerOptions.agentName === undefined && runnerOptions.model === undefined
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
44577
45006
  ? undefined
44578
45007
  : {
44579
45008
  harnessName: runnerOptions.agentName,
44580
45009
  modelName: runnerOptions.model,
45010
+ agentReferences: resolvedCoderAgentBook === null || resolvedCoderAgentBook === void 0 ? void 0 : resolvedCoderAgentBook.agentReferences,
44581
45011
  };
44582
45012
  // Note: Import dynamically to avoid loading prompt parsing dependencies until this command is used.
44583
45013
  const { listCoderPrompts } = await Promise.resolve().then(function () { return listCoderPrompts$1; });
@@ -44645,7 +45075,7 @@ function parseOptionalPeriodDuration(optionName, value) {
44645
45075
  *
44646
45076
  * @private internal utility of `ptbk coder`
44647
45077
  */
44648
- async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName) {
45078
+ async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName, questionsOptions) {
44649
45079
  if (harnessName === undefined) {
44650
45080
  return;
44651
45081
  }
@@ -44656,7 +45086,7 @@ async function $ensureCoderHarnessGitignoreRules(projectPath, harnessName) {
44656
45086
  const { label } = getHarnessDefinition(harnessName);
44657
45087
  const formattedRules = formatGitignoreRules(missingRules);
44658
45088
  const entryLabel = missingRules.length === 1 ? 'entry' : 'entries';
44659
- 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);
44660
45090
  if (!isAdditionApproved) {
44661
45091
  console.info(colors.gray(`Skipped, add ${formattedRules} to \`.gitignore\` manually.`));
44662
45092
  return;
@@ -44681,22 +45111,24 @@ function formatGitignoreRules(rules) {
44681
45111
  */
44682
45112
  function $initializeCoderPingCommand(program) {
44683
45113
  const command = program.command('ping');
44684
- command.description(spaceTrim$1(`
44685
- 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
44686
45116
 
44687
- ${PROMPT_RUNNER_DESCRIPTION}
45117
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
44688
45118
 
44689
- Features:
44690
- - Verifies that the selected harness, model, thinking level and authentication really work
44691
- - Reports the answer of the harness, the response time and the reported usage
44692
- - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
44693
- - Optional --period keeps the quota window refreshing by pinging once per period until stopped
44694
- - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
44695
- - Checks that the selected harness is installed and up to date unless --no-harness-update is used
44696
- - Use --no-ui to stream the raw harness output instead of only the compact result
44697
- `));
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
+ `));
44698
45130
  addPromptRunnerSelectionOptions(command);
44699
- addHarnessUpdateOption(command);
45131
+ addQuestionsOption(command);
44700
45132
  addPromptRunnerRuntimeOptions(command);
44701
45133
  command.option('--period <duration>', spaceTrim$1(`
44702
45134
  Keep pinging once per period instead of pinging only once.
@@ -44705,11 +45137,11 @@ function $initializeCoderPingCommand(program) {
44705
45137
  command.action(handleActionErrors(async (cliOptions) => {
44706
45138
  const { period: periodValue } = cliOptions;
44707
45139
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(cliOptions, { isAgentRequired: true });
44708
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45140
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
44709
45141
  // Note: The period is validated before the harness installation check, so a mistyped duration fails fast
44710
45142
  const periodMs = parseOptionalPeriodDuration('--period', periodValue);
44711
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
44712
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45143
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45144
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
44713
45145
  const pingOptions = {
44714
45146
  agentName: runnerOptions.agentName,
44715
45147
  model: runnerOptions.model,
@@ -44732,6 +45164,159 @@ function $initializeCoderPingCommand(program) {
44732
45164
  // Note: [🟡] Code for CLI command [ping](src/cli/cli-commands/coder/ping.ts) should never be published outside of `@promptbook/cli`
44733
45165
  // Note: [💞] Ignore a discrepancy between file name and entity name
44734
45166
 
45167
+ /**
45168
+ * Free disk space below which `ptbk coder` refuses to start a new run.
45169
+ *
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.
45173
+ *
45174
+ * @private internal utility of `promptbookCli`
45175
+ */
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
45197
+
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;
45231
+ }
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
+ }
45243
+ }
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`
45319
+
44735
45320
  /**
44736
45321
  * Npm packages which directly provide the `ptbk` executable.
44737
45322
  *
@@ -45060,7 +45645,11 @@ function formatPromptbookCliInstallationLocation(installationLocation) {
45060
45645
  * @returns `true` when at least one installation was updated and the current coder run should stop
45061
45646
  * @private internal utility of `promptbookCli`
45062
45647
  */
45063
- 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
+ }
45064
45653
  if (!process.stdin.isTTY) {
45065
45654
  // Note: Non-interactive runs cannot approve an update and should not wait for registry checks
45066
45655
  return false;
@@ -45072,7 +45661,7 @@ async function $ensurePromptbookCliInstallations() {
45072
45661
  return false;
45073
45662
  }
45074
45663
  console.warn(colors.yellow(formatPromptbookCliInstallationWarning(outdatedStatuses)));
45075
- const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?');
45664
+ const isUpdateApproved = await $askForNpmPackageInstallationApproval('Update Promptbook CLI now?', questionsOptions);
45076
45665
  if (!isUpdateApproved) {
45077
45666
  reportManualPromptbookCliUpdateCommands(outdatedStatuses);
45078
45667
  return false;
@@ -45139,6 +45728,17 @@ function isTestBeforeMode(value) {
45139
45728
  return TEST_BEFORE_MODE_VALUES.includes(value);
45140
45729
  }
45141
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
+
45142
45742
  /**
45143
45743
  * Initializes `coder run` command for Promptbook CLI utilities
45144
45744
  *
@@ -45148,10 +45748,12 @@ function isTestBeforeMode(value) {
45148
45748
  */
45149
45749
  function $initializeCoderRunCommand(program) {
45150
45750
  const command = program.command('run');
45151
- command.description(spaceTrim$1(`
45751
+ command.description(spaceTrim$1((block) => `
45152
45752
  Execute coding prompts through selected AI agent
45153
45753
 
45154
- ${PROMPT_RUNNER_DESCRIPTION}
45754
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45755
+
45756
+ ${block(QUESTIONS_DESCRIPTION)}
45155
45757
 
45156
45758
  Features:
45157
45759
  - Automatically stages and commits changes with agent identity unless --no-commit is used
@@ -45161,9 +45763,11 @@ function $initializeCoderRunCommand(program) {
45161
45763
  - Optional post-commit git push with explicit --auto-push opt-in
45162
45764
  - Optional pre-prompt git pull with explicit --auto-pull opt-in
45163
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
45164
45767
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
45165
45768
  - Optional --no-ui keeps plain streaming console output for logging and debugging
45166
- - 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
45167
45771
  - Offers to add missing project-local ignore rules for the selected harness
45168
45772
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
45169
45773
  - Supports GPG signing of commits
@@ -45174,8 +45778,8 @@ function $initializeCoderRunCommand(program) {
45174
45778
  `));
45175
45779
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45176
45780
  addPromptRunnerSelectionOptions(command);
45177
- addHarnessUpdateOption(command);
45178
- 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);
45179
45783
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45180
45784
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45181
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)`)
@@ -45214,7 +45818,7 @@ function $initializeCoderRunCommand(program) {
45214
45818
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45215
45819
  isAgentRequired: !dryRun,
45216
45820
  });
45217
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45821
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45218
45822
  // [1] Parse the wait options and --no-auto:
45219
45823
  // default: run automatically through the queue (no waiting between prompts)
45220
45824
  // --no-auto: wait for user confirmation before each prompt (interactive mode)
@@ -45222,11 +45826,15 @@ function $initializeCoderRunCommand(program) {
45222
45826
  // --wait-between-prompts: pace from start of one prompt to start of next
45223
45827
  // --wait-after-error: wait before retrying after an error (default 10m)
45224
45828
  const waitForUser = !auto;
45225
- 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)) {
45226
45834
  return process.exit(0);
45227
45835
  }
45228
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45229
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45836
+ await $ensureHarnessInstallations([runnerOptions.agentName], questionsOptions);
45837
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName, questionsOptions);
45230
45838
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45231
45839
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45232
45840
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45259,6 +45867,7 @@ function $initializeCoderRunCommand(program) {
45259
45867
  allowDestructiveAutoMigrate,
45260
45868
  autoPush: runnerOptions.autoPush,
45261
45869
  autoPull: runnerOptions.autoPull,
45870
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45262
45871
  };
45263
45872
  // Note: Import the function dynamically to avoid loading heavy dependencies until needed
45264
45873
  const { runCodexPrompts } = await Promise.resolve().then(function () { return runCodexPrompts$1; });
@@ -45268,8 +45877,7 @@ function $initializeCoderRunCommand(program) {
45268
45877
  }
45269
45878
  catch (error) {
45270
45879
  assertsError(error);
45271
- console.error(colors.bgRed(`${error.name}`));
45272
- console.error(colors.red(error.stack || error.message));
45880
+ printCoderRunFailure(error);
45273
45881
  return process.exit(1);
45274
45882
  }
45275
45883
  return process.exit(0);
@@ -45313,28 +45921,31 @@ const DEFAULT_CODER_SERVER_PORT = '4441';
45313
45921
  */
45314
45922
  function $initializeCoderServerCommand(program) {
45315
45923
  const command = program.command('server');
45316
- command.description(spaceTrim$1(`
45317
- 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
45318
45926
 
45319
- ${PROMPT_RUNNER_DESCRIPTION}
45927
+ ${block(PROMPT_RUNNER_DESCRIPTION)}
45320
45928
 
45321
- Features:
45322
- - Runs the same prompt processing as \`ptbk coder run\`
45323
- - Checks that the selected harness is installed and up to date on startup unless --no-harness-update is used
45324
- - Offers to add missing project-local ignore rules for the selected harness
45325
- - Does not exit when all prompts are done; polls for new prompt files instead
45326
- - Serves a kanban board at http://localhost:<port> for visual progress tracking
45327
- - Allows editing prompt files directly from the browser (Trello-style)
45328
- - Play / pause button in the browser stays in sync with the CLI pause state
45329
- - Terminal controls match \`ptbk coder run\`: P pause/resume, S skip waiting, X end after current prompt
45330
- `));
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
+ `));
45331
45942
  command.addOption(new Option('--port <port>', 'Port to start the coder server on')
45332
45943
  .env('PTBK_CODER_SERVER_PORT')
45333
45944
  .default(DEFAULT_CODER_SERVER_PORT));
45334
45945
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45335
45946
  addPromptRunnerSelectionOptions(command);
45336
- addHarnessUpdateOption(command);
45337
- 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);
45338
45949
  command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
45339
45950
  command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
45340
45951
  command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
@@ -45363,11 +45974,15 @@ function $initializeCoderServerCommand(program) {
45363
45974
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
45364
45975
  isAgentRequired: !dryRun,
45365
45976
  });
45366
- const { isHarnessUpdateCheckEnabled } = normalizeHarnessUpdateCliOptions(cliOptions);
45367
- await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
45368
- await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
45977
+ const questionsOptions = normalizeQuestionsCliOptions(cliOptions);
45369
45978
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
45370
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);
45371
45986
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
45372
45987
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
45373
45988
  const waitAfterError = parseOptionalWaitDuration(waitAfterErrorValue, DEFAULT_WAIT_AFTER_ERROR_MS$1);
@@ -45397,6 +46012,7 @@ function $initializeCoderServerCommand(program) {
45397
46012
  allowDestructiveAutoMigrate,
45398
46013
  autoPush: runnerOptions.autoPush,
45399
46014
  autoPull: runnerOptions.autoPull,
46015
+ isAskingQuestionsEnabled: questionsOptions.isAskingQuestionsEnabled,
45400
46016
  };
45401
46017
  // Note: Import dynamically to avoid loading heavy dependencies until needed
45402
46018
  const { runCodexPromptsServer } = await Promise.resolve().then(function () { return runCodexPromptsServer$1; });
@@ -45405,8 +46021,7 @@ function $initializeCoderServerCommand(program) {
45405
46021
  }
45406
46022
  catch (error) {
45407
46023
  assertsError(error);
45408
- console.error(colors.bgRed(`${error.name}`));
45409
- console.error(colors.red(error.stack || error.message));
46024
+ printCoderRunFailure(error);
45410
46025
  return process.exit(1);
45411
46026
  }
45412
46027
  return process.exit(0);
@@ -45595,11 +46210,7 @@ function $initializeCoderCommand(program) {
45595
46210
  $initializeCoderVerifyCommand(coderCommand);
45596
46211
  $initializeCoderFindFreshEmojiTagCommand(coderCommand);
45597
46212
  // If no subcommand is provided, show help
45598
- coderCommand.action(() => {
45599
- console.info(colors.yellow('Please specify a subcommand.'));
45600
- console.info('');
45601
- coderCommand.help();
45602
- });
46213
+ $requireCliSubcommand(coderCommand);
45603
46214
  }
45604
46215
  // Note: [🟡] Code for CLI command [coder](src/cli/cli-commands/coder.ts) should never be published outside of `@promptbook/cli`
45605
46216
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -56189,7 +56800,9 @@ function shouldRunInteractiveChatbot(options) {
56189
56800
  * @private internal function of `promptbookCli`
56190
56801
  */
56191
56802
  function $initializeRunCommand(program) {
56192
- 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');
56193
56806
  configureRunCommand(runCommand);
56194
56807
  runCommand.action(handleActionErrors((pipelineSource, cliOptions) => runCommandAction(pipelineSource, cliOptions)));
56195
56808
  }
@@ -58028,9 +58641,24 @@ function $addGlobalOptionsToCommand(command) {
58028
58641
  command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
58029
58642
  }
58030
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
+
58031
58656
  /**
58032
58657
  * Marks one CLI command as deprecated while keeping it available for existing callers.
58033
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
+ *
58034
58662
  * @private utility of CLI
58035
58663
  */
58036
58664
  function $deprecateCliCommand(command, deprecationMessage) {
@@ -58042,6 +58670,7 @@ function $deprecateCliCommand(command, deprecationMessage) {
58042
58670
  command.hook('preAction', () => {
58043
58671
  console.warn(colors.yellow(createDeprecatedCliCommandWarning(command, deprecationMessage)));
58044
58672
  });
58673
+ $hideCliCommandFromHelp(command);
58045
58674
  }
58046
58675
  /**
58047
58676
  * Creates the runtime warning printed before a deprecated CLI command action starts.
@@ -58109,10 +58738,17 @@ async function promptbookCli() {
58109
58738
  program.version(PROMPTBOOK_ENGINE_VERSION);
58110
58739
  program.description(CLAIM);
58111
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);
58112
58748
  $initializeAboutCommand(program);
58749
+ $initializeHelloCommand(program);
58113
58750
  $initializeRunCommand(program);
58114
58751
  $initializeLoginCommand(program);
58115
- $initializeHelloCommand(program);
58116
58752
  $initializeMakeCommand(program);
58117
58753
  $initializePrettifyCommand(program);
58118
58754
  $initializeTestCommand(program);
@@ -58120,16 +58756,13 @@ async function promptbookCli() {
58120
58756
  $initializeListScrapersCommand(program);
58121
58757
  $initializeStartAgentsServerCommand(program);
58122
58758
  $initializeStartPipelinesServerCommand(program);
58123
- $initializeAgentCommand(program);
58124
- $initializeAgentFolderCommand(program);
58125
- $initializeAgentsServerCommand(program);
58126
- $initializeCoderCommand(program);
58127
58759
  $deprecateTopLevelCommands(program);
58128
58760
  // TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
58129
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`
58130
58764
  if (commandLineArguments.length === 0) {
58131
- program.outputHelp();
58132
- return process.exit(0);
58765
+ return $reportMissingCliSubcommand(program);
58133
58766
  }
58134
58767
  program.parse(process.argv);
58135
58768
  }
@@ -58143,7 +58776,7 @@ function isTopLevelVersionRequested(commandLineArguments) {
58143
58776
  return firstCommandLineArgument !== undefined && VERSION_OPTION_ARGUMENTS.has(firstCommandLineArgument);
58144
58777
  }
58145
58778
  /**
58146
- * 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.
58147
58780
  */
58148
58781
  function $deprecateTopLevelCommands(program) {
58149
58782
  for (const command of program.commands) {
@@ -58154,7 +58787,6 @@ function $deprecateTopLevelCommands(program) {
58154
58787
  }
58155
58788
  }
58156
58789
  // Note: [🟡] Code for CLI program [promptbookCli](src/cli/promptbookCli.ts) should never be published outside of `@promptbook/cli`
58157
- // TODO: [🧠] Maybe `run` command the default, instead of `ptbk run ./foo.book` -> `ptbk ./foo.book`
58158
58790
  // TODO: [🥠] Do not export, its just for CLI script
58159
58791
  // TODO: [🕌] When more functionalities, rename
58160
58792
  // Note: 11:11
@@ -59266,7 +59898,9 @@ const VERCEL_PROVIDER_PROFILE = {
59266
59898
  */
59267
59899
  function createExecutionToolsFromVercelProvider(options) {
59268
59900
  let { title, description } = options;
59269
- 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
59270
59904
  if (!/Vercel/i.test(title)) {
59271
59905
  title = `${title} (through Vercel)`;
59272
59906
  // <- TODO: [🧈] Maybe standartize the suffix
@@ -59307,10 +59941,7 @@ function createExecutionToolsFromVercelProvider(options) {
59307
59941
 
59308
59942
  `));
59309
59943
  }
59310
- const model = await vercelProvider.chat(modelName, {
59311
- user: (userId === null || userId === void 0 ? void 0 : userId.toString()) || undefined,
59312
- ...additionalChatSettings,
59313
- });
59944
+ const model = await vercelProvider.chat(modelName);
59314
59945
  const rawPromptContent = templateParameters(content, { ...parameters, modelName });
59315
59946
  // Support for passing a chat thread (multi-message conversation)
59316
59947
  let promptMessages;
@@ -59348,14 +59979,9 @@ function createExecutionToolsFromVercelProvider(options) {
59348
59979
  }
59349
59980
  const rawRequest = {
59350
59981
  // <- TODO: [☂]
59351
- inputFormat: 'messages',
59352
- mode: {
59353
- type: 'regular',
59354
- tools: [
59355
- /* <- TODO: Pass the tools */
59356
- ],
59357
- },
59358
59982
  prompt: promptMessages,
59983
+ // <- TODO: Pass the tools via `tools`
59984
+ ...additionalChatSettings,
59359
59985
  };
59360
59986
  const start = $getCurrentDate();
59361
59987
  if (options.isVerbose) {
@@ -59372,28 +59998,31 @@ function createExecutionToolsFromVercelProvider(options) {
59372
59998
  if (options.isVerbose) {
59373
59999
  console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
59374
60000
  }
59375
- 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) {
59376
60004
  throw new PipelineExecutionError('No response message');
59377
60005
  }
60006
+ const responseText = responseTextParts.join('');
59378
60007
  const complete = $getCurrentDate();
59379
60008
  const duration = uncertainNumber((new Date(complete).getTime() - new Date(start).getTime()) / 1000);
59380
60009
  const usage = {
59381
60010
  price: UNCERTAIN_ZERO_VALUE,
59382
60011
  duration,
59383
60012
  input: {
59384
- tokensCount: uncertainNumber(rawResponse.usage.promptTokens),
60013
+ tokensCount: uncertainNumber(rawResponse.usage.inputTokens),
59385
60014
  ...computeUsageCounts(rawPromptContent),
59386
60015
  },
59387
60016
  output: {
59388
- tokensCount: uncertainNumber(rawResponse.usage.completionTokens),
59389
- ...computeUsageCounts(rawResponse.text),
60017
+ tokensCount: uncertainNumber(rawResponse.usage.outputTokens),
60018
+ ...computeUsageCounts(responseText),
59390
60019
  },
59391
60020
  };
59392
60021
  return exportJson({
59393
60022
  name: 'promptResult',
59394
60023
  message: `Result of \`createExecutionToolsFromVercelProvider.callChatModel\``,
59395
60024
  value: {
59396
- content: rawResponse.text,
60025
+ content: responseText,
59397
60026
  modelName,
59398
60027
  timing: {
59399
60028
  start,
@@ -74698,13 +75327,15 @@ function findFirstNonEmptyLineIndex(lines, startLine, endLine) {
74698
75327
  }
74699
75328
 
74700
75329
  /**
74701
- * 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.
74702
75331
  *
74703
- * A prompt status line can contain one or more backtick-delimited model or harness
74704
- * names. Matching is intentionally based on normalized substrings so a token such as
74705
- * `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.
74706
75336
  */
74707
75337
  function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
75338
+ var _a;
74708
75339
  if (promptRunnerIdentity === undefined) {
74709
75340
  return true;
74710
75341
  }
@@ -74713,7 +75344,11 @@ function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
74713
75344
  if (requiredRunnerTokens.length === 0) {
74714
75345
  return true;
74715
75346
  }
74716
- 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
+ ]
74717
75352
  .filter((name) => name !== undefined && name.trim() !== '')
74718
75353
  .map((name) => normalizeToKebabCase(name))
74719
75354
  .filter((name) => name !== '');
@@ -74724,7 +75359,7 @@ function isPromptCompatibleWithRunner(file, section, promptRunnerIdentity) {
74724
75359
  });
74725
75360
  }
74726
75361
  /**
74727
- * Extracts model and harness tokens from a prompt status line.
75362
+ * Extracts model, harness and Book-agent tokens from a prompt status line.
74728
75363
  */
74729
75364
  function extractPromptRunnerTokens(statusLine) {
74730
75365
  return Array.from(statusLine.matchAll(/`([^`]+)`/gu))
@@ -74739,7 +75374,7 @@ function extractPromptRunnerTokens(statusLine) {
74739
75374
  *
74740
75375
  * @private internal constant of `parsePromptFile`
74741
75376
  */
74742
- const PROMPT_STATUS_MARKER_PATTERN = /^\[(?: |-|[xX]|\^|!)\]/u;
75377
+ const PROMPT_STATUS_LINE_PATTERN = /^\[(?<marker> |-|\.|[xX]|\^|!)\](?<details>.*)$/u;
74743
75378
  /**
74744
75379
  * Parses a prompt markdown file into sections and metadata.
74745
75380
  */
@@ -74763,7 +75398,7 @@ function parsePromptFile(filePath, content) {
74763
75398
  if (firstNonEmptyLine !== undefined) {
74764
75399
  const statusLine = (lines[firstNonEmptyLine] || '').trim();
74765
75400
  const parsedStatus = parseStatusLine(statusLine);
74766
- 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');
74767
75402
  const priority = (_b = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.priority) !== null && _b !== void 0 ? _b : 0;
74768
75403
  sections.push({
74769
75404
  index,
@@ -74791,51 +75426,47 @@ function parsePromptFile(filePath, content) {
74791
75426
  * For [x] done, [!] failed and [^] in-progress prompts, allow metadata after the status marker.
74792
75427
  */
74793
75428
  function parseStatusLine(line) {
74794
- var _a, _b, _c, _d;
74795
- // For done prompts [x], allow any content after (for cost/time metadata)
74796
- const doneMatch = line.match(/^\[(?<status>[xX])\]/);
74797
- 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).
74798
75435
  return { status: 'done', priority: 0 };
74799
75436
  }
74800
- // For failed prompts [!], allow any content after (for failure metadata)
74801
- const failedMatch = line.match(/^\[(?<status>!)\]/);
74802
- if (failedMatch) {
75437
+ if (marker === '!') {
75438
+ // For failed prompts [!], allow any content after (for failure metadata).
74803
75439
  return { status: 'failed', priority: 0 };
74804
75440
  }
74805
- // For in-progress prompts [^], allow any content after (for the steps recorded so far)
74806
- const inProgressMatch = line.match(/^\[(?<status>\^)\]/);
74807
- if (inProgressMatch) {
75441
+ if (marker === '^') {
75442
+ // For in-progress prompts [^], allow any content after (for the steps recorded so far).
74808
75443
  return { status: 'in-progress', priority: 0 };
74809
75444
  }
74810
- // For not-ready [-], keep the historical clean-line syntax.
74811
- 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)) {
74812
75447
  return { status: 'not-ready', priority: 0 };
74813
75448
  }
74814
- // Todo [ ] may contain backtick-delimited model/harness tokens and priority markers
74815
- // before or after those tokens. Other trailing text remains an invalid status line.
74816
- const todoMatch = line.match(/^\[ \](?<details>.*)$/u);
74817
- if (!todoMatch) {
75449
+ if (marker !== ' ') {
74818
75450
  return undefined;
74819
75451
  }
74820
- 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.
74821
75454
  const isPriorityOnly = /^[!\s]*$/u.test(details);
74822
75455
  const hasPromptRunnerTokens = extractPromptRunnerTokens(line).length > 0;
74823
75456
  if (details.trim() !== '' && !isPriorityOnly && !hasPromptRunnerTokens) {
74824
75457
  return undefined;
74825
75458
  }
74826
- 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 };
74827
75460
  }
75461
+
74828
75462
  /**
74829
- * 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.
74830
75464
  *
74831
- * @private internal utility of `parsePromptFile`
75465
+ * @private internal constant of `loadPromptFiles`
74832
75466
  */
74833
- function hasPromptStatusMarker(line) {
74834
- return PROMPT_STATUS_MARKER_PATTERN.test(line);
74835
- }
74836
-
75467
+ const PTBK_CODER_IGNORE_MARKER = '<!--ptbk-coder-ignore-->';
74837
75468
  /**
74838
- * 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.
74839
75470
  */
74840
75471
  async function loadPromptFiles(promptsDir) {
74841
75472
  const entries = await readdir(promptsDir, { withFileTypes: true });
@@ -74846,6 +75477,9 @@ async function loadPromptFiles(promptsDir) {
74846
75477
  const promptFiles = [];
74847
75478
  for (const filePath of files) {
74848
75479
  const content = await readFile(filePath, 'utf-8');
75480
+ if (content.includes(PTBK_CODER_IGNORE_MARKER)) {
75481
+ continue;
75482
+ }
74849
75483
  promptFiles.push(parsePromptFile(filePath, content));
74850
75484
  }
74851
75485
  return promptFiles;
@@ -74955,6 +75589,77 @@ var findUnwrittenPrompts$1 = /*#__PURE__*/Object.freeze({
74955
75589
  findUnwrittenPrompts: findUnwrittenPrompts
74956
75590
  });
74957
75591
 
75592
+ /**
75593
+ * Reads an optional agent `.book` file and prepares the references that route prompts to it.
75594
+ *
75595
+ * Returns `undefined` when no agent path is provided.
75596
+ */
75597
+ async function resolveCoderAgentBook(agentBookReference, currentWorkingDirectory) {
75598
+ const normalizedAgentBookReference = agentBookReference === null || agentBookReference === void 0 ? void 0 : agentBookReference.trim();
75599
+ if (!normalizedAgentBookReference) {
75600
+ return undefined;
75601
+ }
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;
75632
+ }
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
+
74958
75663
  /**
74959
75664
  * Lists todo prompts that are ready to run (no authoring placeholders).
74960
75665
  */
@@ -75037,23 +75742,6 @@ var listCoderPrompts$1 = /*#__PURE__*/Object.freeze({
75037
75742
  listCoderPrompts: listCoderPrompts
75038
75743
  });
75039
75744
 
75040
- /**
75041
- * Formats one unknown error-like value into its readable message without the stack trace.
75042
- *
75043
- * Use this instead of `formatUnknownErrorDetails` whenever the text is shown to the user, for example
75044
- * inside a branded error, where the stack of the wrapped error would only bury the actual cause.
75045
- */
75046
- function formatUnknownErrorMessage(error) {
75047
- if (error instanceof Error) {
75048
- return error.message;
75049
- }
75050
- if (typeof error === 'string') {
75051
- return error;
75052
- }
75053
- const serializedError = JSON.stringify(error, null, 2);
75054
- return serializedError !== null && serializedError !== void 0 ? serializedError : String(error);
75055
- }
75056
-
75057
75745
  /**
75058
75746
  * Builds a normalized temporary shell script path for prompt runners.
75059
75747
  */
@@ -75352,7 +76040,7 @@ const DEFAULT_WAIT_AFTER_ERROR_MS = 10 * 60 * 1000;
75352
76040
  /**
75353
76041
  * CLI usage text for this script.
75354
76042
  */
75355
- 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]';
75356
76044
  /**
75357
76045
  * Top-level flags supported by this command.
75358
76046
  */
@@ -75360,6 +76048,7 @@ const KNOWN_OPTION_FLAGS = new Set([
75360
76048
  '--dry-run',
75361
76049
  '--harness',
75362
76050
  '--model',
76051
+ '--agent',
75363
76052
  '--context',
75364
76053
  '--test',
75365
76054
  '--test-before',
@@ -75398,6 +76087,7 @@ function parseRunOptions(args) {
75398
76087
  }
75399
76088
  }
75400
76089
  const model = readOptionValue(args, '--model');
76090
+ const agent = readOptionValue(args, '--agent');
75401
76091
  const context = readOptionValue(args, '--context');
75402
76092
  const hasTestCommandFlag = args.includes('--test');
75403
76093
  const testCommand = readVariadicOptionValue(args, '--test');
@@ -75474,6 +76164,7 @@ function parseRunOptions(args) {
75474
76164
  noUi,
75475
76165
  agentName,
75476
76166
  model,
76167
+ agent,
75477
76168
  context,
75478
76169
  testCommand,
75479
76170
  testBefore,
@@ -75908,30 +76599,43 @@ async function recordPromptDurationSample(key, promptDurationMs) {
75908
76599
  // Note: [🟡] Code in this file is only used from `@promptbook/cli` orchestration scripts and is not shipped to other consumers.
75909
76600
 
75910
76601
  /**
75911
- * 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.
75912
76603
  *
75913
- * 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
75914
76609
  */
75915
- async function resolveCoderAgent(agentPath, currentWorkingDirectory) {
75916
- if (!agentPath) {
75917
- return undefined;
75918
- }
75919
- const resolvedAgentPath = resolve(currentWorkingDirectory, agentPath);
75920
- const agentSource = await readFile(resolvedAgentPath, 'utf-8').catch((error) => {
75921
- if (error.code === 'ENOENT' || error.code === 'EISDIR') {
75922
- throw new NotFoundError(spaceTrim(`
75923
- 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.
75924
76625
 
75925
- Pass a path to a \`.book\` file in \`--agent\`.
75926
- `));
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;
75927
76633
  }
75928
- throw error;
75929
- });
75930
- return {
75931
- agentSource: agentSource,
75932
- systemMessage: await createAgentRunnerSystemMessage(agentSource),
76634
+ announcePauseTargetLabel(`continuing with only ${formatFreeDiskSpaceBytes(freeDiskSpaceStatus.availableBytes)} of free disk space left`);
76635
+ requestPause();
75933
76636
  };
75934
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`
75935
76639
 
75936
76640
  /**
75937
76641
  * Refresh interval used by the countdown display while waiting.
@@ -77902,6 +78606,169 @@ async function writePromptFile(file) {
77902
78606
  await writeFile(file.path, content, 'utf-8');
77903
78607
  }
77904
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
+
77905
78772
  /**
77906
78773
  * Refreshes optional harness subscription usage in the shared coder-run UI state.
77907
78774
  *
@@ -78024,7 +78891,9 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
78024
78891
  catch (error) {
78025
78892
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
78026
78893
  lastError = error;
78027
- 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) {
78028
78897
  break;
78029
78898
  }
78030
78899
  await waitAfterErrorBeforeRetry({
@@ -78046,6 +78915,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
78046
78915
  attemptCount,
78047
78916
  error: lastError,
78048
78917
  options,
78918
+ logPath,
78049
78919
  roundCommitScope,
78050
78920
  uiHandle,
78051
78921
  waitForRequestedPause,
@@ -78162,6 +79032,17 @@ async function finalizeSuccessfulPromptRound(options) {
78162
79032
  // Note: The prompt status is always written into the original project, an isolated round transports
78163
79033
  // its own changes back through the merge instead
78164
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
+ });
78165
79046
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78166
79047
  await recordPromptDurationInEstimateCache({
78167
79048
  options: runOptions,
@@ -78209,7 +79090,7 @@ async function finalizeSuccessfulPromptRound(options) {
78209
79090
  * Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
78210
79091
  */
78211
79092
  async function finalizeFailedPromptRound(options) {
78212
- 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;
78213
79094
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
78214
79095
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
78215
79096
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
@@ -78235,8 +79116,42 @@ async function finalizeFailedPromptRound(options) {
78235
79116
  modelName: runnerMetadata.modelName,
78236
79117
  error,
78237
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
+ });
78238
79130
  await normalizeLineEndingsForCurrentRound(runOptions, roundProjectPath, roundCommitScope);
78239
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
+ }
78240
79155
  /**
78241
79156
  * Waits for the optional user confirmation immediately before creating the commit.
78242
79157
  */
@@ -78885,7 +79800,13 @@ async function recordIsolationMergeFailure(options, worktree, failureDetails) {
78885
79800
  await commitChanges(buildCoderIsolationMergeFailureCommitMessage(worktree), {
78886
79801
  autoPush: options.options.autoPush,
78887
79802
  projectPath: worktree.projectPath,
78888
- 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)),
78889
79810
  });
78890
79811
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(mergeFailureError.message);
78891
79812
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Merging \`${worktree.taskName}\` failed, worktree kept for a manual merge`);
@@ -79160,12 +80081,23 @@ const PRE_CODING_TEST_CHANGES_COMMIT_MESSAGE = 'test: Apply changes made by pre-
79160
80081
  * @public exported from `@promptbook/cli`
79161
80082
  */
79162
80083
  async function runCodexPrompts(providedOptions) {
80084
+ var _a;
79163
80085
  const options = normalizeRunOptions(providedOptions !== null && providedOptions !== void 0 ? providedOptions : parseRunOptions(process.argv.slice(2)));
79164
80086
  validateRunCodexPromptOptions(options);
79165
80087
  resetCoderRunControls();
79166
80088
  const runStartDate = moment();
79167
80089
  const { isRichUiEnabled, progressDisplay, uiHandle } = createRunDisplays(options, runStartDate);
79168
- 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
+ });
79169
80101
  startPauseListenerIfNeeded(isRichUiEnabled);
79170
80102
  try {
79171
80103
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
@@ -79178,6 +80110,7 @@ async function runCodexPrompts(providedOptions) {
79178
80110
  const promptRunnerIdentity = {
79179
80111
  harnessName: options.agentName,
79180
80112
  modelName: actualRunnerModel,
80113
+ agentReferences: resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.agentReferences,
79181
80114
  };
79182
80115
  console.info(colors.green(`Running prompts with ${runner.name}`));
79183
80116
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
@@ -79614,11 +80547,14 @@ async function commitTestBeforeChangesIfNeeded(options) {
79614
80547
  * Creates a pause waiter that keeps the progress display and rich UI in sync.
79615
80548
  */
79616
80549
  function createPauseWaiter(options) {
79617
- const { isRichUiEnabled, progressDisplay, uiHandle } = options;
80550
+ const { isRichUiEnabled, progressDisplay, uiHandle, guardFreeDiskSpace } = options;
79618
80551
  return async (checkpoint) => {
79619
80552
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase(checkpoint.phase);
79620
80553
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(checkpoint.statusMessage);
79621
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();
79622
80558
  await checkPause({
79623
80559
  silent: isRichUiEnabled,
79624
80560
  onPaused: () => {
@@ -79737,7 +80673,7 @@ function finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiH
79737
80673
  return false;
79738
80674
  }
79739
80675
  if (promptQueueSnapshot.stats.forAgent > 0) {
79740
- 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);
79741
80677
  }
79742
80678
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79743
80679
  announceRunCompletion('No prompts ready for agent.', colors.yellow, isRichUiEnabled, uiHandle);
@@ -79782,7 +80718,7 @@ function finishWhenEndAfterCurrentPromptIsRequested(options) {
79782
80718
  function announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle) {
79783
80719
  let message;
79784
80720
  if (promptQueueSnapshot.stats.forAgent > 0) {
79785
- 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...';
79786
80722
  }
79787
80723
  else if (promptQueueSnapshot.stats.toBeWritten > 0) {
79788
80724
  message = 'No prompts ready for agent. Watching for changes...';