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