@promptbook/cli 0.112.0-121 → 0.112.0-124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/agents/default/developer.book +23 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/messages/route.ts +4 -11
- package/apps/agents-server/src/app/api/scrape/route.ts +18 -0
- package/apps/agents-server/src/tools/createAgentProgressTools.ts +6 -4
- package/apps/agents-server/src/utils/assertSafeUrl.ts +136 -0
- package/apps/agents-server/src/utils/authenticateUser.ts +2 -1
- package/apps/agents-server/src/utils/getCurrentUser.ts +2 -1
- package/apps/agents-server/src/utils/isAdminPasswordEqual.ts +28 -0
- package/apps/agents-server/src/utils/isUserGlobalAdmin.ts +3 -1
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobPersistenceController.ts +21 -0
- package/apps/agents-server/src/utils/userChat/retryUserChatJob.ts +4 -0
- package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +2 -0
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +288 -0
- package/esm/index.es.js +1430 -283
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +6 -0
- package/esm/scripts/run-codex-prompts/main/findUnwrittenPrompts.d.ts +19 -0
- package/esm/scripts/run-codex-prompts/main/runCodexPromptsServer.d.ts +27 -0
- package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
- package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +9 -0
- package/esm/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +23 -0
- package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +9 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.ts +79 -0
- package/src/cli/cli-commands/coder/find-unwritten.ts +58 -0
- package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
- package/src/cli/cli-commands/coder/init.ts +6 -1
- package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +10 -0
- package/src/cli/cli-commands/coder/printInitializationSummary.ts +3 -0
- package/src/cli/cli-commands/coder/run.ts +26 -11
- package/src/cli/cli-commands/coder/server.ts +239 -0
- package/src/cli/cli-commands/coder.ts +6 -0
- package/src/other/templates/getTemplatesPipelineCollection.ts +727 -1105
- package/src/version.ts +2 -2
- package/src/versions.txt +3 -1
- package/umd/index.umd.js +1431 -284
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +6 -0
- package/umd/scripts/run-codex-prompts/main/findUnwrittenPrompts.d.ts +19 -0
- package/umd/scripts/run-codex-prompts/main/runCodexPromptsServer.d.ts +27 -0
- package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
- package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +9 -0
- package/umd/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +23 -0
- package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +9 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs/promises'), require('path'), require('child_process'), require('crypto'), require('fs'), require('dotenv'), require('moment'), require('waitasecond'), require('prompts'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('jszip'), require('@mozilla/readability'), require('jsdom'), require('crypto-js'), require('showdown'), require('glob-promise'), require('http'), require('express'), require('socket.io'), require('express-openapi-validator'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('rxjs'), require('
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs/promises', 'path', 'child_process', 'crypto', 'fs', 'dotenv', 'moment', 'waitasecond', 'prompts', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'jszip', '@mozilla/readability', 'jsdom', 'crypto-js', 'showdown', 'glob-promise', 'http', 'express', 'socket.io', 'express-openapi-validator', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'rxjs', '
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.promises, global.path, global.child_process, global.crypto, global.fs, global.dotenv, global.moment, global.waitasecond, global.prompts, global.hexEncoder, global.sha256, global.socket_ioClient, global.JSZip, global.readability, global.jsdom, global.CryptoJS, global.showdown, global.glob, global.http, global.express, global.socket_io, global.OpenApiValidator, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.rxjs, global.
|
|
5
|
-
})(this, (function (exports, colors, commander, _spaceTrim, promises, path, child_process, crypto, fs, dotenv, moment, waitasecond, prompts, hexEncoder, sha256, socket_ioClient, JSZip, readability, jsdom, CryptoJS, showdown, glob, http, express, socket_io, OpenApiValidator, swaggerUi, react, server, Anthropic, Bottleneck, openai, rxjs,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs/promises'), require('path'), require('child_process'), require('crypto'), require('fs'), require('dotenv'), require('moment'), require('waitasecond'), require('prompts'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('jszip'), require('@mozilla/readability'), require('jsdom'), require('crypto-js'), require('showdown'), require('glob-promise'), require('http'), require('express'), require('socket.io'), require('express-openapi-validator'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('rxjs'), require('@openai/agents'), require('openai'), require('typescript'), require('ignore'), require('readline'), require('mime-types'), require('papaparse'), require('url'), require('events'), require('pg'), require('@supabase/supabase-js')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs/promises', 'path', 'child_process', 'crypto', 'fs', 'dotenv', 'moment', 'waitasecond', 'prompts', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'jszip', '@mozilla/readability', 'jsdom', 'crypto-js', 'showdown', 'glob-promise', 'http', 'express', 'socket.io', 'express-openapi-validator', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'rxjs', '@openai/agents', 'openai', 'typescript', 'ignore', 'readline', 'mime-types', 'papaparse', 'url', 'events', 'pg', '@supabase/supabase-js'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.promises, global.path, global.child_process, global.crypto, global.fs, global.dotenv, global.moment, global.waitasecond, global.prompts, global.hexEncoder, global.sha256, global.socket_ioClient, global.JSZip, global.readability, global.jsdom, global.CryptoJS, global.showdown, global.glob, global.http, global.express, global.socket_io, global.OpenApiValidator, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.rxjs, global.agents, global.OpenAI, global.ts, global.ignore, global.readline, global.mimeTypes, global.papaparse, global.url, global.events, global.pg));
|
|
5
|
+
})(this, (function (exports, colors, commander, _spaceTrim, promises, path, child_process, crypto, fs, dotenv, moment, waitasecond, prompts, hexEncoder, sha256, socket_ioClient, JSZip, readability, jsdom, CryptoJS, showdown, glob, http, express, socket_io, OpenApiValidator, swaggerUi, react, server, Anthropic, Bottleneck, openai, rxjs, agents, OpenAI, ts, ignore, readline, mimeTypes, papaparse, url, events, pg) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
|
|
44
44
|
var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
|
|
45
45
|
var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
|
|
46
|
-
var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
|
|
47
46
|
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
|
|
48
47
|
var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
|
|
49
48
|
var ignore__default = /*#__PURE__*/_interopDefaultLegacy(ignore);
|
|
50
49
|
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
50
|
+
var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
|
|
51
51
|
|
|
52
52
|
// ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
|
|
53
53
|
/**
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
* @generated
|
|
64
64
|
* @see https://github.com/webgptorg/promptbook
|
|
65
65
|
*/
|
|
66
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
66
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-124';
|
|
67
67
|
/**
|
|
68
68
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
69
69
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -2181,7 +2181,7 @@
|
|
|
2181
2181
|
*/
|
|
2182
2182
|
async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileContent) {
|
|
2183
2183
|
const absoluteFilePath = path.join(projectPath, relativeFilePath);
|
|
2184
|
-
if (await isExistingFile$
|
|
2184
|
+
if (await isExistingFile$3(absoluteFilePath)) {
|
|
2185
2185
|
return 'unchanged';
|
|
2186
2186
|
}
|
|
2187
2187
|
await promises.writeFile(absoluteFilePath, `${fileContent}\n`, 'utf-8');
|
|
@@ -2190,7 +2190,7 @@
|
|
|
2190
2190
|
/**
|
|
2191
2191
|
* Checks whether a path exists and is a file.
|
|
2192
2192
|
*/
|
|
2193
|
-
async function isExistingFile$
|
|
2193
|
+
async function isExistingFile$3(path) {
|
|
2194
2194
|
try {
|
|
2195
2195
|
return (await promises.stat(path)).isFile();
|
|
2196
2196
|
}
|
|
@@ -4021,6 +4021,21 @@
|
|
|
4021
4021
|
function resetPauseTargetLabel() {
|
|
4022
4022
|
setPauseTargetLabel(DEFAULT_PAUSE_TARGET_LABEL);
|
|
4023
4023
|
}
|
|
4024
|
+
/**
|
|
4025
|
+
* Requests a pause from an external controller (e.g. the Ink UI).
|
|
4026
|
+
*/
|
|
4027
|
+
function requestPause() {
|
|
4028
|
+
if (pauseState === 'RUNNING') {
|
|
4029
|
+
setPauseState('PAUSING');
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Resumes execution from an external controller after a pause.
|
|
4034
|
+
*/
|
|
4035
|
+
function requestResume() {
|
|
4036
|
+
setPauseState('RUNNING');
|
|
4037
|
+
resetPauseTargetLabel();
|
|
4038
|
+
}
|
|
4024
4039
|
|
|
4025
4040
|
/**
|
|
4026
4041
|
* Just says that the variable is not used but should be kept
|
|
@@ -38613,6 +38628,48 @@
|
|
|
38613
38628
|
// Note: [🟡] Code for CLI command [find-refactor-candidates](src/cli/cli-commands/coder/find-refactor-candidates.ts) should never be published outside of `@promptbook/cli`
|
|
38614
38629
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
38615
38630
|
|
|
38631
|
+
/**
|
|
38632
|
+
* Initializes `coder find-unwritten` command for Promptbook CLI utilities
|
|
38633
|
+
*
|
|
38634
|
+
* Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
|
|
38635
|
+
*
|
|
38636
|
+
* @private internal function of `promptbookCli`
|
|
38637
|
+
*/
|
|
38638
|
+
function $initializeCoderFindUnwrittenCommand(program) {
|
|
38639
|
+
const command = program.command('find-unwritten');
|
|
38640
|
+
command.description('List all prompt sections that still need to be authored (contain @@@ placeholder)');
|
|
38641
|
+
command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption$2, 0);
|
|
38642
|
+
command.action(handleActionErrors(async (cliOptions) => {
|
|
38643
|
+
const { priority = 0 } = cliOptions;
|
|
38644
|
+
// Note: Import the function dynamically to avoid loading heavy dependencies until needed
|
|
38645
|
+
const { findUnwrittenPrompts } = await Promise.resolve().then(function () { return findUnwrittenPrompts$1; });
|
|
38646
|
+
try {
|
|
38647
|
+
await findUnwrittenPrompts({ priority });
|
|
38648
|
+
}
|
|
38649
|
+
catch (error) {
|
|
38650
|
+
assertsError(error);
|
|
38651
|
+
console.error(colors__default["default"].bgRed(`${error.name}`));
|
|
38652
|
+
console.error(colors__default["default"].red(error.stack || error.message));
|
|
38653
|
+
return process.exit(1);
|
|
38654
|
+
}
|
|
38655
|
+
return process.exit(0);
|
|
38656
|
+
}));
|
|
38657
|
+
}
|
|
38658
|
+
/**
|
|
38659
|
+
* Parses an integer option value
|
|
38660
|
+
*
|
|
38661
|
+
* @private internal utility of `coder find-unwritten` command
|
|
38662
|
+
*/
|
|
38663
|
+
function parseIntOption$2(value) {
|
|
38664
|
+
const parsed = parseInt(value, 10);
|
|
38665
|
+
if (isNaN(parsed)) {
|
|
38666
|
+
throw new Error(`Invalid number: ${value}`);
|
|
38667
|
+
}
|
|
38668
|
+
return parsed;
|
|
38669
|
+
}
|
|
38670
|
+
// Note: [🟡] Code for CLI command [find-unwritten](src/cli/cli-commands/coder/find-unwritten.ts) should never be published outside of `@promptbook/cli`
|
|
38671
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
38672
|
+
|
|
38616
38673
|
/**
|
|
38617
38674
|
* Relative path to the root prompts directory used by Promptbook coder utilities.
|
|
38618
38675
|
*
|
|
@@ -38705,7 +38762,7 @@
|
|
|
38705
38762
|
const ensuredTemplateFiles = [];
|
|
38706
38763
|
for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
|
|
38707
38764
|
const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
|
|
38708
|
-
if (await isExistingFile$
|
|
38765
|
+
if (await isExistingFile$2(absoluteTemplatePath)) {
|
|
38709
38766
|
ensuredTemplateFiles.push({
|
|
38710
38767
|
id: definition.id,
|
|
38711
38768
|
relativeFilePath: definition.relativeFilePath,
|
|
@@ -38819,7 +38876,7 @@
|
|
|
38819
38876
|
/**
|
|
38820
38877
|
* Checks whether a path exists and is a file.
|
|
38821
38878
|
*/
|
|
38822
|
-
async function isExistingFile$
|
|
38879
|
+
async function isExistingFile$2(path) {
|
|
38823
38880
|
try {
|
|
38824
38881
|
return (await promises.stat(path)).isFile();
|
|
38825
38882
|
}
|
|
@@ -39092,6 +39149,70 @@
|
|
|
39092
39149
|
// Note: [🟡] Code for coder AGENT_CODING file boilerplate [agentCodingFile](src/cli/cli-commands/coder/agentCodingFile.ts) should never be published outside of `@promptbook/cli`
|
|
39093
39150
|
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
39094
39151
|
|
|
39152
|
+
/**
|
|
39153
|
+
* Relative directory path for agents initialized by `ptbk coder init`.
|
|
39154
|
+
*
|
|
39155
|
+
* @private internal utility of `coder init` command
|
|
39156
|
+
*/
|
|
39157
|
+
const CODER_AGENTS_DIRECTORY_PATH = 'agents';
|
|
39158
|
+
/**
|
|
39159
|
+
* Relative file path to the default developer agent initialized by `ptbk coder init`.
|
|
39160
|
+
*
|
|
39161
|
+
* @private internal utility of `coder init` command
|
|
39162
|
+
*/
|
|
39163
|
+
const CODER_DEVELOPER_AGENT_FILE_PATH = 'agents/developer.book';
|
|
39164
|
+
/**
|
|
39165
|
+
* Source file path of the bundled developer agent inside the Promptbook repository.
|
|
39166
|
+
*
|
|
39167
|
+
* @private internal utility of `coder init` command
|
|
39168
|
+
*/
|
|
39169
|
+
const DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH = 'agents/default/developer.book';
|
|
39170
|
+
/**
|
|
39171
|
+
* Ensures the default developer agent exists in the initialized project.
|
|
39172
|
+
*
|
|
39173
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
39174
|
+
*/
|
|
39175
|
+
async function ensureCoderDeveloperAgentFile(projectPath) {
|
|
39176
|
+
const absoluteFilePath = path.join(projectPath, CODER_DEVELOPER_AGENT_FILE_PATH);
|
|
39177
|
+
if (await isExistingFile$1(absoluteFilePath)) {
|
|
39178
|
+
return 'unchanged';
|
|
39179
|
+
}
|
|
39180
|
+
await promises.copyFile(await resolveDefaultCoderDeveloperAgentFilePath(), absoluteFilePath);
|
|
39181
|
+
return 'created';
|
|
39182
|
+
}
|
|
39183
|
+
/**
|
|
39184
|
+
* Resolves the bundled developer agent from a source checkout or generated CLI package.
|
|
39185
|
+
*/
|
|
39186
|
+
async function resolveDefaultCoderDeveloperAgentFilePath() {
|
|
39187
|
+
const candidates = [
|
|
39188
|
+
path.join(__dirname, '..', DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH),
|
|
39189
|
+
path.join(__dirname, '..', '..', '..', '..', DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH),
|
|
39190
|
+
];
|
|
39191
|
+
for (const candidate of candidates) {
|
|
39192
|
+
if (await isExistingFile$1(candidate)) {
|
|
39193
|
+
return candidate;
|
|
39194
|
+
}
|
|
39195
|
+
}
|
|
39196
|
+
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
39197
|
+
Cannot find the bundled Promptbook developer agent.
|
|
39198
|
+
|
|
39199
|
+
Checked:
|
|
39200
|
+
${candidates.map((candidate) => `- \`${candidate}\``).join('\n')}
|
|
39201
|
+
`));
|
|
39202
|
+
}
|
|
39203
|
+
/**
|
|
39204
|
+
* Checks whether a path exists and is a file.
|
|
39205
|
+
*/
|
|
39206
|
+
async function isExistingFile$1(path) {
|
|
39207
|
+
try {
|
|
39208
|
+
return (await promises.stat(path)).isFile();
|
|
39209
|
+
}
|
|
39210
|
+
catch (_a) {
|
|
39211
|
+
return false;
|
|
39212
|
+
}
|
|
39213
|
+
}
|
|
39214
|
+
// Note: [🟡] Code for coder init developer agent bootstrapping [ensureCoderDeveloperAgentFile](src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.ts) should never be published outside of `@promptbook/cli`
|
|
39215
|
+
|
|
39095
39216
|
/**
|
|
39096
39217
|
* Fallback `.env` content used when no required variables need to be appended.
|
|
39097
39218
|
*/
|
|
@@ -39168,7 +39289,7 @@
|
|
|
39168
39289
|
*/
|
|
39169
39290
|
const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
|
|
39170
39291
|
'coder:generate-boilerplates': 'ptbk coder generate-boilerplates --template ./prompts/templates/common.md',
|
|
39171
|
-
'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --no-wait',
|
|
39292
|
+
'coder:run': 'ptbk coder run --harness openai-codex --model gpt-5.4 --thinking-level xhigh --agent agents/developer.book --context AGENTS.md --no-wait',
|
|
39172
39293
|
// 'coder:find-refactor-candidates': 'ptbk coder find-refactor-candidates',
|
|
39173
39294
|
'coder:verify': 'ptbk coder verify',
|
|
39174
39295
|
};
|
|
@@ -39414,6 +39535,8 @@
|
|
|
39414
39535
|
const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
|
|
39415
39536
|
const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
|
|
39416
39537
|
const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
|
|
39538
|
+
const agentsDirectoryStatus = await ensureDirectory(projectPath, CODER_AGENTS_DIRECTORY_PATH);
|
|
39539
|
+
const developerAgentFileStatus = await ensureCoderDeveloperAgentFile(projectPath);
|
|
39417
39540
|
const agentsFileStatus = await ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent());
|
|
39418
39541
|
const agentCodingFileStatus = await ensureCoderMarkdownFile(projectPath, AGENT_CODING_FILE_PATH, getDefaultCoderAgentCodingFileContent({
|
|
39419
39542
|
packageJsonScripts: getDefaultCoderPackageJsonScripts(),
|
|
@@ -39427,6 +39550,8 @@
|
|
|
39427
39550
|
promptsDoneDirectoryStatus,
|
|
39428
39551
|
promptsTemplatesDirectoryStatus,
|
|
39429
39552
|
promptTemplateFileStatuses,
|
|
39553
|
+
agentsDirectoryStatus,
|
|
39554
|
+
developerAgentFileStatus,
|
|
39430
39555
|
agentsFileStatus,
|
|
39431
39556
|
agentCodingFileStatus,
|
|
39432
39557
|
envFileStatus,
|
|
@@ -39451,6 +39576,8 @@
|
|
|
39451
39576
|
for (const templateFileStatus of summary.promptTemplateFileStatuses) {
|
|
39452
39577
|
printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
|
|
39453
39578
|
}
|
|
39579
|
+
printInitializationStatusLine('agents/', summary.agentsDirectoryStatus);
|
|
39580
|
+
printInitializationStatusLine(CODER_DEVELOPER_AGENT_FILE_PATH, summary.developerAgentFileStatus);
|
|
39454
39581
|
printInitializationStatusLine(AGENTS_FILE_PATH, summary.agentsFileStatus);
|
|
39455
39582
|
printInitializationStatusLine(AGENT_CODING_FILE_PATH, summary.agentCodingFileStatus);
|
|
39456
39583
|
printInitializationStatusLine('.env', summary.envFileStatus);
|
|
@@ -39507,6 +39634,7 @@
|
|
|
39507
39634
|
- prompts/
|
|
39508
39635
|
- prompts/done/
|
|
39509
39636
|
${listDefaultCoderProjectPromptTemplateDisplayPaths()}
|
|
39637
|
+
- ${CODER_DEVELOPER_AGENT_FILE_PATH}
|
|
39510
39638
|
- ${AGENTS_FILE_PATH}
|
|
39511
39639
|
- ${AGENT_CODING_FILE_PATH}
|
|
39512
39640
|
- .gitignore
|
|
@@ -39519,8 +39647,10 @@
|
|
|
39519
39647
|
- CODING_AGENT_GIT_SIGNING_KEY
|
|
39520
39648
|
`));
|
|
39521
39649
|
command.action(handleActionErrors(async () => {
|
|
39522
|
-
const
|
|
39650
|
+
const projectPath = process.cwd();
|
|
39651
|
+
const summary = await initializeCoderProjectConfiguration(projectPath);
|
|
39523
39652
|
printInitializationSummary(summary);
|
|
39653
|
+
await generatePromptBoilerplate({ projectPath, filesCount: 5 });
|
|
39524
39654
|
}));
|
|
39525
39655
|
}
|
|
39526
39656
|
/**
|
|
@@ -39612,11 +39742,12 @@
|
|
|
39612
39742
|
`));
|
|
39613
39743
|
command.option('--dry-run', 'Print unwritten prompts without executing', false);
|
|
39614
39744
|
addPromptRunnerSelectionOptions(command);
|
|
39745
|
+
command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
|
|
39615
39746
|
command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
|
|
39616
39747
|
command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
|
|
39617
39748
|
command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
|
|
39618
39749
|
addPromptRunnerExecutionOptions(command);
|
|
39619
|
-
command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
|
|
39750
|
+
command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption$1, 0);
|
|
39620
39751
|
command.option('--wait [duration]', _spaceTrim.spaceTrim(`
|
|
39621
39752
|
Wait between prompt rounds.
|
|
39622
39753
|
Without a value (default): waits for user confirmation before each prompt (interactive mode).
|
|
@@ -39627,8 +39758,8 @@
|
|
|
39627
39758
|
command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
|
|
39628
39759
|
command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
|
|
39629
39760
|
command.action(handleActionErrors(async (cliOptions) => {
|
|
39630
|
-
const { dryRun, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate } = cliOptions;
|
|
39631
|
-
const testCommand = normalizeCommandOptionValue(test);
|
|
39761
|
+
const { dryRun, agent, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
|
|
39762
|
+
const testCommand = normalizeCommandOptionValue$1(test);
|
|
39632
39763
|
const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
|
|
39633
39764
|
isAgentRequired: !dryRun,
|
|
39634
39765
|
});
|
|
@@ -39653,6 +39784,7 @@
|
|
|
39653
39784
|
ignoreGitChanges: runnerOptions.ignoreGitChanges,
|
|
39654
39785
|
agentName: runnerOptions.agentName,
|
|
39655
39786
|
model: runnerOptions.model,
|
|
39787
|
+
agent,
|
|
39656
39788
|
context,
|
|
39657
39789
|
testCommand,
|
|
39658
39790
|
preserveLogs,
|
|
@@ -39686,7 +39818,7 @@
|
|
|
39686
39818
|
*
|
|
39687
39819
|
* @private internal utility of `coder run` command
|
|
39688
39820
|
*/
|
|
39689
|
-
function parseIntOption(value) {
|
|
39821
|
+
function parseIntOption$1(value) {
|
|
39690
39822
|
const parsed = parseInt(value, 10);
|
|
39691
39823
|
if (isNaN(parsed)) {
|
|
39692
39824
|
throw new Error(`Invalid number: ${value}`);
|
|
@@ -39698,7 +39830,7 @@
|
|
|
39698
39830
|
*
|
|
39699
39831
|
* @private internal utility of `coder run` command
|
|
39700
39832
|
*/
|
|
39701
|
-
function normalizeCommandOptionValue(value) {
|
|
39833
|
+
function normalizeCommandOptionValue$1(value) {
|
|
39702
39834
|
if (value === undefined) {
|
|
39703
39835
|
return undefined;
|
|
39704
39836
|
}
|
|
@@ -39713,6 +39845,157 @@
|
|
|
39713
39845
|
// Note: [🟡] Code for CLI command [run](src/cli/cli-commands/coder/run.ts) should never be published outside of `@promptbook/cli`
|
|
39714
39846
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
39715
39847
|
|
|
39848
|
+
/**
|
|
39849
|
+
* Default port used by `ptbk coder server`.
|
|
39850
|
+
*
|
|
39851
|
+
* @private internal constant of `ptbk coder server`
|
|
39852
|
+
*/
|
|
39853
|
+
const DEFAULT_CODER_SERVER_PORT = '4441';
|
|
39854
|
+
/**
|
|
39855
|
+
* Initializes `coder server` command for Promptbook CLI utilities.
|
|
39856
|
+
*
|
|
39857
|
+
* Runs the same prompt processing logic as `ptbk coder run` but keeps the process alive
|
|
39858
|
+
* and serves a kanban web UI on the configured port.
|
|
39859
|
+
*
|
|
39860
|
+
* Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
|
|
39861
|
+
*
|
|
39862
|
+
* @private internal function of `promptbookCli`
|
|
39863
|
+
*/
|
|
39864
|
+
function $initializeCoderServerCommand(program) {
|
|
39865
|
+
const command = program.command('server');
|
|
39866
|
+
command.description(_spaceTrim.spaceTrim(`
|
|
39867
|
+
Start a coder server that watches for prompts and serves a kanban web UI
|
|
39868
|
+
|
|
39869
|
+
${PROMPT_RUNNER_DESCRIPTION}
|
|
39870
|
+
|
|
39871
|
+
Features:
|
|
39872
|
+
- Runs the same prompt processing as \`ptbk coder run\`
|
|
39873
|
+
- Does not exit when all prompts are done; polls for new prompt files instead
|
|
39874
|
+
- Serves a kanban board at http://localhost:<port> for visual progress tracking
|
|
39875
|
+
- Allows editing prompt files directly from the browser (Trello-style)
|
|
39876
|
+
- Play / pause button in the browser stays in sync with the CLI pause state
|
|
39877
|
+
- Press "p" in the terminal to pause / resume (same as \`ptbk coder run\`)
|
|
39878
|
+
`));
|
|
39879
|
+
command.addOption(new commander.Option('--port <port>', 'Port to start the coder server on')
|
|
39880
|
+
.env('PTBK_CODER_SERVER_PORT')
|
|
39881
|
+
.default(DEFAULT_CODER_SERVER_PORT));
|
|
39882
|
+
command.option('--dry-run', 'Print unwritten prompts without executing', false);
|
|
39883
|
+
addPromptRunnerSelectionOptions(command);
|
|
39884
|
+
command.option('--agent <agent-book-path>', 'Path to a .book file whose compiled system message is prepended to each coding prompt');
|
|
39885
|
+
command.option('--context <context-or-file>', 'Append extra instructions either inline or from a file path relative to the current project');
|
|
39886
|
+
command.option('--test <test-command...>', 'Run a verification command after each prompt; quote it when the command itself contains top-level flags');
|
|
39887
|
+
command.option('--preserve-logs', 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics', false);
|
|
39888
|
+
addPromptRunnerExecutionOptions(command);
|
|
39889
|
+
command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
|
|
39890
|
+
command.option('--wait [duration]', _spaceTrim.spaceTrim(`
|
|
39891
|
+
Wait between prompt rounds.
|
|
39892
|
+
Without a value (default): waits for user confirmation before each prompt (interactive mode).
|
|
39893
|
+
With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
|
|
39894
|
+
`), true);
|
|
39895
|
+
command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
|
|
39896
|
+
command.option('--auto-migrate', 'Run testing-server database migrations automatically after each successfully processed prompt');
|
|
39897
|
+
command.option('--allow-destructive-auto-migrate', 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations');
|
|
39898
|
+
command.action(handleActionErrors(async (cliOptions) => {
|
|
39899
|
+
const { port: rawPort, dryRun, agent, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate, } = cliOptions;
|
|
39900
|
+
const port = parseCoderServerPort(rawPort);
|
|
39901
|
+
const testCommand = normalizeCommandOptionValue(test);
|
|
39902
|
+
const runnerOptions = normalizePromptRunnerCliOptions(cliOptions, {
|
|
39903
|
+
isAgentRequired: !dryRun,
|
|
39904
|
+
});
|
|
39905
|
+
// [1] Parse the --wait option (same logic as `coder run`)
|
|
39906
|
+
let waitForUser = false;
|
|
39907
|
+
let waitBetweenPrompts = 0;
|
|
39908
|
+
if (wait === true) {
|
|
39909
|
+
waitForUser = true;
|
|
39910
|
+
}
|
|
39911
|
+
else if (typeof wait === 'string' && wait !== '') {
|
|
39912
|
+
waitBetweenPrompts = parseDuration(wait);
|
|
39913
|
+
}
|
|
39914
|
+
const runOptions = {
|
|
39915
|
+
port,
|
|
39916
|
+
dryRun,
|
|
39917
|
+
waitForUser,
|
|
39918
|
+
waitBetweenPrompts,
|
|
39919
|
+
noCommit: runnerOptions.noCommit,
|
|
39920
|
+
ignoreGitChanges: runnerOptions.ignoreGitChanges,
|
|
39921
|
+
agentName: runnerOptions.agentName,
|
|
39922
|
+
model: runnerOptions.model,
|
|
39923
|
+
agent,
|
|
39924
|
+
context,
|
|
39925
|
+
testCommand,
|
|
39926
|
+
preserveLogs,
|
|
39927
|
+
noUi: runnerOptions.noUi,
|
|
39928
|
+
thinkingLevel: runnerOptions.thinkingLevel,
|
|
39929
|
+
priority,
|
|
39930
|
+
normalizeLineEndings: runnerOptions.normalizeLineEndings,
|
|
39931
|
+
allowCredits: runnerOptions.allowCredits,
|
|
39932
|
+
autoMigrate,
|
|
39933
|
+
allowDestructiveAutoMigrate,
|
|
39934
|
+
autoPush: runnerOptions.autoPush,
|
|
39935
|
+
autoPull: runnerOptions.autoPull,
|
|
39936
|
+
};
|
|
39937
|
+
// Note: Import dynamically to avoid loading heavy dependencies until needed
|
|
39938
|
+
const { runCodexPromptsServer } = await Promise.resolve().then(function () { return runCodexPromptsServer$1; });
|
|
39939
|
+
try {
|
|
39940
|
+
await runCodexPromptsServer(runOptions);
|
|
39941
|
+
}
|
|
39942
|
+
catch (error) {
|
|
39943
|
+
assertsError(error);
|
|
39944
|
+
console.error(colors__default["default"].bgRed(`${error.name}`));
|
|
39945
|
+
console.error(colors__default["default"].red(error.stack || error.message));
|
|
39946
|
+
return process.exit(1);
|
|
39947
|
+
}
|
|
39948
|
+
return process.exit(0);
|
|
39949
|
+
}));
|
|
39950
|
+
}
|
|
39951
|
+
/**
|
|
39952
|
+
* Parses and validates the coder server port number.
|
|
39953
|
+
*
|
|
39954
|
+
* @private internal utility of `coder server` command
|
|
39955
|
+
*/
|
|
39956
|
+
function parseCoderServerPort(rawPort) {
|
|
39957
|
+
const port = Number.parseInt(rawPort, 10);
|
|
39958
|
+
if (!Number.isInteger(port) || port <= 0 || port > NETWORK_LIMITS.MAX_PORT) {
|
|
39959
|
+
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
39960
|
+
Invalid coder server port: \`${rawPort}\`.
|
|
39961
|
+
|
|
39962
|
+
Use \`--port\` or \`PTBK_CODER_SERVER_PORT\` with an integer between \`1\` and \`${NETWORK_LIMITS.MAX_PORT}\`.
|
|
39963
|
+
`));
|
|
39964
|
+
}
|
|
39965
|
+
return port;
|
|
39966
|
+
}
|
|
39967
|
+
/**
|
|
39968
|
+
* Parses an integer option value.
|
|
39969
|
+
*
|
|
39970
|
+
* @private internal utility of `coder server` command
|
|
39971
|
+
*/
|
|
39972
|
+
function parseIntOption(value) {
|
|
39973
|
+
const parsed = parseInt(value, 10);
|
|
39974
|
+
if (isNaN(parsed)) {
|
|
39975
|
+
throw new Error(`Invalid number: ${value}`);
|
|
39976
|
+
}
|
|
39977
|
+
return parsed;
|
|
39978
|
+
}
|
|
39979
|
+
/**
|
|
39980
|
+
* Joins one Commander option that may be parsed either as a single string or a variadic token array.
|
|
39981
|
+
*
|
|
39982
|
+
* @private internal utility of `coder server` command
|
|
39983
|
+
*/
|
|
39984
|
+
function normalizeCommandOptionValue(value) {
|
|
39985
|
+
if (value === undefined) {
|
|
39986
|
+
return undefined;
|
|
39987
|
+
}
|
|
39988
|
+
const parts = Array.isArray(value) ? value : [value];
|
|
39989
|
+
const normalizedValue = parts
|
|
39990
|
+
.map((part) => part.trim())
|
|
39991
|
+
.filter(Boolean)
|
|
39992
|
+
.join(' ')
|
|
39993
|
+
.trim();
|
|
39994
|
+
return normalizedValue === '' ? undefined : normalizedValue;
|
|
39995
|
+
}
|
|
39996
|
+
// Note: [🟡] Code for CLI command [server](src/cli/cli-commands/coder/server.ts) should never be published outside of `@promptbook/cli`
|
|
39997
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
39998
|
+
|
|
39716
39999
|
/**
|
|
39717
40000
|
* Initializes `coder verify` command for Promptbook CLI utilities
|
|
39718
40001
|
*
|
|
@@ -39784,7 +40067,9 @@
|
|
|
39784
40067
|
- init: Initialize coder configuration in current project
|
|
39785
40068
|
- generate-boilerplates: Generate prompt boilerplate files
|
|
39786
40069
|
- find-refactor-candidates: Find files that need refactoring
|
|
40070
|
+
- find-unwritten: List prompt sections that still need to be authored
|
|
39787
40071
|
- run: Run coding prompts with AI agents
|
|
40072
|
+
- server: Start a long-running coder server with a kanban web UI
|
|
39788
40073
|
- verify: Verify completed prompts
|
|
39789
40074
|
- find-fresh-emoji-tags: Find unused emoji tags
|
|
39790
40075
|
`));
|
|
@@ -39792,7 +40077,9 @@
|
|
|
39792
40077
|
$initializeCoderInitCommand(coderCommand);
|
|
39793
40078
|
$initializeCoderGenerateBoilerplatesCommand(coderCommand);
|
|
39794
40079
|
$initializeCoderFindRefactorCandidatesCommand(coderCommand);
|
|
40080
|
+
$initializeCoderFindUnwrittenCommand(coderCommand);
|
|
39795
40081
|
$initializeCoderRunCommand(coderCommand);
|
|
40082
|
+
$initializeCoderServerCommand(coderCommand);
|
|
39796
40083
|
$initializeCoderVerifyCommand(coderCommand);
|
|
39797
40084
|
$initializeCoderFindFreshEmojiTagCommand(coderCommand);
|
|
39798
40085
|
// If no subcommand is provided, show help
|
|
@@ -68628,6 +68915,303 @@
|
|
|
68628
68915
|
findRefactorCandidates: findRefactorCandidates
|
|
68629
68916
|
});
|
|
68630
68917
|
|
|
68918
|
+
/**
|
|
68919
|
+
* Checks whether a prompt section meets the minimum priority threshold.
|
|
68920
|
+
*/
|
|
68921
|
+
function hasSufficientPriority(section, minimumPriority) {
|
|
68922
|
+
return section.priority >= minimumPriority;
|
|
68923
|
+
}
|
|
68924
|
+
|
|
68925
|
+
/**
|
|
68926
|
+
* Trims leading and trailing empty lines.
|
|
68927
|
+
*/
|
|
68928
|
+
function trimEmptyEdges(lines) {
|
|
68929
|
+
let start = 0;
|
|
68930
|
+
while (start < lines.length) {
|
|
68931
|
+
const startLine = lines[start];
|
|
68932
|
+
if (startLine !== undefined && startLine.trim() !== '') {
|
|
68933
|
+
break;
|
|
68934
|
+
}
|
|
68935
|
+
start += 1;
|
|
68936
|
+
}
|
|
68937
|
+
let end = lines.length - 1;
|
|
68938
|
+
while (end >= start) {
|
|
68939
|
+
const endLine = lines[end];
|
|
68940
|
+
if (endLine !== undefined && endLine.trim() !== '') {
|
|
68941
|
+
break;
|
|
68942
|
+
}
|
|
68943
|
+
end -= 1;
|
|
68944
|
+
}
|
|
68945
|
+
return lines.slice(start, end + 1);
|
|
68946
|
+
}
|
|
68947
|
+
|
|
68948
|
+
/**
|
|
68949
|
+
* Extracts prompt lines without the status marker.
|
|
68950
|
+
*/
|
|
68951
|
+
function buildPromptLinesWithoutStatus(file, section) {
|
|
68952
|
+
const lines = file.lines.slice(section.startLine, section.endLine + 1);
|
|
68953
|
+
if (section.statusLineIndex !== undefined) {
|
|
68954
|
+
const relativeIndex = section.statusLineIndex - section.startLine;
|
|
68955
|
+
if (relativeIndex >= 0 && relativeIndex < lines.length) {
|
|
68956
|
+
lines.splice(relativeIndex, 1);
|
|
68957
|
+
}
|
|
68958
|
+
}
|
|
68959
|
+
return trimEmptyEdges(lines);
|
|
68960
|
+
}
|
|
68961
|
+
|
|
68962
|
+
/**
|
|
68963
|
+
* Builds the prompt text sent to the agent runner.
|
|
68964
|
+
*/
|
|
68965
|
+
function buildCodexPrompt(file, section) {
|
|
68966
|
+
const lines = buildPromptLinesWithoutStatus(file, section);
|
|
68967
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68968
|
+
const line = lines[i];
|
|
68969
|
+
if (line === undefined || line.trim() === '') {
|
|
68970
|
+
continue;
|
|
68971
|
+
}
|
|
68972
|
+
lines[i] = line.replace(/^\[[^\]]+\]\s*/, '');
|
|
68973
|
+
break;
|
|
68974
|
+
}
|
|
68975
|
+
return lines.join(file.eol);
|
|
68976
|
+
}
|
|
68977
|
+
|
|
68978
|
+
/**
|
|
68979
|
+
* Checks whether a prompt section still needs authoring (contains "@@@").
|
|
68980
|
+
*/
|
|
68981
|
+
function isPromptToBeWritten(file, section) {
|
|
68982
|
+
return buildCodexPrompt(file, section).includes('@@@');
|
|
68983
|
+
}
|
|
68984
|
+
|
|
68985
|
+
/**
|
|
68986
|
+
* Lists todo prompts across all files.
|
|
68987
|
+
*/
|
|
68988
|
+
function listTodoPrompts(files) {
|
|
68989
|
+
const prompts = [];
|
|
68990
|
+
for (const file of files) {
|
|
68991
|
+
for (const section of file.sections) {
|
|
68992
|
+
if (section.status === 'todo') {
|
|
68993
|
+
prompts.push({ file, section });
|
|
68994
|
+
}
|
|
68995
|
+
}
|
|
68996
|
+
}
|
|
68997
|
+
return prompts;
|
|
68998
|
+
}
|
|
68999
|
+
|
|
69000
|
+
/**
|
|
69001
|
+
* Lists todo prompts that still contain authoring placeholders.
|
|
69002
|
+
*/
|
|
69003
|
+
function listPromptsToBeWritten(files, minimumPriority = 0) {
|
|
69004
|
+
return listTodoPrompts(files).filter((prompt) => isPromptToBeWritten(prompt.file, prompt.section) && hasSufficientPriority(prompt.section, minimumPriority));
|
|
69005
|
+
}
|
|
69006
|
+
|
|
69007
|
+
/**
|
|
69008
|
+
* Parses a prompt markdown file into sections and metadata.
|
|
69009
|
+
*/
|
|
69010
|
+
function parsePromptFile(filePath, content) {
|
|
69011
|
+
var _a, _b;
|
|
69012
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
69013
|
+
const hasFinalEol = content.endsWith('\n');
|
|
69014
|
+
const lines = content.split(/\r?\n/);
|
|
69015
|
+
const sections = [];
|
|
69016
|
+
let startLine = 0;
|
|
69017
|
+
let index = 0;
|
|
69018
|
+
for (let i = 0; i <= lines.length; i++) {
|
|
69019
|
+
const line = lines[i];
|
|
69020
|
+
const isSeparator = i < lines.length && line !== undefined && line.trim() === '---';
|
|
69021
|
+
const isEnd = i === lines.length;
|
|
69022
|
+
if (!isSeparator && !isEnd) {
|
|
69023
|
+
continue;
|
|
69024
|
+
}
|
|
69025
|
+
const endLine = i - 1;
|
|
69026
|
+
const firstNonEmptyLine = findFirstNonEmptyLine(lines, startLine, endLine);
|
|
69027
|
+
if (firstNonEmptyLine !== undefined) {
|
|
69028
|
+
const statusLine = (lines[firstNonEmptyLine] || '').trim();
|
|
69029
|
+
const parsedStatus = parseStatusLine(statusLine);
|
|
69030
|
+
const status = (_a = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.status) !== null && _a !== void 0 ? _a : 'not-ready';
|
|
69031
|
+
const priority = (_b = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.priority) !== null && _b !== void 0 ? _b : 0;
|
|
69032
|
+
sections.push({
|
|
69033
|
+
index,
|
|
69034
|
+
startLine,
|
|
69035
|
+
endLine,
|
|
69036
|
+
status,
|
|
69037
|
+
priority,
|
|
69038
|
+
statusLineIndex: parsedStatus ? firstNonEmptyLine : undefined,
|
|
69039
|
+
});
|
|
69040
|
+
index += 1;
|
|
69041
|
+
}
|
|
69042
|
+
startLine = i + 1;
|
|
69043
|
+
}
|
|
69044
|
+
return {
|
|
69045
|
+
path: filePath,
|
|
69046
|
+
name: path.basename(filePath),
|
|
69047
|
+
lines,
|
|
69048
|
+
eol,
|
|
69049
|
+
hasFinalEol,
|
|
69050
|
+
sections,
|
|
69051
|
+
};
|
|
69052
|
+
}
|
|
69053
|
+
/**
|
|
69054
|
+
* Parses a status line like "[ ] !!" or "[-]" or "[x] ~$0.65 21 minutes..." into status and priority.
|
|
69055
|
+
* For [x] done and [!] failed prompts, allow metadata after the status marker.
|
|
69056
|
+
*/
|
|
69057
|
+
function parseStatusLine(line) {
|
|
69058
|
+
var _a, _b, _c, _d, _e;
|
|
69059
|
+
// For done prompts [x], allow any content after (for cost/time metadata)
|
|
69060
|
+
const doneMatch = line.match(/^\[(?<status>[xX])\]/);
|
|
69061
|
+
if (doneMatch) {
|
|
69062
|
+
return { status: 'done', priority: 0 };
|
|
69063
|
+
}
|
|
69064
|
+
// For failed prompts [!], allow any content after (for failure metadata)
|
|
69065
|
+
const failedMatch = line.match(/^\[(?<status>!)\]/);
|
|
69066
|
+
if (failedMatch) {
|
|
69067
|
+
return { status: 'failed', priority: 0 };
|
|
69068
|
+
}
|
|
69069
|
+
// For todo [ ] and not-ready [-], require clean end with optional priority markers
|
|
69070
|
+
const match = line.match(/^\[(?<status>[ -])\]\s*(?<priority>!*)\s*$/);
|
|
69071
|
+
if (!match) {
|
|
69072
|
+
return undefined;
|
|
69073
|
+
}
|
|
69074
|
+
const statusChar = (_b = (_a = match.groups) === null || _a === void 0 ? void 0 : _a.status) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
69075
|
+
let status;
|
|
69076
|
+
if (statusChar === '-') {
|
|
69077
|
+
status = 'not-ready';
|
|
69078
|
+
}
|
|
69079
|
+
else {
|
|
69080
|
+
status = 'todo';
|
|
69081
|
+
}
|
|
69082
|
+
const priority = status === 'todo' ? (_e = (_d = (_c = match.groups) === null || _c === void 0 ? void 0 : _c.priority) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0 : 0;
|
|
69083
|
+
return { status, priority };
|
|
69084
|
+
}
|
|
69085
|
+
/**
|
|
69086
|
+
* Finds the first non-empty line index between two bounds.
|
|
69087
|
+
*/
|
|
69088
|
+
function findFirstNonEmptyLine(lines, startLine, endLine) {
|
|
69089
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
69090
|
+
const line = lines[i];
|
|
69091
|
+
if (line !== undefined && line.trim() !== '') {
|
|
69092
|
+
return i;
|
|
69093
|
+
}
|
|
69094
|
+
}
|
|
69095
|
+
return undefined;
|
|
69096
|
+
}
|
|
69097
|
+
|
|
69098
|
+
/**
|
|
69099
|
+
* Loads and parses prompt files from the prompts directory.
|
|
69100
|
+
*/
|
|
69101
|
+
async function loadPromptFiles(promptsDir) {
|
|
69102
|
+
const entries = await promises.readdir(promptsDir, { withFileTypes: true });
|
|
69103
|
+
const files = entries
|
|
69104
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
|
|
69105
|
+
.map((entry) => path.join(promptsDir, entry.name))
|
|
69106
|
+
.sort((a, b) => a.localeCompare(b));
|
|
69107
|
+
const promptFiles = [];
|
|
69108
|
+
for (const filePath of files) {
|
|
69109
|
+
const content = await promises.readFile(filePath, 'utf-8');
|
|
69110
|
+
promptFiles.push(parsePromptFile(filePath, content));
|
|
69111
|
+
}
|
|
69112
|
+
return promptFiles;
|
|
69113
|
+
}
|
|
69114
|
+
|
|
69115
|
+
/**
|
|
69116
|
+
* Builds a display label using the prompt line number for easier navigation.
|
|
69117
|
+
*/
|
|
69118
|
+
function buildPromptLabelForDisplay(file, section) {
|
|
69119
|
+
return `${path.relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.startLine + 1}`;
|
|
69120
|
+
}
|
|
69121
|
+
|
|
69122
|
+
/**
|
|
69123
|
+
* Extracts a short summary line from a prompt section.
|
|
69124
|
+
*/
|
|
69125
|
+
function buildPromptSummary(file, section) {
|
|
69126
|
+
const lines = buildCodexPrompt(file, section).split(/\r?\n/);
|
|
69127
|
+
const firstLine = lines.find((line) => line.trim() !== '');
|
|
69128
|
+
return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
|
|
69129
|
+
}
|
|
69130
|
+
|
|
69131
|
+
/**
|
|
69132
|
+
* Prints the list of prompts that still need to be written.
|
|
69133
|
+
*/
|
|
69134
|
+
function printPromptsToBeWritten(files, minimumPriority = 0) {
|
|
69135
|
+
const promptsToWrite = listPromptsToBeWritten(files, minimumPriority);
|
|
69136
|
+
let i = 0;
|
|
69137
|
+
for (const { file, section } of promptsToWrite) {
|
|
69138
|
+
const label = buildPromptLabelForDisplay(file, section);
|
|
69139
|
+
const summary = buildPromptSummary(file, section);
|
|
69140
|
+
console.info(` ${++i}) ${label}: ${summary}`);
|
|
69141
|
+
}
|
|
69142
|
+
}
|
|
69143
|
+
|
|
69144
|
+
/**
|
|
69145
|
+
* Prints the summary stats line.
|
|
69146
|
+
*/
|
|
69147
|
+
function printStats(stats, minimumPriority = 0) {
|
|
69148
|
+
const priorityStats = minimumPriority > 0 ? ` | Priority <${minimumPriority}: ${stats.belowMinimumPriority}` : '';
|
|
69149
|
+
console.info(colors__default["default"].cyan(`Done: ${stats.done} | For agent: ${stats.forAgent}${priorityStats} | To be written: ${stats.toBeWritten}`));
|
|
69150
|
+
}
|
|
69151
|
+
|
|
69152
|
+
/**
|
|
69153
|
+
* Summarizes prompt stats for the runner output.
|
|
69154
|
+
*/
|
|
69155
|
+
function summarizePrompts(files, minimumPriority = 0) {
|
|
69156
|
+
const stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
|
|
69157
|
+
for (const file of files) {
|
|
69158
|
+
for (const section of file.sections) {
|
|
69159
|
+
if (section.status === 'done') {
|
|
69160
|
+
stats.done += 1;
|
|
69161
|
+
}
|
|
69162
|
+
else if (section.status === 'todo') {
|
|
69163
|
+
const isAbovePriority = hasSufficientPriority(section, minimumPriority);
|
|
69164
|
+
if (isPromptToBeWritten(file, section)) {
|
|
69165
|
+
if (!isAbovePriority) {
|
|
69166
|
+
continue;
|
|
69167
|
+
}
|
|
69168
|
+
stats.toBeWritten += 1;
|
|
69169
|
+
}
|
|
69170
|
+
else if (isAbovePriority) {
|
|
69171
|
+
stats.forAgent += 1;
|
|
69172
|
+
}
|
|
69173
|
+
else {
|
|
69174
|
+
stats.belowMinimumPriority += 1;
|
|
69175
|
+
}
|
|
69176
|
+
}
|
|
69177
|
+
}
|
|
69178
|
+
}
|
|
69179
|
+
return stats;
|
|
69180
|
+
}
|
|
69181
|
+
|
|
69182
|
+
/**
|
|
69183
|
+
* Constant for prompts dir — mirrors the constant in `runCodexPrompts.ts`.
|
|
69184
|
+
*/
|
|
69185
|
+
const PROMPTS_DIR$2 = path.join(process.cwd(), 'prompts');
|
|
69186
|
+
/**
|
|
69187
|
+
* Lists and prints all prompt sections that still need to be authored (contain `@@@` placeholder).
|
|
69188
|
+
*
|
|
69189
|
+
* Reuses the same filtering logic as `ptbk coder run --dry-run`.
|
|
69190
|
+
*
|
|
69191
|
+
* @returns the number of unwritten prompts found
|
|
69192
|
+
* @public exported from `@promptbook/cli`
|
|
69193
|
+
*/
|
|
69194
|
+
async function findUnwrittenPrompts(options = {}) {
|
|
69195
|
+
const { priority = 0 } = options;
|
|
69196
|
+
const promptFiles = await loadPromptFiles(PROMPTS_DIR$2);
|
|
69197
|
+
const stats = summarizePrompts(promptFiles, priority);
|
|
69198
|
+
printStats(stats, priority);
|
|
69199
|
+
const unwrittenPrompts = listPromptsToBeWritten(promptFiles, priority);
|
|
69200
|
+
if (unwrittenPrompts.length === 0) {
|
|
69201
|
+
console.info(colors__default["default"].green('All prompts are written — nothing to do.'));
|
|
69202
|
+
}
|
|
69203
|
+
else {
|
|
69204
|
+
console.info(colors__default["default"].yellow(`Found ${unwrittenPrompts.length} prompt(s) that need to be written:`));
|
|
69205
|
+
printPromptsToBeWritten(promptFiles, priority);
|
|
69206
|
+
}
|
|
69207
|
+
return unwrittenPrompts.length;
|
|
69208
|
+
}
|
|
69209
|
+
|
|
69210
|
+
var findUnwrittenPrompts$1 = /*#__PURE__*/Object.freeze({
|
|
69211
|
+
__proto__: null,
|
|
69212
|
+
findUnwrittenPrompts: findUnwrittenPrompts
|
|
69213
|
+
});
|
|
69214
|
+
|
|
68631
69215
|
/**
|
|
68632
69216
|
* CLI usage text for this script.
|
|
68633
69217
|
*/
|
|
@@ -68971,6 +69555,29 @@
|
|
|
68971
69555
|
return `${count} prompt${count === 1 ? '' : 's'}`;
|
|
68972
69556
|
}
|
|
68973
69557
|
|
|
69558
|
+
/**
|
|
69559
|
+
* Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
|
|
69560
|
+
*
|
|
69561
|
+
* Returns `undefined` when no agent path is provided.
|
|
69562
|
+
*/
|
|
69563
|
+
async function resolveAgentSystemMessage(agentPath, currentWorkingDirectory) {
|
|
69564
|
+
if (!agentPath) {
|
|
69565
|
+
return undefined;
|
|
69566
|
+
}
|
|
69567
|
+
const resolvedAgentPath = path.resolve(currentWorkingDirectory, agentPath);
|
|
69568
|
+
const agentSource = await promises.readFile(resolvedAgentPath, 'utf-8').catch((error) => {
|
|
69569
|
+
if (error.code === 'ENOENT' || error.code === 'EISDIR') {
|
|
69570
|
+
throw new NotFoundError(spaceTrim(`
|
|
69571
|
+
Agent book \`${agentPath}\` was not found or is not a file.
|
|
69572
|
+
|
|
69573
|
+
Pass a path to a \`.book\` file in \`--agent\`.
|
|
69574
|
+
`));
|
|
69575
|
+
}
|
|
69576
|
+
throw error;
|
|
69577
|
+
});
|
|
69578
|
+
return createAgentRunnerSystemMessage(agentSource);
|
|
69579
|
+
}
|
|
69580
|
+
|
|
68974
69581
|
/**
|
|
68975
69582
|
* Resolves optional coding context provided inline or via a file path.
|
|
68976
69583
|
*/
|
|
@@ -69013,104 +69620,6 @@
|
|
|
69013
69620
|
}
|
|
69014
69621
|
}
|
|
69015
69622
|
|
|
69016
|
-
/**
|
|
69017
|
-
* Builds a display label using the prompt line number for easier navigation.
|
|
69018
|
-
*/
|
|
69019
|
-
function buildPromptLabelForDisplay(file, section) {
|
|
69020
|
-
return `${path.relative(process.cwd(), file.path).replace(/\\/g, '/')}#${section.startLine + 1}`;
|
|
69021
|
-
}
|
|
69022
|
-
|
|
69023
|
-
/**
|
|
69024
|
-
* Trims leading and trailing empty lines.
|
|
69025
|
-
*/
|
|
69026
|
-
function trimEmptyEdges(lines) {
|
|
69027
|
-
let start = 0;
|
|
69028
|
-
while (start < lines.length) {
|
|
69029
|
-
const startLine = lines[start];
|
|
69030
|
-
if (startLine !== undefined && startLine.trim() !== '') {
|
|
69031
|
-
break;
|
|
69032
|
-
}
|
|
69033
|
-
start += 1;
|
|
69034
|
-
}
|
|
69035
|
-
let end = lines.length - 1;
|
|
69036
|
-
while (end >= start) {
|
|
69037
|
-
const endLine = lines[end];
|
|
69038
|
-
if (endLine !== undefined && endLine.trim() !== '') {
|
|
69039
|
-
break;
|
|
69040
|
-
}
|
|
69041
|
-
end -= 1;
|
|
69042
|
-
}
|
|
69043
|
-
return lines.slice(start, end + 1);
|
|
69044
|
-
}
|
|
69045
|
-
|
|
69046
|
-
/**
|
|
69047
|
-
* Extracts prompt lines without the status marker.
|
|
69048
|
-
*/
|
|
69049
|
-
function buildPromptLinesWithoutStatus(file, section) {
|
|
69050
|
-
const lines = file.lines.slice(section.startLine, section.endLine + 1);
|
|
69051
|
-
if (section.statusLineIndex !== undefined) {
|
|
69052
|
-
const relativeIndex = section.statusLineIndex - section.startLine;
|
|
69053
|
-
if (relativeIndex >= 0 && relativeIndex < lines.length) {
|
|
69054
|
-
lines.splice(relativeIndex, 1);
|
|
69055
|
-
}
|
|
69056
|
-
}
|
|
69057
|
-
return trimEmptyEdges(lines);
|
|
69058
|
-
}
|
|
69059
|
-
|
|
69060
|
-
/**
|
|
69061
|
-
* Builds the prompt text sent to the agent runner.
|
|
69062
|
-
*/
|
|
69063
|
-
function buildCodexPrompt(file, section) {
|
|
69064
|
-
const lines = buildPromptLinesWithoutStatus(file, section);
|
|
69065
|
-
for (let i = 0; i < lines.length; i++) {
|
|
69066
|
-
const line = lines[i];
|
|
69067
|
-
if (line === undefined || line.trim() === '') {
|
|
69068
|
-
continue;
|
|
69069
|
-
}
|
|
69070
|
-
lines[i] = line.replace(/^\[[^\]]+\]\s*/, '');
|
|
69071
|
-
break;
|
|
69072
|
-
}
|
|
69073
|
-
return lines.join(file.eol);
|
|
69074
|
-
}
|
|
69075
|
-
|
|
69076
|
-
/**
|
|
69077
|
-
* Extracts a short summary line from a prompt section.
|
|
69078
|
-
*/
|
|
69079
|
-
function buildPromptSummary(file, section) {
|
|
69080
|
-
const lines = buildCodexPrompt(file, section).split(/\r?\n/);
|
|
69081
|
-
const firstLine = lines.find((line) => line.trim() !== '');
|
|
69082
|
-
return (firstLine === null || firstLine === void 0 ? void 0 : firstLine.trim()) || '(empty prompt)';
|
|
69083
|
-
}
|
|
69084
|
-
|
|
69085
|
-
/**
|
|
69086
|
-
* Checks whether a prompt section meets the minimum priority threshold.
|
|
69087
|
-
*/
|
|
69088
|
-
function hasSufficientPriority(section, minimumPriority) {
|
|
69089
|
-
return section.priority >= minimumPriority;
|
|
69090
|
-
}
|
|
69091
|
-
|
|
69092
|
-
/**
|
|
69093
|
-
* Checks whether a prompt section still needs authoring (contains "@@@").
|
|
69094
|
-
*/
|
|
69095
|
-
function isPromptToBeWritten(file, section) {
|
|
69096
|
-
return buildCodexPrompt(file, section).includes('@@@');
|
|
69097
|
-
}
|
|
69098
|
-
|
|
69099
|
-
/**
|
|
69100
|
-
* Lists todo prompts across all files.
|
|
69101
|
-
*/
|
|
69102
|
-
function listTodoPrompts(files) {
|
|
69103
|
-
const prompts = [];
|
|
69104
|
-
for (const file of files) {
|
|
69105
|
-
for (const section of file.sections) {
|
|
69106
|
-
if (section.status === 'todo') {
|
|
69107
|
-
prompts.push({ file, section });
|
|
69108
|
-
}
|
|
69109
|
-
}
|
|
69110
|
-
}
|
|
69111
|
-
return prompts;
|
|
69112
|
-
}
|
|
69113
|
-
|
|
69114
69623
|
/**
|
|
69115
69624
|
* Lists todo prompts that are ready to run (no authoring placeholders).
|
|
69116
69625
|
*/
|
|
@@ -69143,142 +69652,6 @@
|
|
|
69143
69652
|
}));
|
|
69144
69653
|
}
|
|
69145
69654
|
|
|
69146
|
-
/**
|
|
69147
|
-
* Parses a prompt markdown file into sections and metadata.
|
|
69148
|
-
*/
|
|
69149
|
-
function parsePromptFile(filePath, content) {
|
|
69150
|
-
var _a, _b;
|
|
69151
|
-
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
69152
|
-
const hasFinalEol = content.endsWith('\n');
|
|
69153
|
-
const lines = content.split(/\r?\n/);
|
|
69154
|
-
const sections = [];
|
|
69155
|
-
let startLine = 0;
|
|
69156
|
-
let index = 0;
|
|
69157
|
-
for (let i = 0; i <= lines.length; i++) {
|
|
69158
|
-
const line = lines[i];
|
|
69159
|
-
const isSeparator = i < lines.length && line !== undefined && line.trim() === '---';
|
|
69160
|
-
const isEnd = i === lines.length;
|
|
69161
|
-
if (!isSeparator && !isEnd) {
|
|
69162
|
-
continue;
|
|
69163
|
-
}
|
|
69164
|
-
const endLine = i - 1;
|
|
69165
|
-
const firstNonEmptyLine = findFirstNonEmptyLine(lines, startLine, endLine);
|
|
69166
|
-
if (firstNonEmptyLine !== undefined) {
|
|
69167
|
-
const statusLine = (lines[firstNonEmptyLine] || '').trim();
|
|
69168
|
-
const parsedStatus = parseStatusLine(statusLine);
|
|
69169
|
-
const status = (_a = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.status) !== null && _a !== void 0 ? _a : 'not-ready';
|
|
69170
|
-
const priority = (_b = parsedStatus === null || parsedStatus === void 0 ? void 0 : parsedStatus.priority) !== null && _b !== void 0 ? _b : 0;
|
|
69171
|
-
sections.push({
|
|
69172
|
-
index,
|
|
69173
|
-
startLine,
|
|
69174
|
-
endLine,
|
|
69175
|
-
status,
|
|
69176
|
-
priority,
|
|
69177
|
-
statusLineIndex: parsedStatus ? firstNonEmptyLine : undefined,
|
|
69178
|
-
});
|
|
69179
|
-
index += 1;
|
|
69180
|
-
}
|
|
69181
|
-
startLine = i + 1;
|
|
69182
|
-
}
|
|
69183
|
-
return {
|
|
69184
|
-
path: filePath,
|
|
69185
|
-
name: path.basename(filePath),
|
|
69186
|
-
lines,
|
|
69187
|
-
eol,
|
|
69188
|
-
hasFinalEol,
|
|
69189
|
-
sections,
|
|
69190
|
-
};
|
|
69191
|
-
}
|
|
69192
|
-
/**
|
|
69193
|
-
* Parses a status line like "[ ] !!" or "[-]" or "[x] ~$0.65 21 minutes..." into status and priority.
|
|
69194
|
-
* For [x] done and [!] failed prompts, allow metadata after the status marker.
|
|
69195
|
-
*/
|
|
69196
|
-
function parseStatusLine(line) {
|
|
69197
|
-
var _a, _b, _c, _d, _e;
|
|
69198
|
-
// For done prompts [x], allow any content after (for cost/time metadata)
|
|
69199
|
-
const doneMatch = line.match(/^\[(?<status>[xX])\]/);
|
|
69200
|
-
if (doneMatch) {
|
|
69201
|
-
return { status: 'done', priority: 0 };
|
|
69202
|
-
}
|
|
69203
|
-
// For failed prompts [!], allow any content after (for failure metadata)
|
|
69204
|
-
const failedMatch = line.match(/^\[(?<status>!)\]/);
|
|
69205
|
-
if (failedMatch) {
|
|
69206
|
-
return { status: 'failed', priority: 0 };
|
|
69207
|
-
}
|
|
69208
|
-
// For todo [ ] and not-ready [-], require clean end with optional priority markers
|
|
69209
|
-
const match = line.match(/^\[(?<status>[ -])\]\s*(?<priority>!*)\s*$/);
|
|
69210
|
-
if (!match) {
|
|
69211
|
-
return undefined;
|
|
69212
|
-
}
|
|
69213
|
-
const statusChar = (_b = (_a = match.groups) === null || _a === void 0 ? void 0 : _a.status) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
69214
|
-
let status;
|
|
69215
|
-
if (statusChar === '-') {
|
|
69216
|
-
status = 'not-ready';
|
|
69217
|
-
}
|
|
69218
|
-
else {
|
|
69219
|
-
status = 'todo';
|
|
69220
|
-
}
|
|
69221
|
-
const priority = status === 'todo' ? (_e = (_d = (_c = match.groups) === null || _c === void 0 ? void 0 : _c.priority) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0 : 0;
|
|
69222
|
-
return { status, priority };
|
|
69223
|
-
}
|
|
69224
|
-
/**
|
|
69225
|
-
* Finds the first non-empty line index between two bounds.
|
|
69226
|
-
*/
|
|
69227
|
-
function findFirstNonEmptyLine(lines, startLine, endLine) {
|
|
69228
|
-
for (let i = startLine; i <= endLine; i++) {
|
|
69229
|
-
const line = lines[i];
|
|
69230
|
-
if (line !== undefined && line.trim() !== '') {
|
|
69231
|
-
return i;
|
|
69232
|
-
}
|
|
69233
|
-
}
|
|
69234
|
-
return undefined;
|
|
69235
|
-
}
|
|
69236
|
-
|
|
69237
|
-
/**
|
|
69238
|
-
* Loads and parses prompt files from the prompts directory.
|
|
69239
|
-
*/
|
|
69240
|
-
async function loadPromptFiles(promptsDir) {
|
|
69241
|
-
const entries = await promises.readdir(promptsDir, { withFileTypes: true });
|
|
69242
|
-
const files = entries
|
|
69243
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
|
|
69244
|
-
.map((entry) => path.join(promptsDir, entry.name))
|
|
69245
|
-
.sort((a, b) => a.localeCompare(b));
|
|
69246
|
-
const promptFiles = [];
|
|
69247
|
-
for (const filePath of files) {
|
|
69248
|
-
const content = await promises.readFile(filePath, 'utf-8');
|
|
69249
|
-
promptFiles.push(parsePromptFile(filePath, content));
|
|
69250
|
-
}
|
|
69251
|
-
return promptFiles;
|
|
69252
|
-
}
|
|
69253
|
-
|
|
69254
|
-
/**
|
|
69255
|
-
* Lists todo prompts that still contain authoring placeholders.
|
|
69256
|
-
*/
|
|
69257
|
-
function listPromptsToBeWritten(files, minimumPriority = 0) {
|
|
69258
|
-
return listTodoPrompts(files).filter((prompt) => isPromptToBeWritten(prompt.file, prompt.section) && hasSufficientPriority(prompt.section, minimumPriority));
|
|
69259
|
-
}
|
|
69260
|
-
|
|
69261
|
-
/**
|
|
69262
|
-
* Prints the list of prompts that still need to be written.
|
|
69263
|
-
*/
|
|
69264
|
-
function printPromptsToBeWritten(files, minimumPriority = 0) {
|
|
69265
|
-
const promptsToWrite = listPromptsToBeWritten(files, minimumPriority);
|
|
69266
|
-
let i = 0;
|
|
69267
|
-
for (const { file, section } of promptsToWrite) {
|
|
69268
|
-
const label = buildPromptLabelForDisplay(file, section);
|
|
69269
|
-
const summary = buildPromptSummary(file, section);
|
|
69270
|
-
console.info(` ${++i}) ${label}: ${summary}`);
|
|
69271
|
-
}
|
|
69272
|
-
}
|
|
69273
|
-
|
|
69274
|
-
/**
|
|
69275
|
-
* Prints the summary stats line.
|
|
69276
|
-
*/
|
|
69277
|
-
function printStats(stats, minimumPriority = 0) {
|
|
69278
|
-
const priorityStats = minimumPriority > 0 ? ` | Priority <${minimumPriority}: ${stats.belowMinimumPriority}` : '';
|
|
69279
|
-
console.info(colors__default["default"].cyan(`Done: ${stats.done} | For agent: ${stats.forAgent}${priorityStats} | To be written: ${stats.toBeWritten}`));
|
|
69280
|
-
}
|
|
69281
|
-
|
|
69282
69655
|
/**
|
|
69283
69656
|
* Groups upcoming tasks by priority, high to low.
|
|
69284
69657
|
*/
|
|
@@ -69316,36 +69689,6 @@
|
|
|
69316
69689
|
}
|
|
69317
69690
|
}
|
|
69318
69691
|
|
|
69319
|
-
/**
|
|
69320
|
-
* Summarizes prompt stats for the runner output.
|
|
69321
|
-
*/
|
|
69322
|
-
function summarizePrompts(files, minimumPriority = 0) {
|
|
69323
|
-
const stats = { done: 0, forAgent: 0, belowMinimumPriority: 0, toBeWritten: 0 };
|
|
69324
|
-
for (const file of files) {
|
|
69325
|
-
for (const section of file.sections) {
|
|
69326
|
-
if (section.status === 'done') {
|
|
69327
|
-
stats.done += 1;
|
|
69328
|
-
}
|
|
69329
|
-
else if (section.status === 'todo') {
|
|
69330
|
-
const isAbovePriority = hasSufficientPriority(section, minimumPriority);
|
|
69331
|
-
if (isPromptToBeWritten(file, section)) {
|
|
69332
|
-
if (!isAbovePriority) {
|
|
69333
|
-
continue;
|
|
69334
|
-
}
|
|
69335
|
-
stats.toBeWritten += 1;
|
|
69336
|
-
}
|
|
69337
|
-
else if (isAbovePriority) {
|
|
69338
|
-
stats.forAgent += 1;
|
|
69339
|
-
}
|
|
69340
|
-
else {
|
|
69341
|
-
stats.belowMinimumPriority += 1;
|
|
69342
|
-
}
|
|
69343
|
-
}
|
|
69344
|
-
}
|
|
69345
|
-
}
|
|
69346
|
-
return stats;
|
|
69347
|
-
}
|
|
69348
|
-
|
|
69349
69692
|
/**
|
|
69350
69693
|
* Waits for the user to press Enter before continuing.
|
|
69351
69694
|
*/
|
|
@@ -70970,9 +71313,14 @@
|
|
|
70970
71313
|
*
|
|
70971
71314
|
* @private function of runCodexPrompts
|
|
70972
71315
|
*/
|
|
70973
|
-
async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, promptLabel, resolvedCoderContext, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, }) {
|
|
71316
|
+
async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, promptLabel, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, }) {
|
|
70974
71317
|
const commitMessage = buildCommitMessage(nextPrompt.file, nextPrompt.section);
|
|
70975
|
-
const
|
|
71318
|
+
const taskPrompt = buildCodexPrompt(nextPrompt.file, nextPrompt.section);
|
|
71319
|
+
// Prepend agent system message before the task so the harness sees agent instructions first
|
|
71320
|
+
const promptWithAgent = resolvedAgentSystemMessage
|
|
71321
|
+
? `${resolvedAgentSystemMessage.trim()}\n\n${taskPrompt}`
|
|
71322
|
+
: taskPrompt;
|
|
71323
|
+
const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
|
|
70976
71324
|
const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
|
|
70977
71325
|
setPromptRoundRunningState({ isRichUiEnabled, promptLabel, scriptPath, uiHandle });
|
|
70978
71326
|
await waitForRequestedPause({
|
|
@@ -71205,6 +71553,7 @@
|
|
|
71205
71553
|
startPauseListenerIfNeeded(isRichUiEnabled);
|
|
71206
71554
|
try {
|
|
71207
71555
|
const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
|
|
71556
|
+
const resolvedAgentSystemMessage = await resolveAgentSystemMessage(options.agent, process.cwd());
|
|
71208
71557
|
if (await runDryRunIfRequested(options)) {
|
|
71209
71558
|
return;
|
|
71210
71559
|
}
|
|
@@ -71244,7 +71593,13 @@
|
|
|
71244
71593
|
minimumPriority: options.priority,
|
|
71245
71594
|
isRichUiEnabled,
|
|
71246
71595
|
}));
|
|
71247
|
-
if (
|
|
71596
|
+
if (!promptQueueSnapshot.nextPrompt) {
|
|
71597
|
+
if (options.keepAlive) {
|
|
71598
|
+
announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
71599
|
+
await new Promise((resolve) => setTimeout(resolve, KEEP_ALIVE_POLL_INTERVAL_MS));
|
|
71600
|
+
continue;
|
|
71601
|
+
}
|
|
71602
|
+
finishWhenNoPromptIsAvailable(promptQueueSnapshot, isRichUiEnabled, uiHandle);
|
|
71248
71603
|
return;
|
|
71249
71604
|
}
|
|
71250
71605
|
const nextPrompt = promptQueueSnapshot.nextPrompt;
|
|
@@ -71283,6 +71638,7 @@
|
|
|
71283
71638
|
nextPrompt,
|
|
71284
71639
|
promptLabel,
|
|
71285
71640
|
resolvedCoderContext,
|
|
71641
|
+
resolvedAgentSystemMessage,
|
|
71286
71642
|
isRichUiEnabled,
|
|
71287
71643
|
progressDisplay,
|
|
71288
71644
|
uiHandle,
|
|
@@ -71458,6 +71814,19 @@
|
|
|
71458
71814
|
}
|
|
71459
71815
|
return true;
|
|
71460
71816
|
}
|
|
71817
|
+
/**
|
|
71818
|
+
* Updates the UI status message while waiting for new prompts in keepAlive server mode.
|
|
71819
|
+
*/
|
|
71820
|
+
function announceKeepAliveStatus(promptQueueSnapshot, isRichUiEnabled, uiHandle) {
|
|
71821
|
+
const message = promptQueueSnapshot.stats.toBeWritten > 0
|
|
71822
|
+
? 'No prompts ready for agent. Watching for changes...'
|
|
71823
|
+
: 'All prompts are done. Watching for changes...';
|
|
71824
|
+
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(message);
|
|
71825
|
+
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('waiting');
|
|
71826
|
+
if (!isRichUiEnabled) {
|
|
71827
|
+
console.info(colors__default["default"].gray(message));
|
|
71828
|
+
}
|
|
71829
|
+
}
|
|
71461
71830
|
/**
|
|
71462
71831
|
* Updates UI state and plain-console output for the terminal completion message.
|
|
71463
71832
|
*/
|
|
@@ -71498,6 +71867,10 @@
|
|
|
71498
71867
|
* Countdown update interval for the between-rounds wait display.
|
|
71499
71868
|
*/
|
|
71500
71869
|
const WAIT_COUNTDOWN_UPDATE_INTERVAL_MS = 30000;
|
|
71870
|
+
/**
|
|
71871
|
+
* Polling interval when in keepAlive server mode and no runnable prompts are available.
|
|
71872
|
+
*/
|
|
71873
|
+
const KEEP_ALIVE_POLL_INTERVAL_MS = 5000;
|
|
71501
71874
|
/**
|
|
71502
71875
|
* Waits the configured time between prompt rounds to avoid hitting harness rate limits.
|
|
71503
71876
|
* Does nothing when `waitBetweenPrompts` is zero.
|
|
@@ -71543,6 +71916,780 @@
|
|
|
71543
71916
|
runCodexPrompts: runCodexPrompts
|
|
71544
71917
|
});
|
|
71545
71918
|
|
|
71919
|
+
/**
|
|
71920
|
+
* Overwrites the body of one prompt section with new content, preserving the status line.
|
|
71921
|
+
*
|
|
71922
|
+
* The `newContent` string is the prompt text without the status marker.
|
|
71923
|
+
* The status line (`[ ]`, `[x]`, `[!]`, `[-]`) is kept intact.
|
|
71924
|
+
*
|
|
71925
|
+
* @private internal utility of `ptbk coder server`
|
|
71926
|
+
*/
|
|
71927
|
+
async function updatePromptSection(filePath, sectionIndex, newContent) {
|
|
71928
|
+
var _a;
|
|
71929
|
+
const rawContent = await promises.readFile(filePath, 'utf-8');
|
|
71930
|
+
const promptFile = parsePromptFile(filePath, rawContent);
|
|
71931
|
+
const section = promptFile.sections.find((s) => s.index === sectionIndex);
|
|
71932
|
+
if (!section) {
|
|
71933
|
+
throw new Error(`Section ${sectionIndex} not found in file: ${filePath}`);
|
|
71934
|
+
}
|
|
71935
|
+
// Lines before the status line (usually empty lines at section start)
|
|
71936
|
+
const linesBeforeStatus = section.statusLineIndex !== undefined
|
|
71937
|
+
? promptFile.lines.slice(section.startLine, section.statusLineIndex)
|
|
71938
|
+
: [];
|
|
71939
|
+
// The status line itself (preserved exactly)
|
|
71940
|
+
const statusLineContent = section.statusLineIndex !== undefined ? [promptFile.lines[section.statusLineIndex]] : [];
|
|
71941
|
+
// Split the new content and trim trailing blank lines
|
|
71942
|
+
const newContentLines = newContent.split(/\r?\n/);
|
|
71943
|
+
while (newContentLines.length > 0 && ((_a = newContentLines[newContentLines.length - 1]) === null || _a === void 0 ? void 0 : _a.trim()) === '') {
|
|
71944
|
+
newContentLines.pop();
|
|
71945
|
+
}
|
|
71946
|
+
// Build the reconstructed section lines
|
|
71947
|
+
const newSectionLines = [
|
|
71948
|
+
...linesBeforeStatus,
|
|
71949
|
+
...statusLineContent,
|
|
71950
|
+
...(statusLineContent.length > 0 ? [''] : []),
|
|
71951
|
+
...newContentLines,
|
|
71952
|
+
];
|
|
71953
|
+
// Splice the new section lines into the full file line array
|
|
71954
|
+
const updatedLines = [
|
|
71955
|
+
...promptFile.lines.slice(0, section.startLine),
|
|
71956
|
+
...newSectionLines,
|
|
71957
|
+
...promptFile.lines.slice(section.endLine + 1),
|
|
71958
|
+
];
|
|
71959
|
+
const updatedContent = updatedLines.join(promptFile.eol) + (promptFile.hasFinalEol ? promptFile.eol : '');
|
|
71960
|
+
await promises.writeFile(filePath, updatedContent, 'utf-8');
|
|
71961
|
+
}
|
|
71962
|
+
// Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/updatePromptSection.ts) should never be published outside of `@promptbook/cli`
|
|
71963
|
+
|
|
71964
|
+
/**
|
|
71965
|
+
* HTML source for the coder server kanban UI.
|
|
71966
|
+
*
|
|
71967
|
+
* The canonical source lives in `apps/coder-server/index.html`.
|
|
71968
|
+
* Keep both files in sync when updating the frontend.
|
|
71969
|
+
*
|
|
71970
|
+
* @private internal constant of `ptbk coder server`
|
|
71971
|
+
*/
|
|
71972
|
+
const CODER_SERVER_HTML = `<!DOCTYPE html>
|
|
71973
|
+
<html lang="en">
|
|
71974
|
+
<head>
|
|
71975
|
+
<meta charset="UTF-8">
|
|
71976
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
71977
|
+
<title>Ptbk Coder Server</title>
|
|
71978
|
+
<style>
|
|
71979
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
71980
|
+
body {
|
|
71981
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
71982
|
+
background: #f4f5f7;
|
|
71983
|
+
color: #172b4d;
|
|
71984
|
+
min-height: 100vh;
|
|
71985
|
+
}
|
|
71986
|
+
|
|
71987
|
+
header {
|
|
71988
|
+
background: #0052cc;
|
|
71989
|
+
color: white;
|
|
71990
|
+
padding: 12px 20px;
|
|
71991
|
+
display: flex;
|
|
71992
|
+
align-items: center;
|
|
71993
|
+
gap: 12px;
|
|
71994
|
+
position: sticky;
|
|
71995
|
+
top: 0;
|
|
71996
|
+
z-index: 10;
|
|
71997
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
71998
|
+
}
|
|
71999
|
+
header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; }
|
|
72000
|
+
|
|
72001
|
+
.status-badge {
|
|
72002
|
+
padding: 3px 10px;
|
|
72003
|
+
border-radius: 12px;
|
|
72004
|
+
font-size: 11px;
|
|
72005
|
+
font-weight: 700;
|
|
72006
|
+
letter-spacing: 0.5px;
|
|
72007
|
+
text-transform: uppercase;
|
|
72008
|
+
flex-shrink: 0;
|
|
72009
|
+
}
|
|
72010
|
+
.status-RUNNING { background: #00875a; color: white; }
|
|
72011
|
+
.status-PAUSING { background: #ff8b00; color: white; }
|
|
72012
|
+
.status-PAUSED { background: #bf2600; color: white; }
|
|
72013
|
+
|
|
72014
|
+
#pause-label {
|
|
72015
|
+
font-size: 12px;
|
|
72016
|
+
color: rgba(255,255,255,0.75);
|
|
72017
|
+
flex: 1;
|
|
72018
|
+
min-width: 0;
|
|
72019
|
+
overflow: hidden;
|
|
72020
|
+
text-overflow: ellipsis;
|
|
72021
|
+
white-space: nowrap;
|
|
72022
|
+
}
|
|
72023
|
+
|
|
72024
|
+
.btn {
|
|
72025
|
+
padding: 6px 14px;
|
|
72026
|
+
border: none;
|
|
72027
|
+
border-radius: 4px;
|
|
72028
|
+
cursor: pointer;
|
|
72029
|
+
font-size: 13px;
|
|
72030
|
+
font-weight: 600;
|
|
72031
|
+
transition: opacity 0.15s;
|
|
72032
|
+
flex-shrink: 0;
|
|
72033
|
+
}
|
|
72034
|
+
.btn:hover { opacity: 0.85; }
|
|
72035
|
+
.btn-pause { background: #ffc400; color: #172b4d; }
|
|
72036
|
+
.btn-resume { background: #00875a; color: white; }
|
|
72037
|
+
|
|
72038
|
+
.board {
|
|
72039
|
+
display: flex;
|
|
72040
|
+
gap: 14px;
|
|
72041
|
+
padding: 16px;
|
|
72042
|
+
overflow-x: auto;
|
|
72043
|
+
min-height: calc(100vh - 52px);
|
|
72044
|
+
align-items: flex-start;
|
|
72045
|
+
}
|
|
72046
|
+
|
|
72047
|
+
.column {
|
|
72048
|
+
background: #ebecf0;
|
|
72049
|
+
border-radius: 8px;
|
|
72050
|
+
padding: 10px;
|
|
72051
|
+
min-width: 250px;
|
|
72052
|
+
width: 270px;
|
|
72053
|
+
flex-shrink: 0;
|
|
72054
|
+
}
|
|
72055
|
+
|
|
72056
|
+
.column-header {
|
|
72057
|
+
font-size: 12px;
|
|
72058
|
+
font-weight: 700;
|
|
72059
|
+
text-transform: uppercase;
|
|
72060
|
+
letter-spacing: 0.6px;
|
|
72061
|
+
color: #5e6c84;
|
|
72062
|
+
margin-bottom: 10px;
|
|
72063
|
+
display: flex;
|
|
72064
|
+
align-items: center;
|
|
72065
|
+
justify-content: space-between;
|
|
72066
|
+
}
|
|
72067
|
+
|
|
72068
|
+
.column-count {
|
|
72069
|
+
background: #dfe1e6;
|
|
72070
|
+
border-radius: 10px;
|
|
72071
|
+
padding: 1px 7px;
|
|
72072
|
+
font-size: 11px;
|
|
72073
|
+
color: #5e6c84;
|
|
72074
|
+
}
|
|
72075
|
+
|
|
72076
|
+
.column-cards { min-height: 40px; }
|
|
72077
|
+
|
|
72078
|
+
.card {
|
|
72079
|
+
background: white;
|
|
72080
|
+
border-radius: 6px;
|
|
72081
|
+
padding: 10px 12px;
|
|
72082
|
+
margin-bottom: 8px;
|
|
72083
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
72084
|
+
cursor: pointer;
|
|
72085
|
+
transition: box-shadow 0.15s, transform 0.1s;
|
|
72086
|
+
border-left: 3px solid transparent;
|
|
72087
|
+
}
|
|
72088
|
+
.card:hover {
|
|
72089
|
+
box-shadow: 0 4px 10px rgba(0,0,0,0.14);
|
|
72090
|
+
transform: translateY(-1px);
|
|
72091
|
+
}
|
|
72092
|
+
.card-todo { border-left-color: #0052cc; }
|
|
72093
|
+
.card-not-ready { border-left-color: #8993a4; }
|
|
72094
|
+
.card-done { border-left-color: #00875a; }
|
|
72095
|
+
.card-failed { border-left-color: #bf2600; }
|
|
72096
|
+
|
|
72097
|
+
.card-file {
|
|
72098
|
+
font-size: 10px;
|
|
72099
|
+
color: #8993a4;
|
|
72100
|
+
margin-bottom: 5px;
|
|
72101
|
+
font-weight: 500;
|
|
72102
|
+
}
|
|
72103
|
+
|
|
72104
|
+
.card-summary {
|
|
72105
|
+
font-size: 13px;
|
|
72106
|
+
line-height: 1.5;
|
|
72107
|
+
color: #172b4d;
|
|
72108
|
+
word-break: break-word;
|
|
72109
|
+
white-space: pre-wrap;
|
|
72110
|
+
max-height: 78px;
|
|
72111
|
+
overflow: hidden;
|
|
72112
|
+
display: -webkit-box;
|
|
72113
|
+
-webkit-line-clamp: 4;
|
|
72114
|
+
-webkit-box-orient: vertical;
|
|
72115
|
+
}
|
|
72116
|
+
|
|
72117
|
+
.card-priority {
|
|
72118
|
+
margin-top: 6px;
|
|
72119
|
+
color: #ff8b00;
|
|
72120
|
+
font-size: 11px;
|
|
72121
|
+
font-weight: 700;
|
|
72122
|
+
}
|
|
72123
|
+
|
|
72124
|
+
.card-edit-hint {
|
|
72125
|
+
font-size: 10px;
|
|
72126
|
+
color: #c1c7d0;
|
|
72127
|
+
margin-top: 6px;
|
|
72128
|
+
display: none;
|
|
72129
|
+
}
|
|
72130
|
+
.card:hover .card-edit-hint { display: block; }
|
|
72131
|
+
|
|
72132
|
+
.empty-column {
|
|
72133
|
+
color: #b3bac5;
|
|
72134
|
+
font-size: 12px;
|
|
72135
|
+
padding: 10px 4px;
|
|
72136
|
+
text-align: center;
|
|
72137
|
+
}
|
|
72138
|
+
|
|
72139
|
+
.modal-overlay {
|
|
72140
|
+
position: fixed;
|
|
72141
|
+
inset: 0;
|
|
72142
|
+
background: rgba(9,30,66,0.54);
|
|
72143
|
+
display: flex;
|
|
72144
|
+
align-items: flex-start;
|
|
72145
|
+
justify-content: center;
|
|
72146
|
+
padding: 60px 16px 16px;
|
|
72147
|
+
z-index: 100;
|
|
72148
|
+
animation: fadeIn 0.1s ease;
|
|
72149
|
+
}
|
|
72150
|
+
.modal-overlay.hidden { display: none; }
|
|
72151
|
+
|
|
72152
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
72153
|
+
|
|
72154
|
+
.modal {
|
|
72155
|
+
background: white;
|
|
72156
|
+
border-radius: 8px;
|
|
72157
|
+
width: 100%;
|
|
72158
|
+
max-width: 620px;
|
|
72159
|
+
padding: 20px;
|
|
72160
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
|
|
72161
|
+
animation: slideIn 0.15s ease;
|
|
72162
|
+
}
|
|
72163
|
+
@keyframes slideIn { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
72164
|
+
|
|
72165
|
+
.modal-header {
|
|
72166
|
+
display: flex;
|
|
72167
|
+
align-items: flex-start;
|
|
72168
|
+
justify-content: space-between;
|
|
72169
|
+
margin-bottom: 14px;
|
|
72170
|
+
gap: 12px;
|
|
72171
|
+
}
|
|
72172
|
+
|
|
72173
|
+
.modal-meta { flex: 1; min-width: 0; }
|
|
72174
|
+
.modal-file { font-size: 11px; color: #8993a4; margin-bottom: 4px; }
|
|
72175
|
+
.modal-section-label { font-size: 13px; font-weight: 600; color: #172b4d; }
|
|
72176
|
+
|
|
72177
|
+
.modal-status {
|
|
72178
|
+
font-size: 11px;
|
|
72179
|
+
font-weight: 700;
|
|
72180
|
+
padding: 2px 8px;
|
|
72181
|
+
border-radius: 10px;
|
|
72182
|
+
text-transform: uppercase;
|
|
72183
|
+
letter-spacing: 0.4px;
|
|
72184
|
+
flex-shrink: 0;
|
|
72185
|
+
}
|
|
72186
|
+
.status-todo { background: #deebff; color: #0052cc; }
|
|
72187
|
+
.status-not-ready { background: #f4f5f7; color: #5e6c84; }
|
|
72188
|
+
.status-done { background: #e3fcef; color: #006644; }
|
|
72189
|
+
.status-failed { background: #ffebe6; color: #bf2600; }
|
|
72190
|
+
|
|
72191
|
+
.modal-close {
|
|
72192
|
+
background: none;
|
|
72193
|
+
border: none;
|
|
72194
|
+
color: #8993a4;
|
|
72195
|
+
cursor: pointer;
|
|
72196
|
+
font-size: 18px;
|
|
72197
|
+
padding: 0 4px;
|
|
72198
|
+
line-height: 1;
|
|
72199
|
+
flex-shrink: 0;
|
|
72200
|
+
}
|
|
72201
|
+
.modal-close:hover { color: #172b4d; }
|
|
72202
|
+
|
|
72203
|
+
textarea {
|
|
72204
|
+
width: 100%;
|
|
72205
|
+
min-height: 220px;
|
|
72206
|
+
font-family: 'SFMono-Regular', 'Consolas', 'Courier New', monospace;
|
|
72207
|
+
font-size: 13px;
|
|
72208
|
+
border: 2px solid #dfe1e6;
|
|
72209
|
+
border-radius: 4px;
|
|
72210
|
+
padding: 10px 12px;
|
|
72211
|
+
resize: vertical;
|
|
72212
|
+
line-height: 1.65;
|
|
72213
|
+
color: #172b4d;
|
|
72214
|
+
transition: border-color 0.15s;
|
|
72215
|
+
}
|
|
72216
|
+
textarea:focus {
|
|
72217
|
+
outline: none;
|
|
72218
|
+
border-color: #0052cc;
|
|
72219
|
+
box-shadow: 0 0 0 3px rgba(0,82,204,0.12);
|
|
72220
|
+
}
|
|
72221
|
+
|
|
72222
|
+
.modal-hint {
|
|
72223
|
+
font-size: 11px;
|
|
72224
|
+
color: #8993a4;
|
|
72225
|
+
margin-top: 6px;
|
|
72226
|
+
}
|
|
72227
|
+
|
|
72228
|
+
.modal-actions {
|
|
72229
|
+
display: flex;
|
|
72230
|
+
gap: 8px;
|
|
72231
|
+
margin-top: 14px;
|
|
72232
|
+
justify-content: flex-end;
|
|
72233
|
+
}
|
|
72234
|
+
|
|
72235
|
+
.btn-save { background: #0052cc; color: white; }
|
|
72236
|
+
.btn-cancel { background: #f4f5f7; color: #172b4d; }
|
|
72237
|
+
.btn-cancel:hover { background: #ebecf0; opacity: 1; }
|
|
72238
|
+
|
|
72239
|
+
.error-banner {
|
|
72240
|
+
background: #ffebe6;
|
|
72241
|
+
border-bottom: 2px solid #bf2600;
|
|
72242
|
+
color: #bf2600;
|
|
72243
|
+
padding: 8px 20px;
|
|
72244
|
+
font-size: 13px;
|
|
72245
|
+
font-weight: 500;
|
|
72246
|
+
}
|
|
72247
|
+
.error-banner.hidden { display: none; }
|
|
72248
|
+
</style>
|
|
72249
|
+
</head>
|
|
72250
|
+
<body>
|
|
72251
|
+
|
|
72252
|
+
<div id="error-banner" class="error-banner hidden"></div>
|
|
72253
|
+
|
|
72254
|
+
<header>
|
|
72255
|
+
<h1>🔧 Ptbk Coder Server</h1>
|
|
72256
|
+
<span id="status-badge" class="status-badge status-RUNNING">RUNNING</span>
|
|
72257
|
+
<span id="pause-label"></span>
|
|
72258
|
+
<button id="toggle-btn" class="btn btn-pause">▮▮ Pause</button>
|
|
72259
|
+
</header>
|
|
72260
|
+
|
|
72261
|
+
<div class="board">
|
|
72262
|
+
<div class="column">
|
|
72263
|
+
<div class="column-header">
|
|
72264
|
+
To Do
|
|
72265
|
+
<span class="column-count" id="count-todo">0</span>
|
|
72266
|
+
</div>
|
|
72267
|
+
<div class="column-cards" id="cards-todo">
|
|
72268
|
+
<div class="empty-column">Loading…</div>
|
|
72269
|
+
</div>
|
|
72270
|
+
</div>
|
|
72271
|
+
|
|
72272
|
+
<div class="column">
|
|
72273
|
+
<div class="column-header">
|
|
72274
|
+
Not Ready
|
|
72275
|
+
<span class="column-count" id="count-not-ready">0</span>
|
|
72276
|
+
</div>
|
|
72277
|
+
<div class="column-cards" id="cards-not-ready"></div>
|
|
72278
|
+
</div>
|
|
72279
|
+
|
|
72280
|
+
<div class="column">
|
|
72281
|
+
<div class="column-header">
|
|
72282
|
+
Done
|
|
72283
|
+
<span class="column-count" id="count-done">0</span>
|
|
72284
|
+
</div>
|
|
72285
|
+
<div class="column-cards" id="cards-done"></div>
|
|
72286
|
+
</div>
|
|
72287
|
+
|
|
72288
|
+
<div class="column">
|
|
72289
|
+
<div class="column-header">
|
|
72290
|
+
Failed
|
|
72291
|
+
<span class="column-count" id="count-failed">0</span>
|
|
72292
|
+
</div>
|
|
72293
|
+
<div class="column-cards" id="cards-failed"></div>
|
|
72294
|
+
</div>
|
|
72295
|
+
</div>
|
|
72296
|
+
|
|
72297
|
+
<div class="modal-overlay hidden" id="modal-overlay">
|
|
72298
|
+
<div class="modal">
|
|
72299
|
+
<div class="modal-header">
|
|
72300
|
+
<div class="modal-meta">
|
|
72301
|
+
<div class="modal-file" id="modal-file"></div>
|
|
72302
|
+
<div class="modal-section-label" id="modal-section-label"></div>
|
|
72303
|
+
</div>
|
|
72304
|
+
<span class="modal-status" id="modal-status-badge"></span>
|
|
72305
|
+
<button class="modal-close" onclick="closeModal()" title="Close (Esc)">✕</button>
|
|
72306
|
+
</div>
|
|
72307
|
+
|
|
72308
|
+
<textarea id="modal-content" placeholder="Prompt content…"></textarea>
|
|
72309
|
+
<div class="modal-hint">Tip: press Ctrl+Enter to save, Esc to cancel.</div>
|
|
72310
|
+
|
|
72311
|
+
<div class="modal-actions">
|
|
72312
|
+
<button class="btn btn-cancel" onclick="closeModal()">Cancel</button>
|
|
72313
|
+
<button class="btn btn-save" onclick="saveModal()">Save</button>
|
|
72314
|
+
</div>
|
|
72315
|
+
</div>
|
|
72316
|
+
</div>
|
|
72317
|
+
|
|
72318
|
+
<script>
|
|
72319
|
+
'use strict';
|
|
72320
|
+
|
|
72321
|
+
let modalState = null;
|
|
72322
|
+
let lastPauseState = 'RUNNING';
|
|
72323
|
+
|
|
72324
|
+
function showError(msg) {
|
|
72325
|
+
const banner = document.getElementById('error-banner');
|
|
72326
|
+
banner.textContent = '\\u26a0 ' + msg;
|
|
72327
|
+
banner.classList.remove('hidden');
|
|
72328
|
+
clearTimeout(banner._timer);
|
|
72329
|
+
banner._timer = setTimeout(() => banner.classList.add('hidden'), 6000);
|
|
72330
|
+
}
|
|
72331
|
+
|
|
72332
|
+
function escapeHtml(str) {
|
|
72333
|
+
const d = document.createElement('div');
|
|
72334
|
+
d.textContent = String(str);
|
|
72335
|
+
return d.innerHTML;
|
|
72336
|
+
}
|
|
72337
|
+
|
|
72338
|
+
async function fetchStatus() {
|
|
72339
|
+
try {
|
|
72340
|
+
const res = await fetch('/api/status');
|
|
72341
|
+
if (!res.ok) { showError('Status API error: ' + res.status); return; }
|
|
72342
|
+
const data = await res.json();
|
|
72343
|
+
|
|
72344
|
+
lastPauseState = data.pauseState;
|
|
72345
|
+
|
|
72346
|
+
const badge = document.getElementById('status-badge');
|
|
72347
|
+
badge.textContent = data.pauseState;
|
|
72348
|
+
badge.className = 'status-badge status-' + data.pauseState;
|
|
72349
|
+
|
|
72350
|
+
const btn = document.getElementById('toggle-btn');
|
|
72351
|
+
const label = document.getElementById('pause-label');
|
|
72352
|
+
|
|
72353
|
+
if (data.pauseState === 'RUNNING') {
|
|
72354
|
+
btn.textContent = '\\u23f8 Pause';
|
|
72355
|
+
btn.className = 'btn btn-pause';
|
|
72356
|
+
label.textContent = '';
|
|
72357
|
+
} else if (data.pauseState === 'PAUSING') {
|
|
72358
|
+
btn.textContent = '\\u23f5 Resume';
|
|
72359
|
+
btn.className = 'btn btn-resume';
|
|
72360
|
+
label.textContent = 'Pausing before: ' + (data.pauseTargetLabel || '\\u2026');
|
|
72361
|
+
} else {
|
|
72362
|
+
btn.textContent = '\\u23f5 Resume';
|
|
72363
|
+
btn.className = 'btn btn-resume';
|
|
72364
|
+
label.textContent = 'Paused before: ' + (data.pauseTargetLabel || '\\u2026');
|
|
72365
|
+
}
|
|
72366
|
+
} catch (e) {
|
|
72367
|
+
showError('Could not reach coder server: ' + e.message);
|
|
72368
|
+
}
|
|
72369
|
+
}
|
|
72370
|
+
|
|
72371
|
+
async function fetchPrompts() {
|
|
72372
|
+
try {
|
|
72373
|
+
const res = await fetch('/api/prompts');
|
|
72374
|
+
if (!res.ok) { showError('Prompts API error: ' + res.status); return; }
|
|
72375
|
+
renderBoard(await res.json());
|
|
72376
|
+
} catch (e) {
|
|
72377
|
+
showError('Could not load prompts: ' + e.message);
|
|
72378
|
+
}
|
|
72379
|
+
}
|
|
72380
|
+
|
|
72381
|
+
function renderBoard(promptFiles) {
|
|
72382
|
+
const columns = { 'todo': [], 'not-ready': [], 'done': [], 'failed': [] };
|
|
72383
|
+
|
|
72384
|
+
for (const file of promptFiles) {
|
|
72385
|
+
for (const section of file.sections) {
|
|
72386
|
+
const col = columns[section.status];
|
|
72387
|
+
if (col) col.push({ file, section });
|
|
72388
|
+
}
|
|
72389
|
+
}
|
|
72390
|
+
|
|
72391
|
+
for (const [status, cards] of Object.entries(columns)) {
|
|
72392
|
+
const container = document.getElementById('cards-' + status);
|
|
72393
|
+
const countEl = document.getElementById('count-' + status);
|
|
72394
|
+
if (!container) continue;
|
|
72395
|
+
|
|
72396
|
+
countEl.textContent = cards.length;
|
|
72397
|
+
container.innerHTML = '';
|
|
72398
|
+
|
|
72399
|
+
if (cards.length === 0) {
|
|
72400
|
+
container.innerHTML = '<div class="empty-column">Empty</div>';
|
|
72401
|
+
continue;
|
|
72402
|
+
}
|
|
72403
|
+
|
|
72404
|
+
for (const { file, section } of cards) {
|
|
72405
|
+
const card = document.createElement('div');
|
|
72406
|
+
card.className = 'card card-' + section.status;
|
|
72407
|
+
card.innerHTML =
|
|
72408
|
+
'<div class="card-file">' + escapeHtml(file.fileName) + ' • #' + (section.index + 1) + '</div>' +
|
|
72409
|
+
'<div class="card-summary">' + escapeHtml(section.summary) + '</div>' +
|
|
72410
|
+
(section.priority > 0
|
|
72411
|
+
? '<div class="card-priority">' + '!'.repeat(section.priority) + ' priority ' + section.priority + '</div>'
|
|
72412
|
+
: '') +
|
|
72413
|
+
'<div class="card-edit-hint">Click to edit</div>';
|
|
72414
|
+
card.onclick = () => openModal(file, section);
|
|
72415
|
+
container.appendChild(card);
|
|
72416
|
+
}
|
|
72417
|
+
}
|
|
72418
|
+
}
|
|
72419
|
+
|
|
72420
|
+
function openModal(file, section) {
|
|
72421
|
+
modalState = { filePath: file.filePath, sectionIndex: section.index };
|
|
72422
|
+
|
|
72423
|
+
document.getElementById('modal-file').textContent = file.fileName;
|
|
72424
|
+
document.getElementById('modal-section-label').textContent = 'Section ' + (section.index + 1);
|
|
72425
|
+
|
|
72426
|
+
const badge = document.getElementById('modal-status-badge');
|
|
72427
|
+
badge.textContent = section.status.replace('-', '\\u2011');
|
|
72428
|
+
badge.className = 'modal-status status-' + section.status;
|
|
72429
|
+
|
|
72430
|
+
document.getElementById('modal-content').value = section.content;
|
|
72431
|
+
document.getElementById('modal-overlay').classList.remove('hidden');
|
|
72432
|
+
setTimeout(() => document.getElementById('modal-content').focus(), 50);
|
|
72433
|
+
}
|
|
72434
|
+
|
|
72435
|
+
function closeModal() {
|
|
72436
|
+
document.getElementById('modal-overlay').classList.add('hidden');
|
|
72437
|
+
modalState = null;
|
|
72438
|
+
}
|
|
72439
|
+
|
|
72440
|
+
async function saveModal() {
|
|
72441
|
+
if (!modalState) return;
|
|
72442
|
+
|
|
72443
|
+
const content = document.getElementById('modal-content').value;
|
|
72444
|
+
const saveBtn = document.querySelector('.btn-save');
|
|
72445
|
+
saveBtn.disabled = true;
|
|
72446
|
+
saveBtn.textContent = 'Saving\\u2026';
|
|
72447
|
+
|
|
72448
|
+
try {
|
|
72449
|
+
const res = await fetch('/api/prompts/update', {
|
|
72450
|
+
method: 'PUT',
|
|
72451
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72452
|
+
body: JSON.stringify({
|
|
72453
|
+
filePath: modalState.filePath,
|
|
72454
|
+
sectionIndex: modalState.sectionIndex,
|
|
72455
|
+
content,
|
|
72456
|
+
}),
|
|
72457
|
+
});
|
|
72458
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
72459
|
+
closeModal();
|
|
72460
|
+
fetchPrompts();
|
|
72461
|
+
} catch (e) {
|
|
72462
|
+
showError('Save failed: ' + e.message);
|
|
72463
|
+
} finally {
|
|
72464
|
+
saveBtn.disabled = false;
|
|
72465
|
+
saveBtn.textContent = 'Save';
|
|
72466
|
+
}
|
|
72467
|
+
}
|
|
72468
|
+
|
|
72469
|
+
document.getElementById('toggle-btn').onclick = async () => {
|
|
72470
|
+
try {
|
|
72471
|
+
const endpoint = lastPauseState === 'RUNNING' ? '/api/pause' : '/api/resume';
|
|
72472
|
+
await fetch(endpoint, { method: 'POST' });
|
|
72473
|
+
await fetchStatus();
|
|
72474
|
+
} catch (e) {
|
|
72475
|
+
showError('Toggle failed: ' + e.message);
|
|
72476
|
+
}
|
|
72477
|
+
};
|
|
72478
|
+
|
|
72479
|
+
document.addEventListener('keydown', (e) => {
|
|
72480
|
+
if (e.key === 'Escape') { closeModal(); return; }
|
|
72481
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { saveModal(); return; }
|
|
72482
|
+
});
|
|
72483
|
+
|
|
72484
|
+
document.getElementById('modal-overlay').addEventListener('click', (e) => {
|
|
72485
|
+
if (e.target === document.getElementById('modal-overlay')) closeModal();
|
|
72486
|
+
});
|
|
72487
|
+
|
|
72488
|
+
fetchStatus();
|
|
72489
|
+
fetchPrompts();
|
|
72490
|
+
setInterval(fetchStatus, 2000);
|
|
72491
|
+
setInterval(fetchPrompts, 5000);
|
|
72492
|
+
</script>
|
|
72493
|
+
</body>
|
|
72494
|
+
</html>`;
|
|
72495
|
+
// Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/coderServerHtml.ts) should never be published outside of `@promptbook/cli`
|
|
72496
|
+
// Note: Keep in sync with apps/coder-server/index.html
|
|
72497
|
+
|
|
72498
|
+
/**
|
|
72499
|
+
* Directory from which prompt files are loaded, relative to the current working directory.
|
|
72500
|
+
*
|
|
72501
|
+
* @private internal constant of `ptbk coder server`
|
|
72502
|
+
*/
|
|
72503
|
+
const PROMPTS_DIRECTORY_NAME = 'prompts';
|
|
72504
|
+
/**
|
|
72505
|
+
* Starts the lightweight HTTP server that serves the coder kanban UI and REST API.
|
|
72506
|
+
*
|
|
72507
|
+
* API endpoints:
|
|
72508
|
+
* - `GET /` → HTML kanban page
|
|
72509
|
+
* - `GET /api/prompts` → JSON list of all prompt files and sections
|
|
72510
|
+
* - `GET /api/status` → JSON current pause state
|
|
72511
|
+
* - `POST /api/pause` → request pause
|
|
72512
|
+
* - `POST /api/resume` → request resume
|
|
72513
|
+
* - `PUT /api/prompts/update` → update prompt section content
|
|
72514
|
+
*
|
|
72515
|
+
* @private internal utility of `ptbk coder server`
|
|
72516
|
+
*/
|
|
72517
|
+
function startCoderHttpServer(port) {
|
|
72518
|
+
const promptsDir = path.join(process.cwd(), PROMPTS_DIRECTORY_NAME);
|
|
72519
|
+
const server = http.createServer(async (req, res) => {
|
|
72520
|
+
try {
|
|
72521
|
+
await handleRequest(req, res, promptsDir);
|
|
72522
|
+
}
|
|
72523
|
+
catch (error) {
|
|
72524
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72525
|
+
console.error(colors__default["default"].red(`Coder server request error: ${message}`));
|
|
72526
|
+
if (!res.headersSent) {
|
|
72527
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
72528
|
+
}
|
|
72529
|
+
res.end('Internal server error');
|
|
72530
|
+
}
|
|
72531
|
+
});
|
|
72532
|
+
server.listen(port, () => {
|
|
72533
|
+
console.info(_spaceTrim.spaceTrim(`
|
|
72534
|
+
Coder server running at ${colors__default["default"].cyan(`http://localhost:${port}`)}
|
|
72535
|
+
Open the URL above in your browser to see the kanban board.
|
|
72536
|
+
Press ${colors__default["default"].bold('p')} to pause / resume the agent runner.
|
|
72537
|
+
`));
|
|
72538
|
+
});
|
|
72539
|
+
return {
|
|
72540
|
+
close: () => {
|
|
72541
|
+
server.close();
|
|
72542
|
+
},
|
|
72543
|
+
};
|
|
72544
|
+
}
|
|
72545
|
+
/**
|
|
72546
|
+
* Routes one HTTP request to the appropriate handler.
|
|
72547
|
+
*/
|
|
72548
|
+
async function handleRequest(req, res, promptsDir) {
|
|
72549
|
+
const urlPath = new URL(req.url || '/', `http://localhost`).pathname;
|
|
72550
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
72551
|
+
// Serve the kanban UI
|
|
72552
|
+
if (urlPath === '/' && method === 'GET') {
|
|
72553
|
+
res.writeHead(200, {
|
|
72554
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
72555
|
+
'Cache-Control': 'no-cache',
|
|
72556
|
+
});
|
|
72557
|
+
res.end(CODER_SERVER_HTML);
|
|
72558
|
+
return;
|
|
72559
|
+
}
|
|
72560
|
+
// GET /api/status
|
|
72561
|
+
if (urlPath === '/api/status' && method === 'GET') {
|
|
72562
|
+
res.writeHead(200, jsonHeaders());
|
|
72563
|
+
res.end(JSON.stringify({
|
|
72564
|
+
pauseState: getPauseState(),
|
|
72565
|
+
pauseTargetLabel: getPauseTargetLabel(),
|
|
72566
|
+
}));
|
|
72567
|
+
return;
|
|
72568
|
+
}
|
|
72569
|
+
// GET /api/prompts
|
|
72570
|
+
if (urlPath === '/api/prompts' && method === 'GET') {
|
|
72571
|
+
const promptData = await loadPromptsForApi(promptsDir);
|
|
72572
|
+
res.writeHead(200, jsonHeaders());
|
|
72573
|
+
res.end(JSON.stringify(promptData));
|
|
72574
|
+
return;
|
|
72575
|
+
}
|
|
72576
|
+
// POST /api/pause
|
|
72577
|
+
if (urlPath === '/api/pause' && method === 'POST') {
|
|
72578
|
+
requestPause();
|
|
72579
|
+
res.writeHead(200, jsonHeaders());
|
|
72580
|
+
res.end(JSON.stringify({ pauseState: getPauseState() }));
|
|
72581
|
+
return;
|
|
72582
|
+
}
|
|
72583
|
+
// POST /api/resume
|
|
72584
|
+
if (urlPath === '/api/resume' && method === 'POST') {
|
|
72585
|
+
requestResume();
|
|
72586
|
+
res.writeHead(200, jsonHeaders());
|
|
72587
|
+
res.end(JSON.stringify({ pauseState: getPauseState() }));
|
|
72588
|
+
return;
|
|
72589
|
+
}
|
|
72590
|
+
// PUT /api/prompts/update
|
|
72591
|
+
if (urlPath === '/api/prompts/update' && method === 'PUT') {
|
|
72592
|
+
const body = await readRequestBody(req);
|
|
72593
|
+
const parsed = JSON.parse(body);
|
|
72594
|
+
if (typeof parsed.filePath !== 'string' ||
|
|
72595
|
+
typeof parsed.sectionIndex !== 'number' ||
|
|
72596
|
+
typeof parsed.content !== 'string') {
|
|
72597
|
+
throw new NotAllowed(_spaceTrim.spaceTrim(`
|
|
72598
|
+
Invalid request body for \`PUT /api/prompts/update\`.
|
|
72599
|
+
|
|
72600
|
+
Expected: \`{ filePath: string, sectionIndex: number, content: string }\`
|
|
72601
|
+
`));
|
|
72602
|
+
}
|
|
72603
|
+
await updatePromptSection(parsed.filePath, parsed.sectionIndex, parsed.content);
|
|
72604
|
+
res.writeHead(200, jsonHeaders());
|
|
72605
|
+
res.end(JSON.stringify({ success: true }));
|
|
72606
|
+
return;
|
|
72607
|
+
}
|
|
72608
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
72609
|
+
res.end('Not found');
|
|
72610
|
+
}
|
|
72611
|
+
/**
|
|
72612
|
+
* Loads all prompt files and converts them to the API response shape.
|
|
72613
|
+
*/
|
|
72614
|
+
async function loadPromptsForApi(promptsDir) {
|
|
72615
|
+
try {
|
|
72616
|
+
const promptFiles = await loadPromptFiles(promptsDir);
|
|
72617
|
+
return promptFiles.map((file) => ({
|
|
72618
|
+
filePath: file.path,
|
|
72619
|
+
fileName: file.name,
|
|
72620
|
+
sections: file.sections.map((section) => ({
|
|
72621
|
+
index: section.index,
|
|
72622
|
+
status: section.status,
|
|
72623
|
+
priority: section.priority,
|
|
72624
|
+
summary: buildPromptSummary(file, section),
|
|
72625
|
+
content: buildCodexPrompt(file, section),
|
|
72626
|
+
})),
|
|
72627
|
+
}));
|
|
72628
|
+
}
|
|
72629
|
+
catch (error) {
|
|
72630
|
+
// Prompts directory may not exist yet; return empty list
|
|
72631
|
+
if (error.code === 'ENOENT') {
|
|
72632
|
+
return [];
|
|
72633
|
+
}
|
|
72634
|
+
throw error;
|
|
72635
|
+
}
|
|
72636
|
+
}
|
|
72637
|
+
/**
|
|
72638
|
+
* Reads the full request body as a UTF-8 string.
|
|
72639
|
+
*/
|
|
72640
|
+
function readRequestBody(req) {
|
|
72641
|
+
return new Promise((resolve, reject) => {
|
|
72642
|
+
const chunks = [];
|
|
72643
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
72644
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
72645
|
+
req.on('error', reject);
|
|
72646
|
+
});
|
|
72647
|
+
}
|
|
72648
|
+
/**
|
|
72649
|
+
* Returns standard JSON response headers with no-cache directives.
|
|
72650
|
+
*/
|
|
72651
|
+
function jsonHeaders() {
|
|
72652
|
+
return {
|
|
72653
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
72654
|
+
'Cache-Control': 'no-cache',
|
|
72655
|
+
};
|
|
72656
|
+
}
|
|
72657
|
+
// Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/server/runCoderHttpServer.ts) should never be published outside of `@promptbook/cli`
|
|
72658
|
+
|
|
72659
|
+
/**
|
|
72660
|
+
* Starts the coder server: runs prompt processing in keepAlive mode while serving a
|
|
72661
|
+
* kanban web UI and REST API on the given port.
|
|
72662
|
+
*
|
|
72663
|
+
* Differences from `runCodexPrompts`:
|
|
72664
|
+
* - Does not exit when no runnable prompts are available; instead it polls every few seconds
|
|
72665
|
+
* and resumes processing as soon as a new prompt becomes ready.
|
|
72666
|
+
* - Starts a lightweight HTTP server that serves a kanban board at `http://localhost:<port>`
|
|
72667
|
+
* and exposes REST endpoints for reading prompts, controlling pause state, and editing
|
|
72668
|
+
* prompt files directly from the browser.
|
|
72669
|
+
*
|
|
72670
|
+
* @private internal function of `ptbk coder server`
|
|
72671
|
+
*/
|
|
72672
|
+
async function runCodexPromptsServer(options) {
|
|
72673
|
+
const { port, ...runOptions } = options;
|
|
72674
|
+
const serverHandle = startCoderHttpServer(port);
|
|
72675
|
+
console.info(colors__default["default"].gray('Starting prompt runner in server (keep-alive) mode…'));
|
|
72676
|
+
try {
|
|
72677
|
+
await runCodexPrompts({
|
|
72678
|
+
...runOptions,
|
|
72679
|
+
keepAlive: true,
|
|
72680
|
+
});
|
|
72681
|
+
}
|
|
72682
|
+
finally {
|
|
72683
|
+
serverHandle.close();
|
|
72684
|
+
}
|
|
72685
|
+
}
|
|
72686
|
+
// Note: [🟡] Code for CLI command [coder server](scripts/run-codex-prompts/main/runCodexPromptsServer.ts) should never be published outside of `@promptbook/cli`
|
|
72687
|
+
|
|
72688
|
+
var runCodexPromptsServer$1 = /*#__PURE__*/Object.freeze({
|
|
72689
|
+
__proto__: null,
|
|
72690
|
+
runCodexPromptsServer: runCodexPromptsServer
|
|
72691
|
+
});
|
|
72692
|
+
|
|
71546
72693
|
/**
|
|
71547
72694
|
* Path to the directory that holds the prompt markdown files.
|
|
71548
72695
|
*/
|