@promptbook/cli 0.112.0-121 → 0.112.0-123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/messages/route.ts +4 -11
  2. package/esm/index.es.js +1353 -277
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +6 -0
  5. package/esm/scripts/run-codex-prompts/main/findUnwrittenPrompts.d.ts +19 -0
  6. package/esm/scripts/run-codex-prompts/main/runCodexPromptsServer.d.ts +27 -0
  7. package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
  8. package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +9 -0
  9. package/esm/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +23 -0
  10. package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +9 -0
  11. package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  12. package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
  13. package/esm/src/version.d.ts +1 -1
  14. package/package.json +1 -1
  15. package/src/cli/cli-commands/coder/find-unwritten.ts +58 -0
  16. package/src/cli/cli-commands/coder/init.ts +4 -1
  17. package/src/cli/cli-commands/coder/run.ts +26 -11
  18. package/src/cli/cli-commands/coder/server.ts +239 -0
  19. package/src/cli/cli-commands/coder.ts +6 -0
  20. package/src/other/templates/getTemplatesPipelineCollection.ts +825 -1034
  21. package/src/version.ts +2 -2
  22. package/src/versions.txt +2 -1
  23. package/umd/index.umd.js +1355 -279
  24. package/umd/index.umd.js.map +1 -1
  25. package/umd/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +6 -0
  26. package/umd/scripts/run-codex-prompts/main/findUnwrittenPrompts.d.ts +19 -0
  27. package/umd/scripts/run-codex-prompts/main/runCodexPromptsServer.d.ts +27 -0
  28. package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
  29. package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +9 -0
  30. package/umd/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +23 -0
  31. package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +9 -0
  32. package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
  33. package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
  34. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Reads an optional agent `.book` file and compiles its system message for injection into coder prompts.
3
+ *
4
+ * Returns `undefined` when no agent path is provided.
5
+ */
6
+ export declare function resolveAgentSystemMessage(agentPath: string | undefined, currentWorkingDirectory: string): Promise<string | undefined>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Options for `findUnwrittenPrompts`.
3
+ */
4
+ export type FindUnwrittenPromptsOptions = {
5
+ /**
6
+ * Minimum priority level — sections below this threshold are excluded.
7
+ * @default 0
8
+ */
9
+ readonly priority?: number;
10
+ };
11
+ /**
12
+ * Lists and prints all prompt sections that still need to be authored (contain `@@@` placeholder).
13
+ *
14
+ * Reuses the same filtering logic as `ptbk coder run --dry-run`.
15
+ *
16
+ * @returns the number of unwritten prompts found
17
+ * @public exported from `@promptbook/cli`
18
+ */
19
+ export declare function findUnwrittenPrompts(options?: FindUnwrittenPromptsOptions): Promise<number>;
@@ -0,0 +1,27 @@
1
+ import type { number_port } from '../../../src/types/number_positive';
2
+ import type { RunOptions } from '../cli/RunOptions';
3
+ /**
4
+ * Options for `ptbk coder server` combining the standard run options with a server port.
5
+ *
6
+ * @private internal type of `ptbk coder server`
7
+ */
8
+ export type CoderServerRunOptions = RunOptions & {
9
+ /**
10
+ * TCP port on which the kanban HTTP server will listen.
11
+ */
12
+ port: number_port;
13
+ };
14
+ /**
15
+ * Starts the coder server: runs prompt processing in keepAlive mode while serving a
16
+ * kanban web UI and REST API on the given port.
17
+ *
18
+ * Differences from `runCodexPrompts`:
19
+ * - Does not exit when no runnable prompts are available; instead it polls every few seconds
20
+ * and resumes processing as soon as a new prompt becomes ready.
21
+ * - Starts a lightweight HTTP server that serves a kanban board at `http://localhost:<port>`
22
+ * and exposes REST endpoints for reading prompts, controlling pause state, and editing
23
+ * prompt files directly from the browser.
24
+ *
25
+ * @private internal function of `ptbk coder server`
26
+ */
27
+ export declare function runCodexPromptsServer(options: CoderServerRunOptions): Promise<void>;
@@ -17,6 +17,7 @@ type RunPromptRoundOptions = {
17
17
  nextPrompt: PromptSelection;
18
18
  promptLabel: string;
19
19
  resolvedCoderContext?: string;
20
+ resolvedAgentSystemMessage?: string;
20
21
  isRichUiEnabled: boolean;
21
22
  progressDisplay?: CliProgressDisplay;
22
23
  uiHandle?: CoderRunUiHandle;
@@ -27,5 +28,5 @@ type RunPromptRoundOptions = {
27
28
  *
28
29
  * @private function of runCodexPrompts
29
30
  */
30
- export declare function runPromptRound({ options, runner, runnerMetadata, nextPrompt, promptLabel, resolvedCoderContext, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, }: RunPromptRoundOptions): Promise<void>;
31
+ export declare function runPromptRound({ options, runner, runnerMetadata, nextPrompt, promptLabel, resolvedCoderContext, resolvedAgentSystemMessage, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, }: RunPromptRoundOptions): Promise<void>;
31
32
  export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * HTML source for the coder server kanban UI.
3
+ *
4
+ * The canonical source lives in `apps/coder-server/index.html`.
5
+ * Keep both files in sync when updating the frontend.
6
+ *
7
+ * @private internal constant of `ptbk coder server`
8
+ */
9
+ export declare const CODER_SERVER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Ptbk Coder Server</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f4f5f7;\n color: #172b4d;\n min-height: 100vh;\n }\n\n header {\n background: #0052cc;\n color: white;\n padding: 12px 20px;\n display: flex;\n align-items: center;\n gap: 12px;\n position: sticky;\n top: 0;\n z-index: 10;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n }\n header h1 { font-size: 17px; font-weight: 700; flex-shrink: 0; }\n\n .status-badge {\n padding: 3px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n .status-RUNNING { background: #00875a; color: white; }\n .status-PAUSING { background: #ff8b00; color: white; }\n .status-PAUSED { background: #bf2600; color: white; }\n\n #pause-label {\n font-size: 12px;\n color: rgba(255,255,255,0.75);\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .btn {\n padding: 6px 14px;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n transition: opacity 0.15s;\n flex-shrink: 0;\n }\n .btn:hover { opacity: 0.85; }\n .btn-pause { background: #ffc400; color: #172b4d; }\n .btn-resume { background: #00875a; color: white; }\n\n .board {\n display: flex;\n gap: 14px;\n padding: 16px;\n overflow-x: auto;\n min-height: calc(100vh - 52px);\n align-items: flex-start;\n }\n\n .column {\n background: #ebecf0;\n border-radius: 8px;\n padding: 10px;\n min-width: 250px;\n width: 270px;\n flex-shrink: 0;\n }\n\n .column-header {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n color: #5e6c84;\n margin-bottom: 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .column-count {\n background: #dfe1e6;\n border-radius: 10px;\n padding: 1px 7px;\n font-size: 11px;\n color: #5e6c84;\n }\n\n .column-cards { min-height: 40px; }\n\n .card {\n background: white;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.08);\n cursor: pointer;\n transition: box-shadow 0.15s, transform 0.1s;\n border-left: 3px solid transparent;\n }\n .card:hover {\n box-shadow: 0 4px 10px rgba(0,0,0,0.14);\n transform: translateY(-1px);\n }\n .card-todo { border-left-color: #0052cc; }\n .card-not-ready { border-left-color: #8993a4; }\n .card-done { border-left-color: #00875a; }\n .card-failed { border-left-color: #bf2600; }\n\n .card-file {\n font-size: 10px;\n color: #8993a4;\n margin-bottom: 5px;\n font-weight: 500;\n }\n\n .card-summary {\n font-size: 13px;\n line-height: 1.5;\n color: #172b4d;\n word-break: break-word;\n white-space: pre-wrap;\n max-height: 78px;\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 4;\n -webkit-box-orient: vertical;\n }\n\n .card-priority {\n margin-top: 6px;\n color: #ff8b00;\n font-size: 11px;\n font-weight: 700;\n }\n\n .card-edit-hint {\n font-size: 10px;\n color: #c1c7d0;\n margin-top: 6px;\n display: none;\n }\n .card:hover .card-edit-hint { display: block; }\n\n .empty-column {\n color: #b3bac5;\n font-size: 12px;\n padding: 10px 4px;\n text-align: center;\n }\n\n .modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(9,30,66,0.54);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 60px 16px 16px;\n z-index: 100;\n animation: fadeIn 0.1s ease;\n }\n .modal-overlay.hidden { display: none; }\n\n @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n .modal {\n background: white;\n border-radius: 8px;\n width: 100%;\n max-width: 620px;\n padding: 20px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.25);\n animation: slideIn 0.15s ease;\n }\n @keyframes slideIn { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }\n\n .modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n margin-bottom: 14px;\n gap: 12px;\n }\n\n .modal-meta { flex: 1; min-width: 0; }\n .modal-file { font-size: 11px; color: #8993a4; margin-bottom: 4px; }\n .modal-section-label { font-size: 13px; font-weight: 600; color: #172b4d; }\n\n .modal-status {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n letter-spacing: 0.4px;\n flex-shrink: 0;\n }\n .status-todo { background: #deebff; color: #0052cc; }\n .status-not-ready { background: #f4f5f7; color: #5e6c84; }\n .status-done { background: #e3fcef; color: #006644; }\n .status-failed { background: #ffebe6; color: #bf2600; }\n\n .modal-close {\n background: none;\n border: none;\n color: #8993a4;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n line-height: 1;\n flex-shrink: 0;\n }\n .modal-close:hover { color: #172b4d; }\n\n textarea {\n width: 100%;\n min-height: 220px;\n font-family: 'SFMono-Regular', 'Consolas', 'Courier New', monospace;\n font-size: 13px;\n border: 2px solid #dfe1e6;\n border-radius: 4px;\n padding: 10px 12px;\n resize: vertical;\n line-height: 1.65;\n color: #172b4d;\n transition: border-color 0.15s;\n }\n textarea:focus {\n outline: none;\n border-color: #0052cc;\n box-shadow: 0 0 0 3px rgba(0,82,204,0.12);\n }\n\n .modal-hint {\n font-size: 11px;\n color: #8993a4;\n margin-top: 6px;\n }\n\n .modal-actions {\n display: flex;\n gap: 8px;\n margin-top: 14px;\n justify-content: flex-end;\n }\n\n .btn-save { background: #0052cc; color: white; }\n .btn-cancel { background: #f4f5f7; color: #172b4d; }\n .btn-cancel:hover { background: #ebecf0; opacity: 1; }\n\n .error-banner {\n background: #ffebe6;\n border-bottom: 2px solid #bf2600;\n color: #bf2600;\n padding: 8px 20px;\n font-size: 13px;\n font-weight: 500;\n }\n .error-banner.hidden { display: none; }\n </style>\n</head>\n<body>\n\n <div id=\"error-banner\" class=\"error-banner hidden\"></div>\n\n <header>\n <h1>&#128295; Ptbk Coder Server</h1>\n <span id=\"status-badge\" class=\"status-badge status-RUNNING\">RUNNING</span>\n <span id=\"pause-label\"></span>\n <button id=\"toggle-btn\" class=\"btn btn-pause\">&#9646;&#9646; Pause</button>\n </header>\n\n <div class=\"board\">\n <div class=\"column\">\n <div class=\"column-header\">\n To Do\n <span class=\"column-count\" id=\"count-todo\">0</span>\n </div>\n <div class=\"column-cards\" id=\"cards-todo\">\n <div class=\"empty-column\">Loading&hellip;</div>\n </div>\n </div>\n\n <div class=\"column\">\n <div class=\"column-header\">\n Not Ready\n <span class=\"column-count\" id=\"count-not-ready\">0</span>\n </div>\n <div class=\"column-cards\" id=\"cards-not-ready\"></div>\n </div>\n\n <div class=\"column\">\n <div class=\"column-header\">\n Done\n <span class=\"column-count\" id=\"count-done\">0</span>\n </div>\n <div class=\"column-cards\" id=\"cards-done\"></div>\n </div>\n\n <div class=\"column\">\n <div class=\"column-header\">\n Failed\n <span class=\"column-count\" id=\"count-failed\">0</span>\n </div>\n <div class=\"column-cards\" id=\"cards-failed\"></div>\n </div>\n </div>\n\n <div class=\"modal-overlay hidden\" id=\"modal-overlay\">\n <div class=\"modal\">\n <div class=\"modal-header\">\n <div class=\"modal-meta\">\n <div class=\"modal-file\" id=\"modal-file\"></div>\n <div class=\"modal-section-label\" id=\"modal-section-label\"></div>\n </div>\n <span class=\"modal-status\" id=\"modal-status-badge\"></span>\n <button class=\"modal-close\" onclick=\"closeModal()\" title=\"Close (Esc)\">&#x2715;</button>\n </div>\n\n <textarea id=\"modal-content\" placeholder=\"Prompt content&hellip;\"></textarea>\n <div class=\"modal-hint\">Tip: press Ctrl+Enter to save, Esc to cancel.</div>\n\n <div class=\"modal-actions\">\n <button class=\"btn btn-cancel\" onclick=\"closeModal()\">Cancel</button>\n <button class=\"btn btn-save\" onclick=\"saveModal()\">Save</button>\n </div>\n </div>\n </div>\n\n <script>\n 'use strict';\n\n let modalState = null;\n let lastPauseState = 'RUNNING';\n\n function showError(msg) {\n const banner = document.getElementById('error-banner');\n banner.textContent = '\\u26a0 ' + msg;\n banner.classList.remove('hidden');\n clearTimeout(banner._timer);\n banner._timer = setTimeout(() => banner.classList.add('hidden'), 6000);\n }\n\n function escapeHtml(str) {\n const d = document.createElement('div');\n d.textContent = String(str);\n return d.innerHTML;\n }\n\n async function fetchStatus() {\n try {\n const res = await fetch('/api/status');\n if (!res.ok) { showError('Status API error: ' + res.status); return; }\n const data = await res.json();\n\n lastPauseState = data.pauseState;\n\n const badge = document.getElementById('status-badge');\n badge.textContent = data.pauseState;\n badge.className = 'status-badge status-' + data.pauseState;\n\n const btn = document.getElementById('toggle-btn');\n const label = document.getElementById('pause-label');\n\n if (data.pauseState === 'RUNNING') {\n btn.textContent = '\\u23f8 Pause';\n btn.className = 'btn btn-pause';\n label.textContent = '';\n } else if (data.pauseState === 'PAUSING') {\n btn.textContent = '\\u23f5 Resume';\n btn.className = 'btn btn-resume';\n label.textContent = 'Pausing before: ' + (data.pauseTargetLabel || '\\u2026');\n } else {\n btn.textContent = '\\u23f5 Resume';\n btn.className = 'btn btn-resume';\n label.textContent = 'Paused before: ' + (data.pauseTargetLabel || '\\u2026');\n }\n } catch (e) {\n showError('Could not reach coder server: ' + e.message);\n }\n }\n\n async function fetchPrompts() {\n try {\n const res = await fetch('/api/prompts');\n if (!res.ok) { showError('Prompts API error: ' + res.status); return; }\n renderBoard(await res.json());\n } catch (e) {\n showError('Could not load prompts: ' + e.message);\n }\n }\n\n function renderBoard(promptFiles) {\n const columns = { 'todo': [], 'not-ready': [], 'done': [], 'failed': [] };\n\n for (const file of promptFiles) {\n for (const section of file.sections) {\n const col = columns[section.status];\n if (col) col.push({ file, section });\n }\n }\n\n for (const [status, cards] of Object.entries(columns)) {\n const container = document.getElementById('cards-' + status);\n const countEl = document.getElementById('count-' + status);\n if (!container) continue;\n\n countEl.textContent = cards.length;\n container.innerHTML = '';\n\n if (cards.length === 0) {\n container.innerHTML = '<div class=\"empty-column\">Empty</div>';\n continue;\n }\n\n for (const { file, section } of cards) {\n const card = document.createElement('div');\n card.className = 'card card-' + section.status;\n card.innerHTML =\n '<div class=\"card-file\">' + escapeHtml(file.fileName) + ' &bull; #' + (section.index + 1) + '</div>' +\n '<div class=\"card-summary\">' + escapeHtml(section.summary) + '</div>' +\n (section.priority > 0\n ? '<div class=\"card-priority\">' + '!'.repeat(section.priority) + ' priority ' + section.priority + '</div>'\n : '') +\n '<div class=\"card-edit-hint\">Click to edit</div>';\n card.onclick = () => openModal(file, section);\n container.appendChild(card);\n }\n }\n }\n\n function openModal(file, section) {\n modalState = { filePath: file.filePath, sectionIndex: section.index };\n\n document.getElementById('modal-file').textContent = file.fileName;\n document.getElementById('modal-section-label').textContent = 'Section ' + (section.index + 1);\n\n const badge = document.getElementById('modal-status-badge');\n badge.textContent = section.status.replace('-', '\\u2011');\n badge.className = 'modal-status status-' + section.status;\n\n document.getElementById('modal-content').value = section.content;\n document.getElementById('modal-overlay').classList.remove('hidden');\n setTimeout(() => document.getElementById('modal-content').focus(), 50);\n }\n\n function closeModal() {\n document.getElementById('modal-overlay').classList.add('hidden');\n modalState = null;\n }\n\n async function saveModal() {\n if (!modalState) return;\n\n const content = document.getElementById('modal-content').value;\n const saveBtn = document.querySelector('.btn-save');\n saveBtn.disabled = true;\n saveBtn.textContent = 'Saving\\u2026';\n\n try {\n const res = await fetch('/api/prompts/update', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n filePath: modalState.filePath,\n sectionIndex: modalState.sectionIndex,\n content,\n }),\n });\n if (!res.ok) throw new Error('HTTP ' + res.status);\n closeModal();\n fetchPrompts();\n } catch (e) {\n showError('Save failed: ' + e.message);\n } finally {\n saveBtn.disabled = false;\n saveBtn.textContent = 'Save';\n }\n }\n\n document.getElementById('toggle-btn').onclick = async () => {\n try {\n const endpoint = lastPauseState === 'RUNNING' ? '/api/pause' : '/api/resume';\n await fetch(endpoint, { method: 'POST' });\n await fetchStatus();\n } catch (e) {\n showError('Toggle failed: ' + e.message);\n }\n };\n\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') { closeModal(); return; }\n if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { saveModal(); return; }\n });\n\n document.getElementById('modal-overlay').addEventListener('click', (e) => {\n if (e.target === document.getElementById('modal-overlay')) closeModal();\n });\n\n fetchStatus();\n fetchPrompts();\n setInterval(fetchStatus, 2000);\n setInterval(fetchPrompts, 5000);\n </script>\n</body>\n</html>";
@@ -0,0 +1,23 @@
1
+ import type { number_port } from '../../../src/types/number_positive';
2
+ /**
3
+ * Handle returned by the running coder HTTP server.
4
+ *
5
+ * @private internal type of `ptbk coder server`
6
+ */
7
+ export type CoderHttpServerHandle = {
8
+ close: () => void;
9
+ };
10
+ /**
11
+ * Starts the lightweight HTTP server that serves the coder kanban UI and REST API.
12
+ *
13
+ * API endpoints:
14
+ * - `GET /` → HTML kanban page
15
+ * - `GET /api/prompts` → JSON list of all prompt files and sections
16
+ * - `GET /api/status` → JSON current pause state
17
+ * - `POST /api/pause` → request pause
18
+ * - `POST /api/resume` → request resume
19
+ * - `PUT /api/prompts/update` → update prompt section content
20
+ *
21
+ * @private internal utility of `ptbk coder server`
22
+ */
23
+ export declare function startCoderHttpServer(port: number_port): CoderHttpServerHandle;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Overwrites the body of one prompt section with new content, preserving the status line.
3
+ *
4
+ * The `newContent` string is the prompt text without the status marker.
5
+ * The status line (`[ ]`, `[x]`, `[!]`, `[-]`) is kept intact.
6
+ *
7
+ * @private internal utility of `ptbk coder server`
8
+ */
9
+ export declare function updatePromptSection(filePath: string, sectionIndex: number, newContent: string): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import { Command as Program } from 'commander';
2
+ import type { $side_effect } from '../../../utils/organization/$side_effect';
3
+ /**
4
+ * Initializes `coder find-unwritten` command for Promptbook CLI utilities
5
+ *
6
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
7
+ *
8
+ * @private internal function of `promptbookCli`
9
+ */
10
+ export declare function $initializeCoderFindUnwrittenCommand(program: Program): $side_effect;
@@ -0,0 +1,13 @@
1
+ import { Command as Program } from 'commander';
2
+ import type { $side_effect } from '../../../utils/organization/$side_effect';
3
+ /**
4
+ * Initializes `coder server` command for Promptbook CLI utilities.
5
+ *
6
+ * Runs the same prompt processing logic as `ptbk coder run` but keeps the process alive
7
+ * and serves a kanban web UI on the configured port.
8
+ *
9
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
10
+ *
11
+ * @private internal function of `promptbookCli`
12
+ */
13
+ export declare function $initializeCoderServerCommand(program: Program): $side_effect;
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.112.0-119`).
18
+ * It follows semantic versioning (e.g., `0.112.0-122`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.112.0-121",
3
+ "version": "0.112.0-123",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -0,0 +1,58 @@
1
+ import colors from 'colors';
2
+ import { Command as Program /* <- Note: [🔸] Using Program because Command is misleading name */ } from 'commander';
3
+ import { assertsError } from '../../../errors/assertsError';
4
+ import type { $side_effect } from '../../../utils/organization/$side_effect';
5
+ import { handleActionErrors } from '../common/handleActionErrors';
6
+
7
+ /**
8
+ * Initializes `coder find-unwritten` command for Promptbook CLI utilities
9
+ *
10
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
11
+ *
12
+ * @private internal function of `promptbookCli`
13
+ */
14
+ export function $initializeCoderFindUnwrittenCommand(program: Program): $side_effect {
15
+ const command = program.command('find-unwritten');
16
+ command.description('List all prompt sections that still need to be authored (contain @@@ placeholder)');
17
+ command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
18
+
19
+ command.action(
20
+ handleActionErrors(async (cliOptions) => {
21
+ const { priority = 0 } = cliOptions as {
22
+ readonly priority?: number;
23
+ };
24
+
25
+ // Note: Import the function dynamically to avoid loading heavy dependencies until needed
26
+ const { findUnwrittenPrompts } = await import(
27
+ '../../../../scripts/run-codex-prompts/main/findUnwrittenPrompts'
28
+ );
29
+
30
+ try {
31
+ await findUnwrittenPrompts({ priority });
32
+ } catch (error) {
33
+ assertsError(error);
34
+ console.error(colors.bgRed(`${error.name}`));
35
+ console.error(colors.red(error.stack || error.message));
36
+ return process.exit(1);
37
+ }
38
+
39
+ return process.exit(0);
40
+ }),
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Parses an integer option value
46
+ *
47
+ * @private internal utility of `coder find-unwritten` command
48
+ */
49
+ function parseIntOption(value: string): number {
50
+ const parsed = parseInt(value, 10);
51
+ if (isNaN(parsed)) {
52
+ throw new Error(`Invalid number: ${value}`);
53
+ }
54
+ return parsed;
55
+ }
56
+
57
+ // Note: [🟡] Code for CLI command [find-unwritten](src/cli/cli-commands/coder/find-unwritten.ts) should never be published outside of `@promptbook/cli`
58
+ // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -8,6 +8,7 @@ import { AGENT_CODING_FILE_PATH } from './agentCodingFile';
8
8
  import { AGENTS_FILE_PATH } from './agentsFile';
9
9
  import { getDefaultCoderProjectPromptTemplateDefinitions } from './boilerplateTemplates';
10
10
  import { formatDisplayPath } from './formatDisplayPath';
11
+ import { generatePromptBoilerplate } from './generate-boilerplates';
11
12
  import { initializeCoderProjectConfiguration } from './initializeCoderProjectConfiguration';
12
13
  import { printInitializationSummary } from './printInitializationSummary';
13
14
 
@@ -48,8 +49,10 @@ export function $initializeCoderInitCommand(program: Program): $side_effect {
48
49
 
49
50
  command.action(
50
51
  handleActionErrors(async () => {
51
- const summary = await initializeCoderProjectConfiguration(process.cwd());
52
+ const projectPath = process.cwd();
53
+ const summary = await initializeCoderProjectConfiguration(projectPath);
52
54
  printInitializationSummary(summary);
55
+ await generatePromptBoilerplate({ projectPath, filesCount: 5 });
53
56
  }),
54
57
  );
55
58
  }
@@ -43,6 +43,10 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
43
43
 
44
44
  command.option('--dry-run', 'Print unwritten prompts without executing', false);
45
45
  addPromptRunnerSelectionOptions(command);
46
+ command.option(
47
+ '--agent <agent-book-path>',
48
+ 'Path to a .book file whose compiled system message is prepended to each coding prompt',
49
+ );
46
50
  command.option(
47
51
  '--context <context-or-file>',
48
52
  'Append extra instructions either inline or from a file path relative to the current project',
@@ -80,17 +84,27 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
80
84
 
81
85
  command.action(
82
86
  handleActionErrors(async (cliOptions) => {
83
- const { dryRun, context, test, preserveLogs, priority, wait, autoMigrate, allowDestructiveAutoMigrate } =
84
- cliOptions as {
85
- readonly dryRun: boolean;
86
- readonly context?: string;
87
- readonly test?: string | string[];
88
- readonly preserveLogs: boolean;
89
- readonly priority: number;
90
- readonly wait: boolean | string;
91
- readonly autoMigrate: boolean;
92
- readonly allowDestructiveAutoMigrate: boolean;
93
- } & PromptRunnerCliOptions;
87
+ const {
88
+ dryRun,
89
+ agent,
90
+ context,
91
+ test,
92
+ preserveLogs,
93
+ priority,
94
+ wait,
95
+ autoMigrate,
96
+ allowDestructiveAutoMigrate,
97
+ } = cliOptions as {
98
+ readonly dryRun: boolean;
99
+ readonly agent?: string;
100
+ readonly context?: string;
101
+ readonly test?: string | string[];
102
+ readonly preserveLogs: boolean;
103
+ readonly priority: number;
104
+ readonly wait: boolean | string;
105
+ readonly autoMigrate: boolean;
106
+ readonly allowDestructiveAutoMigrate: boolean;
107
+ } & PromptRunnerCliOptions;
94
108
 
95
109
  const testCommand = normalizeCommandOptionValue(test);
96
110
  const runnerOptions = normalizePromptRunnerCliOptions(cliOptions as PromptRunnerCliOptions, {
@@ -119,6 +133,7 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
119
133
  ignoreGitChanges: runnerOptions.ignoreGitChanges,
120
134
  agentName: runnerOptions.agentName,
121
135
  model: runnerOptions.model,
136
+ agent,
122
137
  context,
123
138
  testCommand,
124
139
  preserveLogs,
@@ -0,0 +1,239 @@
1
+ import colors from 'colors';
2
+ import { Command as Program /* <- Note: [🔸] Using Program because Command is misleading name */ } from 'commander';
3
+ import { Option } from 'commander';
4
+ import { spaceTrim } from 'spacetrim';
5
+ import { NETWORK_LIMITS } from '../../../constants';
6
+ import { assertsError } from '../../../errors/assertsError';
7
+ import { NotAllowed } from '../../../errors/NotAllowed';
8
+ import type { number_port } from '../../../types/number_positive';
9
+ import type { $side_effect } from '../../../utils/organization/$side_effect';
10
+ import { handleActionErrors } from '../common/handleActionErrors';
11
+ import type { PromptRunnerCliOptions } from '../common/promptRunnerCliOptions';
12
+ import {
13
+ addPromptRunnerExecutionOptions,
14
+ addPromptRunnerSelectionOptions,
15
+ normalizePromptRunnerCliOptions,
16
+ PROMPT_RUNNER_DESCRIPTION,
17
+ } from '../common/promptRunnerCliOptions';
18
+ import { parseDuration } from '../../../../scripts/run-codex-prompts/common/parseDuration';
19
+
20
+ /**
21
+ * Default port used by `ptbk coder server`.
22
+ *
23
+ * @private internal constant of `ptbk coder server`
24
+ */
25
+ const DEFAULT_CODER_SERVER_PORT = '4441';
26
+
27
+ /**
28
+ * Initializes `coder server` command for Promptbook CLI utilities.
29
+ *
30
+ * Runs the same prompt processing logic as `ptbk coder run` but keeps the process alive
31
+ * and serves a kanban web UI on the configured port.
32
+ *
33
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
34
+ *
35
+ * @private internal function of `promptbookCli`
36
+ */
37
+ export function $initializeCoderServerCommand(program: Program): $side_effect {
38
+ const command = program.command('server');
39
+ command.description(
40
+ spaceTrim(`
41
+ Start a coder server that watches for prompts and serves a kanban web UI
42
+
43
+ ${PROMPT_RUNNER_DESCRIPTION}
44
+
45
+ Features:
46
+ - Runs the same prompt processing as \`ptbk coder run\`
47
+ - Does not exit when all prompts are done; polls for new prompt files instead
48
+ - Serves a kanban board at http://localhost:<port> for visual progress tracking
49
+ - Allows editing prompt files directly from the browser (Trello-style)
50
+ - Play / pause button in the browser stays in sync with the CLI pause state
51
+ - Press "p" in the terminal to pause / resume (same as \`ptbk coder run\`)
52
+ `),
53
+ );
54
+
55
+ command.addOption(
56
+ new Option('--port <port>', 'Port to start the coder server on')
57
+ .env('PTBK_CODER_SERVER_PORT')
58
+ .default(DEFAULT_CODER_SERVER_PORT),
59
+ );
60
+ command.option('--dry-run', 'Print unwritten prompts without executing', false);
61
+ addPromptRunnerSelectionOptions(command);
62
+ command.option(
63
+ '--agent <agent-book-path>',
64
+ 'Path to a .book file whose compiled system message is prepended to each coding prompt',
65
+ );
66
+ command.option(
67
+ '--context <context-or-file>',
68
+ 'Append extra instructions either inline or from a file path relative to the current project',
69
+ );
70
+ command.option(
71
+ '--test <test-command...>',
72
+ 'Run a verification command after each prompt; quote it when the command itself contains top-level flags',
73
+ );
74
+ command.option(
75
+ '--preserve-logs',
76
+ 'Keep generated temp prompt/log artifacts after successful rounds for debugging and analytics',
77
+ false,
78
+ );
79
+ addPromptRunnerExecutionOptions(command);
80
+ command.option('--priority <minimum-priority>', 'Filter prompts by minimum priority level', parseIntOption, 0);
81
+ command.option(
82
+ '--wait [duration]',
83
+ spaceTrim(`
84
+ Wait between prompt rounds.
85
+ Without a value (default): waits for user confirmation before each prompt (interactive mode).
86
+ With a duration like 1h, 30m, 5s: waits that long between prompts to avoid hitting rate limits of the harness.
87
+ `),
88
+ true,
89
+ );
90
+ command.option('--no-wait', 'Skip all waiting between prompts and run non-interactively');
91
+ command.option(
92
+ '--auto-migrate',
93
+ 'Run testing-server database migrations automatically after each successfully processed prompt',
94
+ );
95
+ command.option(
96
+ '--allow-destructive-auto-migrate',
97
+ 'Allow auto-migrate even when heuristic SQL safety check flags destructive pending migrations',
98
+ );
99
+
100
+ command.action(
101
+ handleActionErrors(async (cliOptions) => {
102
+ const {
103
+ port: rawPort,
104
+ dryRun,
105
+ agent,
106
+ context,
107
+ test,
108
+ preserveLogs,
109
+ priority,
110
+ wait,
111
+ autoMigrate,
112
+ allowDestructiveAutoMigrate,
113
+ } = cliOptions as {
114
+ readonly port: string;
115
+ readonly dryRun: boolean;
116
+ readonly agent?: string;
117
+ readonly context?: string;
118
+ readonly test?: string | string[];
119
+ readonly preserveLogs: boolean;
120
+ readonly priority: number;
121
+ readonly wait: boolean | string;
122
+ readonly autoMigrate: boolean;
123
+ readonly allowDestructiveAutoMigrate: boolean;
124
+ } & PromptRunnerCliOptions;
125
+
126
+ const port = parseCoderServerPort(rawPort);
127
+ const testCommand = normalizeCommandOptionValue(test);
128
+ const runnerOptions = normalizePromptRunnerCliOptions(cliOptions as PromptRunnerCliOptions, {
129
+ isAgentRequired: !dryRun,
130
+ });
131
+
132
+ // [1] Parse the --wait option (same logic as `coder run`)
133
+ let waitForUser = false;
134
+ let waitBetweenPrompts = 0;
135
+
136
+ if (wait === true) {
137
+ waitForUser = true;
138
+ } else if (typeof wait === 'string' && wait !== '') {
139
+ waitBetweenPrompts = parseDuration(wait);
140
+ }
141
+
142
+ const runOptions = {
143
+ port,
144
+ dryRun,
145
+ waitForUser,
146
+ waitBetweenPrompts,
147
+ noCommit: runnerOptions.noCommit,
148
+ ignoreGitChanges: runnerOptions.ignoreGitChanges,
149
+ agentName: runnerOptions.agentName,
150
+ model: runnerOptions.model,
151
+ agent,
152
+ context,
153
+ testCommand,
154
+ preserveLogs,
155
+ noUi: runnerOptions.noUi,
156
+ thinkingLevel: runnerOptions.thinkingLevel,
157
+ priority,
158
+ normalizeLineEndings: runnerOptions.normalizeLineEndings,
159
+ allowCredits: runnerOptions.allowCredits,
160
+ autoMigrate,
161
+ allowDestructiveAutoMigrate,
162
+ autoPush: runnerOptions.autoPush,
163
+ autoPull: runnerOptions.autoPull,
164
+ };
165
+
166
+ // Note: Import dynamically to avoid loading heavy dependencies until needed
167
+ const { runCodexPromptsServer } = await import(
168
+ '../../../../scripts/run-codex-prompts/main/runCodexPromptsServer'
169
+ );
170
+
171
+ try {
172
+ await runCodexPromptsServer(runOptions);
173
+ } catch (error) {
174
+ assertsError(error);
175
+ console.error(colors.bgRed(`${error.name}`));
176
+ console.error(colors.red(error.stack || error.message));
177
+ return process.exit(1);
178
+ }
179
+
180
+ return process.exit(0);
181
+ }),
182
+ );
183
+ }
184
+
185
+ /**
186
+ * Parses and validates the coder server port number.
187
+ *
188
+ * @private internal utility of `coder server` command
189
+ */
190
+ function parseCoderServerPort(rawPort: string): number_port {
191
+ const port = Number.parseInt(rawPort, 10);
192
+
193
+ if (!Number.isInteger(port) || port <= 0 || port > NETWORK_LIMITS.MAX_PORT) {
194
+ throw new NotAllowed(
195
+ spaceTrim(`
196
+ Invalid coder server port: \`${rawPort}\`.
197
+
198
+ Use \`--port\` or \`PTBK_CODER_SERVER_PORT\` with an integer between \`1\` and \`${NETWORK_LIMITS.MAX_PORT}\`.
199
+ `),
200
+ );
201
+ }
202
+
203
+ return port as number_port;
204
+ }
205
+
206
+ /**
207
+ * Parses an integer option value.
208
+ *
209
+ * @private internal utility of `coder server` command
210
+ */
211
+ function parseIntOption(value: string): number {
212
+ const parsed = parseInt(value, 10);
213
+ if (isNaN(parsed)) {
214
+ throw new Error(`Invalid number: ${value}`);
215
+ }
216
+ return parsed;
217
+ }
218
+
219
+ /**
220
+ * Joins one Commander option that may be parsed either as a single string or a variadic token array.
221
+ *
222
+ * @private internal utility of `coder server` command
223
+ */
224
+ function normalizeCommandOptionValue(value: string | string[] | undefined): string | undefined {
225
+ if (value === undefined) {
226
+ return undefined;
227
+ }
228
+
229
+ const parts = Array.isArray(value) ? value : [value];
230
+ const normalizedValue = parts
231
+ .map((part) => part.trim())
232
+ .filter(Boolean)
233
+ .join(' ')
234
+ .trim();
235
+ return normalizedValue === '' ? undefined : normalizedValue;
236
+ }
237
+
238
+ // Note: [🟡] Code for CLI command [server](src/cli/cli-commands/coder/server.ts) should never be published outside of `@promptbook/cli`
239
+ // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -6,9 +6,11 @@ import { spaceTrim } from 'spacetrim';
6
6
  import type { $side_effect } from '../../utils/organization/$side_effect';
7
7
  import { $initializeCoderFindFreshEmojiTagCommand } from './coder/find-fresh-emoji-tags';
8
8
  import { $initializeCoderFindRefactorCandidatesCommand } from './coder/find-refactor-candidates';
9
+ import { $initializeCoderFindUnwrittenCommand } from './coder/find-unwritten';
9
10
  import { $initializeCoderGenerateBoilerplatesCommand } from './coder/generate-boilerplates';
10
11
  import { $initializeCoderInitCommand } from './coder/init';
11
12
  import { $initializeCoderRunCommand } from './coder/run';
13
+ import { $initializeCoderServerCommand } from './coder/server';
12
14
  import { $initializeCoderVerifyCommand } from './coder/verify';
13
15
 
14
16
  /**
@@ -36,7 +38,9 @@ export function $initializeCoderCommand(program: Program): $side_effect {
36
38
  - init: Initialize coder configuration in current project
37
39
  - generate-boilerplates: Generate prompt boilerplate files
38
40
  - find-refactor-candidates: Find files that need refactoring
41
+ - find-unwritten: List prompt sections that still need to be authored
39
42
  - run: Run coding prompts with AI agents
43
+ - server: Start a long-running coder server with a kanban web UI
40
44
  - verify: Verify completed prompts
41
45
  - find-fresh-emoji-tags: Find unused emoji tags
42
46
  `),
@@ -46,7 +50,9 @@ export function $initializeCoderCommand(program: Program): $side_effect {
46
50
  $initializeCoderInitCommand(coderCommand);
47
51
  $initializeCoderGenerateBoilerplatesCommand(coderCommand);
48
52
  $initializeCoderFindRefactorCandidatesCommand(coderCommand);
53
+ $initializeCoderFindUnwrittenCommand(coderCommand);
49
54
  $initializeCoderRunCommand(coderCommand);
55
+ $initializeCoderServerCommand(coderCommand);
50
56
  $initializeCoderVerifyCommand(coderCommand);
51
57
  $initializeCoderFindFreshEmojiTagCommand(coderCommand);
52
58