@promptbook/cli 0.112.0-35 → 0.112.0-36

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/umd/index.umd.js CHANGED
@@ -60,7 +60,7 @@
60
60
  * @generated
61
61
  * @see https://github.com/webgptorg/promptbook
62
62
  */
63
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-35';
63
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-36';
64
64
  /**
65
65
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
66
66
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1700,6 +1700,220 @@
1700
1700
  // 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`
1701
1701
  // Note: [💞] Ignore a discrepancy between file name and entity name
1702
1702
 
1703
+ /**
1704
+ * This error indicates that promptbook not found in the collection
1705
+ *
1706
+ * @public exported from `@promptbook/core`
1707
+ */
1708
+ class NotFoundError extends Error {
1709
+ constructor(message) {
1710
+ super(message);
1711
+ this.name = 'NotFoundError';
1712
+ Object.setPrototypeOf(this, NotFoundError.prototype);
1713
+ }
1714
+ }
1715
+
1716
+ /**
1717
+ * Relative path to the root prompts directory used by Promptbook coder utilities.
1718
+ *
1719
+ * @private internal utility of `ptbk coder`
1720
+ */
1721
+ const PROMPTS_DIRECTORY_PATH = 'prompts';
1722
+ /**
1723
+ * Relative path to the archive directory used by `coder verify`.
1724
+ *
1725
+ * @private internal utility of `ptbk coder`
1726
+ */
1727
+ const PROMPTS_DONE_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'done');
1728
+ /**
1729
+ * Relative path to the project-owned boilerplate templates directory.
1730
+ *
1731
+ * @private internal utility of `ptbk coder`
1732
+ */
1733
+ const PROMPTS_TEMPLATES_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'templates');
1734
+ /**
1735
+ * Built-in boilerplate templates shared by `coder init` and `coder generate-boilerplates`.
1736
+ */
1737
+ const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
1738
+ {
1739
+ id: 'common',
1740
+ relativeFilePath: path.join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md'),
1741
+ slugPrefix: null,
1742
+ content: buildCoderPromptTemplateContent([
1743
+ '- @@@',
1744
+ '- Keep in mind the DRY _(don\'t repeat yourself)_ principle.',
1745
+ '- Do a proper analysis of the current functionality before you start implementing.',
1746
+ '- Add the changes into the [changelog](./changelog/_current-preversion.md)',
1747
+ ]),
1748
+ },
1749
+ {
1750
+ id: 'agents-server',
1751
+ relativeFilePath: path.join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'agents-server.md'),
1752
+ slugPrefix: 'agents-server',
1753
+ content: buildCoderPromptTemplateContent([
1754
+ '- @@@',
1755
+ '- Keep in mind the DRY _(don\'t repeat yourself)_ principle.',
1756
+ '- Do a proper analysis of the current functionality before you start implementing.',
1757
+ '- You are working with the [Agents Server](apps/agents-server)',
1758
+ '- If you need to do the database migration, do it',
1759
+ '- Add the changes into the [changelog](changelog/_current-preversion.md)',
1760
+ ]),
1761
+ },
1762
+ ];
1763
+ /**
1764
+ * Lists the built-in coder boilerplate templates.
1765
+ *
1766
+ * @private internal utility of `ptbk coder`
1767
+ */
1768
+ function getDefaultCoderPromptTemplateDefinitions() {
1769
+ return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS;
1770
+ }
1771
+ /**
1772
+ * Resolves one built-in coder boilerplate template definition by its stable identifier.
1773
+ *
1774
+ * @private internal utility of `ptbk coder`
1775
+ */
1776
+ function getDefaultCoderPromptTemplateDefinition(template) {
1777
+ const definition = getDefaultCoderPromptTemplateDefinitionOrUndefined(template);
1778
+ if (!definition) {
1779
+ throw new NotFoundError(`Built-in coder prompt template \`${template}\` was not found.`);
1780
+ }
1781
+ return definition;
1782
+ }
1783
+ /**
1784
+ * Ensures the default project-owned coder template files exist without overwriting user customizations.
1785
+ *
1786
+ * @private internal utility of `ptbk coder`
1787
+ */
1788
+ async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
1789
+ const ensuredTemplateFiles = [];
1790
+ for (const definition of DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS) {
1791
+ const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
1792
+ if (await isExistingFile$1(absoluteTemplatePath)) {
1793
+ ensuredTemplateFiles.push({
1794
+ id: definition.id,
1795
+ relativeFilePath: definition.relativeFilePath,
1796
+ status: 'unchanged',
1797
+ });
1798
+ continue;
1799
+ }
1800
+ await promises.writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
1801
+ ensuredTemplateFiles.push({
1802
+ id: definition.id,
1803
+ relativeFilePath: definition.relativeFilePath,
1804
+ status: 'created',
1805
+ });
1806
+ }
1807
+ return ensuredTemplateFiles;
1808
+ }
1809
+ /**
1810
+ * Resolves the template requested by `coder generate-boilerplates`.
1811
+ *
1812
+ * Supports three modes:
1813
+ * - omitted option => built-in `common`
1814
+ * - built-in alias => one of the shared default templates
1815
+ * - relative path => markdown template file resolved from the project root
1816
+ *
1817
+ * @private internal utility of `ptbk coder`
1818
+ */
1819
+ async function resolveCoderPromptTemplate({ projectPath, templateOption, }) {
1820
+ const normalizedTemplateOption = normalizeCoderPromptTemplateOption(templateOption);
1821
+ if (!normalizedTemplateOption) {
1822
+ return createResolvedBuiltInCoderPromptTemplate('common');
1823
+ }
1824
+ const builtInTemplateDefinition = getDefaultCoderPromptTemplateDefinitionOrUndefined(normalizedTemplateOption);
1825
+ if (builtInTemplateDefinition) {
1826
+ return createResolvedBuiltInCoderPromptTemplate(builtInTemplateDefinition.id);
1827
+ }
1828
+ const absoluteTemplatePath = path.join(projectPath, normalizedTemplateOption);
1829
+ try {
1830
+ const content = (await promises.readFile(absoluteTemplatePath, 'utf-8')).trim();
1831
+ return {
1832
+ identifier: normalizedTemplateOption,
1833
+ relativeFilePath: normalizedTemplateOption,
1834
+ content,
1835
+ slugPrefix: deriveCoderPromptSlugPrefix(normalizedTemplateOption),
1836
+ };
1837
+ }
1838
+ catch (error) {
1839
+ if (isNodeJsErrorWithCode(error, 'ENOENT')) {
1840
+ throw new NotFoundError(_spaceTrim.spaceTrim(`
1841
+ Prompt boilerplate template was not found at \`${normalizedTemplateOption}\`.
1842
+
1843
+ - The \`--template\` option resolves paths relative to the current project root: \`${projectPath}\`
1844
+ - Run \`ptbk coder init\` to create the default project templates in \`${PROMPTS_TEMPLATES_DIRECTORY_PATH}\`
1845
+ - Or omit \`--template\` / use the built-in aliases \`common\` or \`agents-server\`
1846
+ `));
1847
+ }
1848
+ throw error;
1849
+ }
1850
+ }
1851
+ /**
1852
+ * Normalizes one raw `--template` option to either a trimmed string or `undefined`.
1853
+ */
1854
+ function normalizeCoderPromptTemplateOption(templateOption) {
1855
+ const normalizedTemplateOption = templateOption === null || templateOption === void 0 ? void 0 : templateOption.trim();
1856
+ if (!normalizedTemplateOption) {
1857
+ return undefined;
1858
+ }
1859
+ return normalizedTemplateOption;
1860
+ }
1861
+ /**
1862
+ * Resolves one built-in template definition without throwing.
1863
+ */
1864
+ function getDefaultCoderPromptTemplateDefinitionOrUndefined(template) {
1865
+ return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS.find((definition) => definition.id === template);
1866
+ }
1867
+ /**
1868
+ * Builds stable markdown content for one coder prompt template without indentation drift.
1869
+ */
1870
+ function buildCoderPromptTemplateContent(lines) {
1871
+ return lines.join('\n');
1872
+ }
1873
+ /**
1874
+ * Creates a fully resolved template payload from one built-in definition.
1875
+ */
1876
+ function createResolvedBuiltInCoderPromptTemplate(template) {
1877
+ const definition = getDefaultCoderPromptTemplateDefinition(template);
1878
+ return {
1879
+ identifier: definition.id,
1880
+ relativeFilePath: definition.relativeFilePath,
1881
+ content: definition.content,
1882
+ slugPrefix: definition.slugPrefix,
1883
+ };
1884
+ }
1885
+ /**
1886
+ * Derives the filename slug prefix from a project-relative template path.
1887
+ */
1888
+ function deriveCoderPromptSlugPrefix(relativeTemplatePath) {
1889
+ const templateBasename = path.basename(relativeTemplatePath)
1890
+ .replace(/\.[^.]+$/u, '')
1891
+ .replace(/\.template$/u, '');
1892
+ if (templateBasename === 'common') {
1893
+ return null;
1894
+ }
1895
+ return templateBasename;
1896
+ }
1897
+ /**
1898
+ * Checks whether the provided error object exposes a specific Node.js `code`.
1899
+ */
1900
+ function isNodeJsErrorWithCode(error, code) {
1901
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
1902
+ }
1903
+ /**
1904
+ * Checks whether a path exists and is a file.
1905
+ */
1906
+ async function isExistingFile$1(path) {
1907
+ try {
1908
+ return (await promises.stat(path)).isFile();
1909
+ }
1910
+ catch (_a) {
1911
+ return false;
1912
+ }
1913
+ }
1914
+ // Note: [🟡] Code for coder boilerplate templates [boilerplateTemplates](src/cli/cli-commands/coder/boilerplateTemplates.ts) should never be published outside of `@promptbook/cli`
1915
+ // Note: [💞] Ignore a discrepancy between file name and exported helper names
1916
+
1703
1917
  /**
1704
1918
  * Initializes `coder generate-boilerplates` command for Promptbook CLI utilities
1705
1919
  *
@@ -1713,12 +1927,21 @@
1713
1927
  Generate prompt boilerplate files with unique emoji tags
1714
1928
  `));
1715
1929
  command.option('--count <count>', `Number of prompt boilerplate files to generate`, '5');
1716
- command.option('--template <template>', `Prompt template to use: common | agents-server`, 'common');
1930
+ command.option('--template <template>', _spaceTrim.spaceTrim(`
1931
+ Prompt template to use.
1932
+
1933
+ Accepts either a built-in alias (${getDefaultCoderPromptTemplateDefinitions()
1934
+ .map(({ id }) => id)
1935
+ .join(', ')}) or a markdown file path relative to the current project root.
1936
+ `));
1717
1937
  command.action(handleActionErrors(async (cliOptions) => {
1718
1938
  const { count: countOption, template: templateOption } = cliOptions;
1719
1939
  const filesCount = parseFilesCount(countOption);
1720
- const template = parsePromptTemplate(templateOption);
1721
- await generatePromptBoilerplate({ filesCount, template });
1940
+ await generatePromptBoilerplate({
1941
+ projectPath: process.cwd(),
1942
+ filesCount,
1943
+ templateOption,
1944
+ });
1722
1945
  return process.exit(0);
1723
1946
  }));
1724
1947
  }
@@ -1727,14 +1950,15 @@
1727
1950
  *
1728
1951
  * @private internal function of `generatePromptBoilerplate` command
1729
1952
  */
1730
- async function generatePromptBoilerplate({ filesCount, template, }) {
1953
+ async function generatePromptBoilerplate({ projectPath, filesCount, templateOption, }) {
1731
1954
  // Note: Import these dynamically to avoid circular dependencies and keep CLI fast
1732
1955
  const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
1733
1956
  const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
1734
1957
  console.info(`🚀 Generate prompt boilerplate files`);
1735
- const promptTemplateContent = loadPromptTemplate(template);
1958
+ fs.mkdirSync(path.join(projectPath, PROMPTS_DIRECTORY_PATH), { recursive: true });
1959
+ const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
1736
1960
  const promptNumbering = await getPromptNumbering({
1737
- promptsDir: path.join(process.cwd(), 'prompts'),
1961
+ promptsDir: path.join(projectPath, PROMPTS_DIRECTORY_PATH),
1738
1962
  step: 10,
1739
1963
  ignoreGlobs: ['**/node_modules/**'],
1740
1964
  });
@@ -1743,7 +1967,7 @@
1743
1967
  console.info(colors__default["default"].blue(`Highest existing number for ${promptNumbering.datePrefix} found: ${highestNumberFormatted}`));
1744
1968
  const { availableCount, selectedEmojis } = await getFreshPromptEmojiTags({
1745
1969
  count: filesCount,
1746
- rootDir: process.cwd(),
1970
+ rootDir: projectPath,
1747
1971
  });
1748
1972
  console.info(colors__default["default"].green(`Found ${availableCount} available fresh emojis`));
1749
1973
  console.info(colors__default["default"].green(`Selected emojis: ${selectedEmojis.map((emoji) => formatPromptEmojiTag(emoji)).join(' ')}`));
@@ -1755,8 +1979,9 @@
1755
1979
  const number = promptNumbering.startNumber + i * promptNumbering.step;
1756
1980
  const title = titles[i % titles.length];
1757
1981
  const emoji = selectedEmojis[i];
1758
- const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(template, title));
1759
- const filepath = path.join('prompts', filename);
1982
+ const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(promptTemplate.slugPrefix, title));
1983
+ const filepath = path.join(PROMPTS_DIRECTORY_PATH, filename);
1984
+ const absoluteFilepath = path.join(projectPath, filepath);
1760
1985
  const emojiTag = formatPromptEmojiTag(emoji);
1761
1986
  const one = _spaceTrim.spaceTrim((block) => `
1762
1987
 
@@ -1764,7 +1989,7 @@
1764
1989
 
1765
1990
  ${emojiTag} ${title}
1766
1991
 
1767
- ${block(promptTemplateContent)}
1992
+ ${block(promptTemplate.content)}
1768
1993
  `);
1769
1994
  const content = _spaceTrim.spaceTrim((block) => `
1770
1995
 
@@ -1785,6 +2010,7 @@
1785
2010
  `);
1786
2011
  filesToCreate.push({
1787
2012
  filepath,
2013
+ absoluteFilepath,
1788
2014
  filename,
1789
2015
  content,
1790
2016
  emoji,
@@ -1794,7 +2020,7 @@
1794
2020
  // Create the files
1795
2021
  console.info(colors__default["default"].yellow(`Creating ${filesToCreate.length} files:`));
1796
2022
  for (const file of filesToCreate) {
1797
- fs.writeFileSync(file.filepath, file.content, 'utf-8');
2023
+ fs.writeFileSync(file.absoluteFilepath, file.content, 'utf-8');
1798
2024
  console.info(colors__default["default"].green(`✓ Created: ${file.filename} with ${formatPromptEmojiTag(file.emoji)}`));
1799
2025
  }
1800
2026
  console.info(colors__default["default"].bgGreen(` Successfully created ${filesToCreate.length} prompt boilerplate files! `));
@@ -1812,52 +2038,34 @@
1812
2038
  }
1813
2039
  return Math.floor(filesCount);
1814
2040
  }
1815
- /**
1816
- * Parses and validates the prompt template name.
1817
- *
1818
- * @private internal utility of `generatePromptBoilerplate` command
1819
- */
1820
- function parsePromptTemplate(templateOption) {
1821
- if (templateOption === 'common' ||
1822
- templateOption === 'agents-server'
1823
- // <- TODO: Unhardcode and allow this dynamically by the template files.
1824
- ) {
1825
- return templateOption;
1826
- }
1827
- console.info(colors__default["default"].yellow(`Invalid --template '${templateOption}'. Falling back to default 'common'.`));
1828
- return 'common';
1829
- }
1830
- /**
1831
- * Loads prompt template markdown content from the local templates folder.
1832
- *
1833
- * @private internal utility of `generatePromptBoilerplate` command
1834
- */
1835
- function loadPromptTemplate(template) {
1836
- const templateFilePath = path.join(__dirname, '../../../../scripts/generate-prompt-boilerplate/templates', `${template}.template.md`);
1837
- return fs.readFileSync(templateFilePath, 'utf-8').trim();
1838
- }
1839
2041
  /**
1840
2042
  * Builds filename slug from template and placeholder title.
1841
2043
  *
1842
2044
  * @private internal utility of `generatePromptBoilerplate` command
1843
2045
  */
1844
- function buildPromptSlug$1(template, title) {
1845
- if (template === 'common') {
2046
+ function buildPromptSlug$1(templateSlugPrefix, title) {
2047
+ if (!templateSlugPrefix) {
1846
2048
  return title;
1847
2049
  }
1848
- return `${template}-${title}`;
2050
+ return `${templateSlugPrefix}-${title}`;
1849
2051
  }
1850
2052
  // Note: [🟡] Code for CLI command [generate-boilerplates](src/cli/cli-commands/coder/generate-boilerplates.ts) should never be published outside of `@promptbook/cli`
1851
2053
  // Note: [💞] Ignore a discrepancy between file name and entity name
1852
2054
 
1853
2055
  /**
1854
- * Relative path to the root prompts directory used by Promptbook coder utilities.
1855
- */
1856
- const PROMPTS_DIRECTORY_PATH = 'prompts';
1857
- /**
1858
- * Relative path to the archive directory used by `coder verify`.
2056
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
2057
+ *
2058
+ * @public exported from `@promptbook/core`
1859
2059
  */
1860
- const PROMPTS_DONE_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'done');
2060
+ class ParseError extends Error {
2061
+ constructor(message) {
2062
+ super(message);
2063
+ this.name = 'ParseError';
2064
+ Object.setPrototypeOf(this, ParseError.prototype);
2065
+ }
2066
+ }
2067
+ // TODO: Maybe split `ParseError` and `ApplyError`
2068
+
1861
2069
  /**
1862
2070
  * Required environment variables for coding-agent git identity.
1863
2071
  */
@@ -1875,6 +2083,58 @@
1875
2083
  value: '13406525ED912F938FEA85AB4046C687298B2382',
1876
2084
  },
1877
2085
  ];
2086
+ /**
2087
+ * Default npm scripts initialized by `ptbk coder init`.
2088
+ */
2089
+ const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
2090
+ 'coder:generate-boilerplates': 'npx ptbk coder generate-boilerplates',
2091
+ 'coder:run': 'npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --no-wait',
2092
+ 'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
2093
+ 'coder:verify': 'npx ptbk coder verify',
2094
+ };
2095
+ /**
2096
+ * Relative path to `.gitignore` in the initialized project.
2097
+ */
2098
+ const GITIGNORE_FILE_PATH = '.gitignore';
2099
+ /**
2100
+ * Relative path to `package.json` in the initialized project.
2101
+ */
2102
+ const PACKAGE_JSON_FILE_PATH = 'package.json';
2103
+ /**
2104
+ * Relative path to the VS Code settings file initialized by `ptbk coder init`.
2105
+ */
2106
+ const VSCODE_SETTINGS_FILE_PATH = '.vscode/settings.json';
2107
+ /**
2108
+ * Relative path to the VS Code directory initialized by `ptbk coder init`.
2109
+ */
2110
+ const VSCODE_DIRECTORY_PATH = '.vscode';
2111
+ /**
2112
+ * VS Code setting key used to route pasted markdown images into prompt-specific screenshots.
2113
+ */
2114
+ const MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY = 'markdown.copyFiles.destination';
2115
+ /**
2116
+ * Markdown glob used for coder prompt files inside VS Code settings.
2117
+ */
2118
+ const PROMPTS_MARKDOWN_FILE_GLOB = 'prompts/*md';
2119
+ /**
2120
+ * Screenshot destination used for pasted prompt images inside VS Code settings.
2121
+ */
2122
+ const PROMPTS_SCREENSHOT_DESTINATION = './prompts/screenshots/${documentBaseName}.png';
2123
+ /**
2124
+ * Default indentation used when creating new JSON configuration files.
2125
+ */
2126
+ const DEFAULT_JSON_FILE_INDENTATION = ' ';
2127
+ /**
2128
+ * Default newline used when creating new JSON configuration files.
2129
+ */
2130
+ const DEFAULT_JSON_FILE_NEWLINE = '\n';
2131
+ /**
2132
+ * `.gitignore` block required by standalone Promptbook coder projects.
2133
+ */
2134
+ const CODER_GITIGNORE_BLOCK = _spaceTrim.spaceTrim(`
2135
+ # Promptbook Coder
2136
+ /.tmp
2137
+ `);
1878
2138
  /**
1879
2139
  * Initializes `coder init` command for Promptbook CLI utilities.
1880
2140
  *
@@ -1888,9 +2148,14 @@
1888
2148
  command.description(_spaceTrim.spaceTrim(`
1889
2149
  Initialize Promptbook coder configuration for current project
1890
2150
 
1891
- Creates:
2151
+ Creates or updates:
1892
2152
  - prompts/
1893
2153
  - prompts/done/
2154
+ - prompts/templates/common.md
2155
+ - prompts/templates/agents-server.md
2156
+ - .gitignore
2157
+ - package.json
2158
+ - .vscode/settings.json
1894
2159
 
1895
2160
  Ensures required coding-agent environment variables in .env:
1896
2161
  - CODING_AGENT_GIT_NAME
@@ -1902,17 +2167,37 @@
1902
2167
  printInitializationSummary(summary);
1903
2168
  }));
1904
2169
  }
2170
+ /**
2171
+ * Lists the default npm scripts initialized by `ptbk coder init`.
2172
+ *
2173
+ * @private internal utility of `coder init` command
2174
+ */
2175
+ function getDefaultCoderPackageJsonScripts() {
2176
+ return DEFAULT_CODER_PACKAGE_JSON_SCRIPTS;
2177
+ }
1905
2178
  /**
1906
2179
  * Creates or updates all coder configuration artifacts required in the current project.
2180
+ *
2181
+ * @private internal utility of `coder init` command
1907
2182
  */
1908
2183
  async function initializeCoderProjectConfiguration(projectPath) {
1909
2184
  const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
1910
2185
  const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
2186
+ const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
2187
+ const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
1911
2188
  const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
2189
+ const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
2190
+ const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
2191
+ const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
1912
2192
  return {
1913
2193
  promptsDirectoryStatus,
1914
2194
  promptsDoneDirectoryStatus,
2195
+ promptsTemplatesDirectoryStatus,
2196
+ promptTemplateFileStatuses,
1915
2197
  envFileStatus,
2198
+ gitignoreFileStatus,
2199
+ packageJsonFileStatus,
2200
+ vscodeSettingsFileStatus,
1916
2201
  initializedEnvVariableNames,
1917
2202
  };
1918
2203
  }
@@ -1958,6 +2243,70 @@
1958
2243
  initializedEnvVariableNames: missingEnvVariables.map(({ name }) => name),
1959
2244
  };
1960
2245
  }
2246
+ /**
2247
+ * Ensures `.gitignore` contains the standalone Promptbook coder cache entry.
2248
+ */
2249
+ async function ensureCoderGitignoreFile(projectPath) {
2250
+ const gitignorePath = path.join(projectPath, GITIGNORE_FILE_PATH);
2251
+ const currentGitignoreContent = await readTextFileIfExists(gitignorePath);
2252
+ if (currentGitignoreContent !== undefined && hasTmpGitignoreRule(currentGitignoreContent)) {
2253
+ return 'unchanged';
2254
+ }
2255
+ const nextGitignoreContent = appendBlock(currentGitignoreContent || '', CODER_GITIGNORE_BLOCK);
2256
+ await promises.writeFile(gitignorePath, nextGitignoreContent, 'utf-8');
2257
+ return currentGitignoreContent === undefined ? 'created' : 'updated';
2258
+ }
2259
+ /**
2260
+ * Ensures `package.json` contains the standalone Promptbook coder helper scripts.
2261
+ */
2262
+ async function ensureCoderPackageJsonFile(projectPath) {
2263
+ const packageJsonPath = path.join(projectPath, PACKAGE_JSON_FILE_PATH);
2264
+ const packageJsonContent = await readTextFileIfExists(packageJsonPath);
2265
+ const formatting = detectJsonFileFormatting(packageJsonContent);
2266
+ const packageJson = packageJsonContent === undefined ? {} : await parseJsonObjectFile(PACKAGE_JSON_FILE_PATH, packageJsonContent);
2267
+ const scripts = getStringRecordOrDefault(packageJson['scripts'], PACKAGE_JSON_FILE_PATH, 'scripts');
2268
+ let hasChanges = packageJsonContent === undefined;
2269
+ const nextScripts = { ...scripts };
2270
+ for (const [scriptName, scriptCommand] of Object.entries(getDefaultCoderPackageJsonScripts())) {
2271
+ if (nextScripts[scriptName] !== scriptCommand) {
2272
+ nextScripts[scriptName] = scriptCommand;
2273
+ hasChanges = true;
2274
+ }
2275
+ }
2276
+ if (!hasChanges) {
2277
+ return 'unchanged';
2278
+ }
2279
+ const nextPackageJson = { ...packageJson };
2280
+ nextPackageJson['scripts'] = nextScripts;
2281
+ await promises.writeFile(packageJsonPath, serializeJsonObject(nextPackageJson, formatting), 'utf-8');
2282
+ return packageJsonContent === undefined ? 'created' : 'updated';
2283
+ }
2284
+ /**
2285
+ * Ensures VS Code routes pasted prompt images into `prompts/screenshots`.
2286
+ */
2287
+ async function ensureCoderVscodeSettingsFile(projectPath) {
2288
+ await promises.mkdir(path.join(projectPath, VSCODE_DIRECTORY_PATH), { recursive: true });
2289
+ const vscodeSettingsPath = path.join(projectPath, VSCODE_SETTINGS_FILE_PATH);
2290
+ const vscodeSettingsContent = await readTextFileIfExists(vscodeSettingsPath);
2291
+ const formatting = detectJsonFileFormatting(vscodeSettingsContent);
2292
+ const vscodeSettings = vscodeSettingsContent === undefined
2293
+ ? {}
2294
+ : await parseJsonObjectFile(VSCODE_SETTINGS_FILE_PATH, vscodeSettingsContent);
2295
+ const markdownCopyFilesDestinations = getStringRecordOrDefault(vscodeSettings[MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY], VSCODE_SETTINGS_FILE_PATH, MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY);
2296
+ let hasChanges = vscodeSettingsContent === undefined;
2297
+ const nextMarkdownCopyFilesDestinations = { ...markdownCopyFilesDestinations };
2298
+ if (nextMarkdownCopyFilesDestinations[PROMPTS_MARKDOWN_FILE_GLOB] !== PROMPTS_SCREENSHOT_DESTINATION) {
2299
+ nextMarkdownCopyFilesDestinations[PROMPTS_MARKDOWN_FILE_GLOB] = PROMPTS_SCREENSHOT_DESTINATION;
2300
+ hasChanges = true;
2301
+ }
2302
+ if (!hasChanges) {
2303
+ return 'unchanged';
2304
+ }
2305
+ const nextVscodeSettings = { ...vscodeSettings };
2306
+ nextVscodeSettings[MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY] = nextMarkdownCopyFilesDestinations;
2307
+ await promises.writeFile(vscodeSettingsPath, serializeJsonObject(nextVscodeSettings, formatting), 'utf-8');
2308
+ return vscodeSettingsContent === undefined ? 'created' : 'updated';
2309
+ }
1961
2310
  /**
1962
2311
  * Parses variable names currently defined in `.env` style content.
1963
2312
  */
@@ -2000,14 +2349,21 @@
2000
2349
  */
2001
2350
  function printInitializationSummary(summary) {
2002
2351
  console.info(colors__default["default"].green('Promptbook coder configuration initialized.'));
2003
- console.info(colors__default["default"].gray(`- prompts/: ${formatInitializationStatus(summary.promptsDirectoryStatus)}`));
2004
- console.info(colors__default["default"].gray(`- prompts/done/: ${formatInitializationStatus(summary.promptsDoneDirectoryStatus)}`));
2005
- console.info(colors__default["default"].gray(`- .env: ${formatInitializationStatus(summary.envFileStatus)}`));
2352
+ printInitializationStatusLine('prompts/', summary.promptsDirectoryStatus);
2353
+ printInitializationStatusLine('prompts/done/', summary.promptsDoneDirectoryStatus);
2354
+ printInitializationStatusLine('prompts/templates/', summary.promptsTemplatesDirectoryStatus);
2355
+ for (const templateFileStatus of summary.promptTemplateFileStatuses) {
2356
+ printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
2357
+ }
2358
+ printInitializationStatusLine('.env', summary.envFileStatus);
2359
+ printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
2360
+ printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
2361
+ printInitializationStatusLine('.vscode/settings.json', summary.vscodeSettingsFileStatus);
2006
2362
  if (summary.initializedEnvVariableNames.length > 0) {
2007
- console.info(colors__default["default"].cyan(`- Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`));
2363
+ printInitializationNote(`Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`, colors__default["default"].cyan);
2008
2364
  }
2009
2365
  else {
2010
- console.info(colors__default["default"].gray('- Required coder env variables are already present.'));
2366
+ printInitializationNote('Required coder env variables are already present.', colors__default["default"].gray);
2011
2367
  }
2012
2368
  }
2013
2369
  /**
@@ -2022,6 +2378,117 @@
2022
2378
  }
2023
2379
  return 'unchanged';
2024
2380
  }
2381
+ /**
2382
+ * Prints one checked initialization-status line.
2383
+ */
2384
+ function printInitializationStatusLine(relativePath, status) {
2385
+ console.info(colors__default["default"].gray(`✔ ${relativePath}: ${formatInitializationStatus(status)}`));
2386
+ }
2387
+ /**
2388
+ * Prints one checked initialization note.
2389
+ */
2390
+ function printInitializationNote(message, colorize) {
2391
+ console.info(colorize(`✔ ${message}`));
2392
+ }
2393
+ /**
2394
+ * Normalizes one project-relative path for human-readable CLI output.
2395
+ */
2396
+ function formatDisplayPath(relativePath) {
2397
+ return relativePath.replace(/\\/gu, '/');
2398
+ }
2399
+ /**
2400
+ * Detects whether `.gitignore` already covers the standalone coder temp directory.
2401
+ */
2402
+ function hasTmpGitignoreRule(gitignoreContent) {
2403
+ return /(^|[\r\n])\/?\.tmp(?:[\r\n]|$)/u.test(gitignoreContent);
2404
+ }
2405
+ /**
2406
+ * Reads one text file when it exists, otherwise returns `undefined`.
2407
+ */
2408
+ async function readTextFileIfExists(path) {
2409
+ if (!(await isExistingFile(path))) {
2410
+ return undefined;
2411
+ }
2412
+ return promises.readFile(path, 'utf-8');
2413
+ }
2414
+ /**
2415
+ * Parses one JSON object file while accepting VS Code style comments and trailing commas.
2416
+ */
2417
+ async function parseJsonObjectFile(relativeFilePath, fileContent) {
2418
+ if (fileContent.trim() === '') {
2419
+ return {};
2420
+ }
2421
+ const typescript = await import('typescript');
2422
+ const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
2423
+ if (parsedFile.error) {
2424
+ throw new ParseError(_spaceTrim.spaceTrim(`
2425
+ Cannot parse \`${relativeFilePath}\` as JSON.
2426
+
2427
+ ${typescript.flattenDiagnosticMessageText(parsedFile.error.messageText, '\n')}
2428
+ `));
2429
+ }
2430
+ if (!isPlainObject(parsedFile.config)) {
2431
+ throw new ParseError(_spaceTrim.spaceTrim(`
2432
+ File \`${relativeFilePath}\` must contain one top-level JSON object.
2433
+ `));
2434
+ }
2435
+ return parsedFile.config;
2436
+ }
2437
+ /**
2438
+ * Reads one JSON object field as a string-to-string record.
2439
+ */
2440
+ function getStringRecordOrDefault(value, relativeFilePath, fieldPath) {
2441
+ if (value === undefined) {
2442
+ return {};
2443
+ }
2444
+ if (!isPlainObject(value)) {
2445
+ throw new ParseError(_spaceTrim.spaceTrim(`
2446
+ File \`${relativeFilePath}\` contains invalid \`${fieldPath}\`.
2447
+
2448
+ Expected \`${fieldPath}\` to be an object with string values.
2449
+ `));
2450
+ }
2451
+ const stringRecord = {};
2452
+ for (const [key, itemValue] of Object.entries(value)) {
2453
+ if (typeof itemValue !== 'string') {
2454
+ throw new ParseError(_spaceTrim.spaceTrim(`
2455
+ File \`${relativeFilePath}\` contains invalid \`${fieldPath}.${key}\`.
2456
+
2457
+ Expected \`${fieldPath}\` to be an object with string values.
2458
+ `));
2459
+ }
2460
+ stringRecord[key] = itemValue;
2461
+ }
2462
+ return stringRecord;
2463
+ }
2464
+ /**
2465
+ * Serializes one JSON object using detected or default formatting.
2466
+ */
2467
+ function serializeJsonObject(value, formatting) {
2468
+ return `${JSON.stringify(value, null, formatting.indentation)}${formatting.newline}`;
2469
+ }
2470
+ /**
2471
+ * Detects indentation and newline formatting from an existing JSON file.
2472
+ */
2473
+ function detectJsonFileFormatting(fileContent) {
2474
+ if (!fileContent) {
2475
+ return {
2476
+ indentation: DEFAULT_JSON_FILE_INDENTATION,
2477
+ newline: DEFAULT_JSON_FILE_NEWLINE,
2478
+ };
2479
+ }
2480
+ const indentationMatch = fileContent.match(/^[ \t]+(?=")/mu);
2481
+ return {
2482
+ indentation: (indentationMatch === null || indentationMatch === void 0 ? void 0 : indentationMatch[0]) || DEFAULT_JSON_FILE_INDENTATION,
2483
+ newline: fileContent.includes('\r\n') ? '\r\n' : '\n',
2484
+ };
2485
+ }
2486
+ /**
2487
+ * Checks whether one parsed JSON value is a plain object.
2488
+ */
2489
+ function isPlainObject(value) {
2490
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
2491
+ }
2025
2492
  /**
2026
2493
  * Checks whether a path exists and is a file.
2027
2494
  */
@@ -4046,33 +4513,6 @@
4046
4513
  }
4047
4514
  }
4048
4515
 
4049
- /**
4050
- * This error indicates that promptbook not found in the collection
4051
- *
4052
- * @public exported from `@promptbook/core`
4053
- */
4054
- class NotFoundError extends Error {
4055
- constructor(message) {
4056
- super(message);
4057
- this.name = 'NotFoundError';
4058
- Object.setPrototypeOf(this, NotFoundError.prototype);
4059
- }
4060
- }
4061
-
4062
- /**
4063
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
4064
- *
4065
- * @public exported from `@promptbook/core`
4066
- */
4067
- class ParseError extends Error {
4068
- constructor(message) {
4069
- super(message);
4070
- this.name = 'ParseError';
4071
- Object.setPrototypeOf(this, ParseError.prototype);
4072
- }
4073
- }
4074
- // TODO: Maybe split `ParseError` and `ApplyError`
4075
-
4076
4516
  /**
4077
4517
  * Generates random token
4078
4518
  *
@@ -44051,11 +44491,11 @@
44051
44491
  ${block(options.prompt)}
44052
44492
 
44053
44493
  ${delimiter}
44054
- )" \
44055
- --yolo \
44056
- --no-ask-user \
44057
- --no-color \
44058
- --output-format json \
44494
+ )" \\
44495
+ --yolo \\
44496
+ --no-ask-user \\
44497
+ --no-color \\
44498
+ --output-format json \\
44059
44499
  --stream off${modelArgument}${thinkingLevelArgument}
44060
44500
  `);
44061
44501
  }