@promptbook/cli 0.114.0-30 → 0.114.0-32

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 (38) hide show
  1. package/esm/index.es.js +197 -25
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-codex-prompts/prompts/findFirstNonEmptyLineIndex.d.ts +6 -0
  4. package/esm/scripts/run-codex-prompts/prompts/writePromptStatusLine.d.ts +3 -2
  5. package/esm/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.d.ts +9 -0
  6. package/esm/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.test.d.ts +1 -0
  7. package/esm/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +14 -1
  8. package/esm/src/cli/cli-commands/coder/server.test.d.ts +1 -0
  9. package/esm/src/cli/cli-commands/common/$askForConfirmation.d.ts +9 -0
  10. package/esm/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +10 -0
  11. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +6 -0
  12. package/esm/src/version.d.ts +1 -1
  13. package/package.json +1 -1
  14. package/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.ts +51 -0
  15. package/src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts +37 -3
  16. package/src/cli/cli-commands/coder/init.ts +1 -1
  17. package/src/cli/cli-commands/coder/ping.ts +3 -1
  18. package/src/cli/cli-commands/coder/run.ts +3 -0
  19. package/src/cli/cli-commands/coder/server.ts +3 -0
  20. package/src/cli/cli-commands/common/$askForConfirmation.ts +31 -0
  21. package/src/cli/cli-commands/common/harness/HarnessDefinition.ts +25 -0
  22. package/src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts +2 -18
  23. package/src/cli/cli-commands/common/projectInitialization.ts +25 -2
  24. package/src/other/templates/getTemplatesPipelineCollection.ts +645 -964
  25. package/src/version.ts +2 -2
  26. package/src/versions.txt +2 -0
  27. package/umd/index.umd.js +197 -25
  28. package/umd/index.umd.js.map +1 -1
  29. package/umd/scripts/run-codex-prompts/prompts/findFirstNonEmptyLineIndex.d.ts +6 -0
  30. package/umd/scripts/run-codex-prompts/prompts/writePromptStatusLine.d.ts +3 -2
  31. package/umd/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.d.ts +9 -0
  32. package/umd/src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.test.d.ts +1 -0
  33. package/umd/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +14 -1
  34. package/umd/src/cli/cli-commands/coder/server.test.d.ts +1 -0
  35. package/umd/src/cli/cli-commands/common/$askForConfirmation.d.ts +9 -0
  36. package/umd/src/cli/cli-commands/common/harness/HarnessDefinition.d.ts +10 -0
  37. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +6 -0
  38. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Finds the first non-empty line within the inclusive bounds.
3
+ *
4
+ * @private internal utility of prompt parsing and status writing
5
+ */
6
+ export declare function findFirstNonEmptyLineIndex(lines: readonly string[], startLine: number, endLine: number): number | undefined;
@@ -3,7 +3,8 @@ import type { PromptSection } from './types/PromptSection';
3
3
  /**
4
4
  * Rewrites the status line of one prompt section while preserving its indentation.
5
5
  *
6
- * Only a todo `[ ]` or an in-progress `[^]` status line is rewritten, so an already finalized
7
- * `[x]`, `[!]` or `[-]` status is never overwritten by accident.
6
+ * An unmarked prompt is implicitly a todo, so its first live status line is inserted immediately before its content.
7
+ * Only a todo `[ ]` or an in-progress `[^]` status line is otherwise rewritten, so an already finalized `[x]`, `[!]`
8
+ * or `[-]` status is never overwritten by accident.
8
9
  */
9
10
  export declare function writePromptStatusLine(file: PromptFile, section: PromptSection, replacementStatusLine: string): void;
@@ -0,0 +1,9 @@
1
+ import type { PromptRunnerHarnessName } from '../common/promptRunnerCliOptions';
2
+ /**
3
+ * Offers to add missing project-local ignore rules for the selected coding harness.
4
+ *
5
+ * Note: `$` is used to indicate that this function is not a pure function - it may ask the user and update `.gitignore`.
6
+ *
7
+ * @private internal utility of `ptbk coder`
8
+ */
9
+ export declare function $ensureCoderHarnessGitignoreRules(projectPath: string, harnessName: PromptRunnerHarnessName | undefined): Promise<void>;
@@ -1,7 +1,20 @@
1
+ import { type PromptRunnerHarnessName } from '../common/promptRunnerCliOptions';
1
2
  import type { InitializationStatus } from './boilerplateTemplates';
2
3
  /**
3
- * Ensures `.gitignore` contains the standalone Promptbook temp entry.
4
+ * Ensures `.gitignore` contains Promptbook Coder's local artifacts and every supported harness's local artifacts.
4
5
  *
5
6
  * @private function of `initializeCoderProjectConfiguration`
6
7
  */
7
8
  export declare function ensureCoderGitignoreFile(projectPath: string): Promise<InitializationStatus>;
9
+ /**
10
+ * Lists missing project-local ignore rules for the selected harnesses.
11
+ *
12
+ * @private internal utility of `ptbk coder`
13
+ */
14
+ export declare function getMissingCoderHarnessGitignoreRules(projectPath: string, harnessNames: ReadonlyArray<PromptRunnerHarnessName>): Promise<ReadonlyArray<string>>;
15
+ /**
16
+ * Adds the given Promptbook Coder rules to a project's `.gitignore` file.
17
+ *
18
+ * @private internal utility of `ptbk coder`
19
+ */
20
+ export declare function ensureCoderGitignoreRules(projectPath: string, rules: ReadonlyArray<string>): Promise<InitializationStatus>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Asks the user in the terminal to confirm one optional change.
3
+ *
4
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin.
5
+ *
6
+ * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive.
7
+ * @private internal utility of `promptbookCli`
8
+ */
9
+ export declare function $askForConfirmation(question: string): Promise<boolean>;
@@ -45,6 +45,10 @@ export type HarnessDefinition = {
45
45
  * Extra environment variables required by the global npm installation of this harness.
46
46
  */
47
47
  readonly npmInstallEnvironment?: Readonly<Record<string, string>>;
48
+ /**
49
+ * Project-local files or directories which this harness can create and which should not be committed.
50
+ */
51
+ readonly projectGitignoreRules: ReadonlyArray<string>;
48
52
  /**
49
53
  * How a standalone installation of this harness is recognized and updated.
50
54
  *
@@ -64,3 +68,9 @@ export declare const HARNESS_DEFINITIONS: Readonly<Record<PromptRunnerHarnessNam
64
68
  * @private internal utility of `promptbookCli`
65
69
  */
66
70
  export declare function getHarnessDefinition(harnessName: PromptRunnerHarnessName): HarnessDefinition;
71
+ /**
72
+ * Lists the unique project ignore rules required by the selected harnesses, in selection order.
73
+ *
74
+ * @private internal utility of `promptbookCli`
75
+ */
76
+ export declare function getHarnessProjectGitignoreRules(harnessNames: ReadonlyArray<PromptRunnerHarnessName>): ReadonlyArray<string>;
@@ -50,6 +50,12 @@ export declare function ensureProjectEnvFile<TEnvVariable extends ProjectEnvVari
50
50
  * @private internal utility of Promptbook CLI project initialization
51
51
  */
52
52
  export declare function ensureProjectGitignoreFile({ projectPath, blockHeader, rules, }: EnsureProjectGitignoreFileOptions): Promise<ProjectInitializationStatus>;
53
+ /**
54
+ * Lists the ignore rules which are not yet present in a project's `.gitignore` file.
55
+ *
56
+ * @private internal utility of Promptbook CLI project initialization
57
+ */
58
+ export declare function getMissingProjectGitignoreRules(projectPath: string, rules: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
53
59
  /**
54
60
  * Reads one text file when it exists, otherwise returns `undefined`.
55
61
  *
@@ -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.114.0-29`).
18
+ * It follows semantic versioning (e.g., `0.114.0-31`).
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.114.0-30",
3
+ "version": "0.114.0-32",
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,51 @@
1
+ import colors from 'colors';
2
+ import type { PromptRunnerHarnessName } from '../common/promptRunnerCliOptions';
3
+ import { $askForConfirmation } from '../common/$askForConfirmation';
4
+ import { getHarnessDefinition } from '../common/harness/HarnessDefinition';
5
+ import { ensureCoderGitignoreRules, getMissingCoderHarnessGitignoreRules } from './ensureCoderGitignoreFile';
6
+
7
+ /**
8
+ * Offers to add missing project-local ignore rules for the selected coding harness.
9
+ *
10
+ * Note: `$` is used to indicate that this function is not a pure function - it may ask the user and update `.gitignore`.
11
+ *
12
+ * @private internal utility of `ptbk coder`
13
+ */
14
+ export async function $ensureCoderHarnessGitignoreRules(
15
+ projectPath: string,
16
+ harnessName: PromptRunnerHarnessName | undefined,
17
+ ): Promise<void> {
18
+ if (harnessName === undefined) {
19
+ return;
20
+ }
21
+
22
+ const missingRules = await getMissingCoderHarnessGitignoreRules(projectPath, [harnessName]);
23
+
24
+ if (missingRules.length === 0) {
25
+ return;
26
+ }
27
+
28
+ const { label } = getHarnessDefinition(harnessName);
29
+ const formattedRules = formatGitignoreRules(missingRules);
30
+ const entryLabel = missingRules.length === 1 ? 'entry' : 'entries';
31
+ const isAdditionApproved = await $askForConfirmation(
32
+ `Add the missing ${label} ignore ${entryLabel} ${formattedRules} to \`.gitignore\` now?`,
33
+ );
34
+
35
+ if (!isAdditionApproved) {
36
+ console.info(colors.gray(`Skipped, add ${formattedRules} to \`.gitignore\` manually.`));
37
+ return;
38
+ }
39
+
40
+ await ensureCoderGitignoreRules(projectPath, missingRules);
41
+ console.info(colors.gray(`✔ Added ${formattedRules} to \`.gitignore\`.`));
42
+ }
43
+
44
+ /**
45
+ * Formats one or more `.gitignore` rules for a terminal confirmation message.
46
+ */
47
+ function formatGitignoreRules(rules: ReadonlyArray<string>): string {
48
+ return rules.map((rule) => `\`${rule}\``).join(', ');
49
+ }
50
+
51
+ // Note: [🟡] Code for coder harness gitignore prompts [$ensureCoderHarnessGitignoreRules](src/cli/cli-commands/coder/$ensureCoderHarnessGitignoreRules.ts) should never be published outside of `@promptbook/cli`
@@ -1,6 +1,8 @@
1
1
  import { getPromptbookTemporaryGitignoreRule } from '../../../utils/filesystem/promptbookTemporaryPath';
2
+ import { PROMPT_RUNNER_HARNESS_NAMES, type PromptRunnerHarnessName } from '../common/promptRunnerCliOptions';
3
+ import { getHarnessProjectGitignoreRules } from '../common/harness/HarnessDefinition';
2
4
  import type { InitializationStatus } from './boilerplateTemplates';
3
- import { ensureProjectGitignoreFile } from '../common/projectInitialization';
5
+ import { ensureProjectGitignoreFile, getMissingProjectGitignoreRules } from '../common/projectInitialization';
4
6
 
5
7
  /**
6
8
  * Promptbook temporary root directory that should stay out of version control.
@@ -18,15 +20,47 @@ const CODER_ENV_GITIGNORE_RULE = '.env';
18
20
  const CODER_GITIGNORE_HEADER = '# Promptbook Coder';
19
21
 
20
22
  /**
21
- * Ensures `.gitignore` contains the standalone Promptbook temp entry.
23
+ * Coder-owned local artifacts which every initialized project should ignore.
24
+ */
25
+ const CODER_GITIGNORE_RULES = [PROMPTBOOK_TEMP_GITIGNORE_RULE, CODER_ENV_GITIGNORE_RULE] as const;
26
+
27
+ /**
28
+ * Ensures `.gitignore` contains Promptbook Coder's local artifacts and every supported harness's local artifacts.
22
29
  *
23
30
  * @private function of `initializeCoderProjectConfiguration`
24
31
  */
25
32
  export async function ensureCoderGitignoreFile(projectPath: string): Promise<InitializationStatus> {
33
+ return ensureCoderGitignoreRules(projectPath, [
34
+ ...CODER_GITIGNORE_RULES,
35
+ ...getHarnessProjectGitignoreRules(PROMPT_RUNNER_HARNESS_NAMES),
36
+ ]);
37
+ }
38
+
39
+ /**
40
+ * Lists missing project-local ignore rules for the selected harnesses.
41
+ *
42
+ * @private internal utility of `ptbk coder`
43
+ */
44
+ export async function getMissingCoderHarnessGitignoreRules(
45
+ projectPath: string,
46
+ harnessNames: ReadonlyArray<PromptRunnerHarnessName>,
47
+ ): Promise<ReadonlyArray<string>> {
48
+ return getMissingProjectGitignoreRules(projectPath, getHarnessProjectGitignoreRules(harnessNames));
49
+ }
50
+
51
+ /**
52
+ * Adds the given Promptbook Coder rules to a project's `.gitignore` file.
53
+ *
54
+ * @private internal utility of `ptbk coder`
55
+ */
56
+ export async function ensureCoderGitignoreRules(
57
+ projectPath: string,
58
+ rules: ReadonlyArray<string>,
59
+ ): Promise<InitializationStatus> {
26
60
  return ensureProjectGitignoreFile({
27
61
  projectPath,
28
62
  blockHeader: CODER_GITIGNORE_HEADER,
29
- rules: [PROMPTBOOK_TEMP_GITIGNORE_RULE, CODER_ENV_GITIGNORE_RULE],
63
+ rules,
30
64
  });
31
65
  }
32
66
 
@@ -62,7 +62,7 @@ export function $initializeCoderInitCommand(program: Program): $side_effect {
62
62
  - ${CODER_DEVELOPER_AGENT_FILE_PATH}
63
63
  - ${AGENTS_FILE_PATH}
64
64
  - ${AGENT_CODING_FILE_PATH}
65
- - .gitignore
65
+ - .gitignore with local artifacts from every supported harness
66
66
  - package.json
67
67
  - .vscode/settings.json
68
68
 
@@ -18,6 +18,7 @@ import {
18
18
  PROMPT_RUNNER_DESCRIPTION,
19
19
  } from '../common/promptRunnerCliOptions';
20
20
  import { parseOptionalPeriodDuration } from './waitOptions';
21
+ import { $ensureCoderHarnessGitignoreRules } from './$ensureCoderHarnessGitignoreRules';
21
22
 
22
23
  /**
23
24
  * Initializes `coder ping` command for Promptbook CLI utilities
@@ -39,7 +40,7 @@ export function $initializeCoderPingCommand(program: Program): $side_effect {
39
40
  - Reports the answer of the harness, the response time and the reported usage
40
41
  - Starts the hourly/weekly quota window before you need it, so it is already refreshing when you do
41
42
  - Optional --period keeps the quota window refreshing by pinging once per period until stopped
42
- - Leaves the project exactly as it was nothing is read, written, changed or committed
43
+ - Makes no coding changes or commits; if the selected harness has missing local ignore rules, offers to add them to .gitignore
43
44
  - Checks that the selected harness is installed and up to date unless --no-harness-update is used
44
45
  - Use --no-ui to stream the raw harness output instead of only the compact result
45
46
  `),
@@ -74,6 +75,7 @@ export function $initializeCoderPingCommand(program: Program): $side_effect {
74
75
  const periodMs = parseOptionalPeriodDuration('--period', periodValue);
75
76
 
76
77
  await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
78
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
77
79
 
78
80
  const pingOptions = {
79
81
  agentName: runnerOptions.agentName,
@@ -29,6 +29,7 @@ import {
29
29
  type TestBeforeMode,
30
30
  } from '../../../../scripts/run-codex-prompts/testing/TestBeforeMode';
31
31
  import { DEFAULT_WAIT_AFTER_ERROR_MS, parseOptionalWaitDuration } from './waitOptions';
32
+ import { $ensureCoderHarnessGitignoreRules } from './$ensureCoderHarnessGitignoreRules';
32
33
 
33
34
  /**
34
35
  * Initializes `coder run` command for Promptbook CLI utilities
@@ -56,6 +57,7 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
56
57
  - Optional --preserve-logs keeps temp prompt/log artifacts after successful rounds
57
58
  - Optional --no-ui keeps plain streaming console output for logging and debugging
58
59
  - Checks that the selected harness is installed and up to date before the first prompt unless --no-harness-update is used
60
+ - Offers to add missing project-local ignore rules for the selected harness
59
61
  - In interactive mode, checks local and global Promptbook CLI installations and offers to update them
60
62
  - Supports GPG signing of commits
61
63
  - Optional pre-coding test run that can stop or repair pre-existing failures
@@ -223,6 +225,7 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
223
225
  }
224
226
 
225
227
  await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
228
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
226
229
 
227
230
  const waitAfterPrompt = parseOptionalWaitDuration(waitAfterPromptValue, 0);
228
231
  const waitBetweenPrompts = parseOptionalWaitDuration(waitBetweenPromptsValue, 0);
@@ -25,6 +25,7 @@ import {
25
25
  PROMPT_RUNNER_DESCRIPTION,
26
26
  } from '../common/promptRunnerCliOptions';
27
27
  import { DEFAULT_WAIT_AFTER_ERROR_MS, parseOptionalWaitDuration } from './waitOptions';
28
+ import { $ensureCoderHarnessGitignoreRules } from './$ensureCoderHarnessGitignoreRules';
28
29
 
29
30
  /**
30
31
  * Default port used by `ptbk coder server`.
@@ -54,6 +55,7 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
54
55
  Features:
55
56
  - Runs the same prompt processing as \`ptbk coder run\`
56
57
  - Checks that the selected harness is installed and up to date on startup unless --no-harness-update is used
58
+ - Offers to add missing project-local ignore rules for the selected harness
57
59
  - Does not exit when all prompts are done; polls for new prompt files instead
58
60
  - Serves a kanban board at http://localhost:<port> for visual progress tracking
59
61
  - Allows editing prompt files directly from the browser (Trello-style)
@@ -184,6 +186,7 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
184
186
  );
185
187
 
186
188
  await $ensureHarnessInstallations([runnerOptions.agentName], isHarnessUpdateCheckEnabled);
189
+ await $ensureCoderHarnessGitignoreRules(process.cwd(), runnerOptions.agentName);
187
190
 
188
191
  // [1] Parse the wait options and --no-auto (same logic as `coder run`)
189
192
  const waitForUser = !auto;
@@ -0,0 +1,31 @@
1
+ import colors from 'colors';
2
+ import { createInterface } from 'readline';
3
+
4
+ /**
5
+ * Asks the user in the terminal to confirm one optional change.
6
+ *
7
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the answer from stdin.
8
+ *
9
+ * @returns `true` when the user confirms, `false` when the user declines or the terminal is not interactive.
10
+ * @private internal utility of `promptbookCli`
11
+ */
12
+ export async function $askForConfirmation(question: string): Promise<boolean> {
13
+ if (!process.stdin.isTTY) {
14
+ // Note: In non-interactive environments like CI there is nobody who could confirm the change.
15
+ return false;
16
+ }
17
+
18
+ const readlineInterface = createInterface({ input: process.stdin, output: process.stdout });
19
+
20
+ try {
21
+ const answer = await new Promise<string>((resolve) => {
22
+ readlineInterface.question(colors.cyan(`${question} [y/N] `), resolve);
23
+ });
24
+
25
+ return ['y', 'yes'].includes(answer.trim().toLowerCase());
26
+ } finally {
27
+ readlineInterface.close();
28
+ }
29
+ }
30
+
31
+ // Note: [🟡] Code for CLI confirmations [$askForConfirmation](src/cli/cli-commands/common/$askForConfirmation.ts) should never be published outside of `@promptbook/cli`
@@ -55,6 +55,11 @@ export type HarnessDefinition = {
55
55
  */
56
56
  readonly npmInstallEnvironment?: Readonly<Record<string, string>>;
57
57
 
58
+ /**
59
+ * Project-local files or directories which this harness can create and which should not be committed.
60
+ */
61
+ readonly projectGitignoreRules: ReadonlyArray<string>;
62
+
58
63
  /**
59
64
  * How a standalone installation of this harness is recognized and updated.
60
65
  *
@@ -74,6 +79,7 @@ export const HARNESS_DEFINITIONS: Readonly<Record<PromptRunnerHarnessName, Harne
74
79
  label: 'OpenAI Codex',
75
80
  commandName: 'codex',
76
81
  npmPackageName: '@openai/codex',
82
+ projectGitignoreRules: ['.codex'],
77
83
  // Note: The official standalone installer keeps its package in `~/.codex` and links it onto the `PATH`
78
84
  standaloneInstallation: {
79
85
  directoryNames: ['.codex'],
@@ -85,6 +91,7 @@ export const HARNESS_DEFINITIONS: Readonly<Record<PromptRunnerHarnessName, Harne
85
91
  label: 'GitHub Copilot',
86
92
  commandName: 'copilot',
87
93
  npmPackageName: '@github/copilot',
94
+ projectGitignoreRules: ['.github/copilot/settings.local.json'],
88
95
  // Note: The GitHub Copilot CLI downloads its native binary in a postinstall script
89
96
  npmInstallEnvironment: { npm_config_ignore_scripts: 'false' },
90
97
  },
@@ -93,30 +100,35 @@ export const HARNESS_DEFINITIONS: Readonly<Record<PromptRunnerHarnessName, Harne
93
100
  label: 'Cline',
94
101
  commandName: 'cline',
95
102
  npmPackageName: 'cline',
103
+ projectGitignoreRules: ['.cline'],
96
104
  },
97
105
  'claude-code': {
98
106
  harnessName: 'claude-code',
99
107
  label: 'Claude Code',
100
108
  commandName: 'claude',
101
109
  npmPackageName: '@anthropic-ai/claude-code',
110
+ projectGitignoreRules: ['.claude'],
102
111
  },
103
112
  opencode: {
104
113
  harnessName: 'opencode',
105
114
  label: 'Opencode',
106
115
  commandName: 'opencode',
107
116
  npmPackageName: 'opencode-ai',
117
+ projectGitignoreRules: ['.opencode'],
108
118
  },
109
119
  gemini: {
110
120
  harnessName: 'gemini',
111
121
  label: 'Gemini CLI',
112
122
  commandName: 'gemini',
113
123
  npmPackageName: '@google/gemini-cli',
124
+ projectGitignoreRules: ['.gemini'],
114
125
  },
115
126
  'qwen-code': {
116
127
  harnessName: 'qwen-code',
117
128
  label: 'Qwen Code',
118
129
  commandName: 'qwen',
119
130
  npmPackageName: '@qwen-code/qwen-code',
131
+ projectGitignoreRules: ['.qwen'],
120
132
  },
121
133
  };
122
134
 
@@ -140,3 +152,16 @@ export function getHarnessDefinition(harnessName: PromptRunnerHarnessName): Harn
140
152
 
141
153
  return definition;
142
154
  }
155
+
156
+ /**
157
+ * Lists the unique project ignore rules required by the selected harnesses, in selection order.
158
+ *
159
+ * @private internal utility of `promptbookCli`
160
+ */
161
+ export function getHarnessProjectGitignoreRules(
162
+ harnessNames: ReadonlyArray<PromptRunnerHarnessName>,
163
+ ): ReadonlyArray<string> {
164
+ return Array.from(
165
+ new Set(harnessNames.flatMap((harnessName) => getHarnessDefinition(harnessName).projectGitignoreRules)),
166
+ );
167
+ }
@@ -1,5 +1,4 @@
1
- import colors from 'colors';
2
- import { createInterface } from 'readline';
1
+ import { $askForConfirmation } from '../$askForConfirmation';
3
2
 
4
3
  /**
5
4
  * Asks the user in the terminal whether an npm package should be installed or updated now.
@@ -10,22 +9,7 @@ import { createInterface } from 'readline';
10
9
  * @private internal utility of `promptbookCli`
11
10
  */
12
11
  export async function $askForNpmPackageInstallationApproval(question: string): Promise<boolean> {
13
- if (!process.stdin.isTTY) {
14
- // Note: In non-interactive environments like CI there is nobody who could confirm the installation
15
- return false;
16
- }
17
-
18
- const readlineInterface = createInterface({ input: process.stdin, output: process.stdout });
19
-
20
- try {
21
- const answer = await new Promise<string>((resolve) => {
22
- readlineInterface.question(colors.cyan(`${question} [y/N] `), resolve);
23
- });
24
-
25
- return ['y', 'yes'].includes(answer.trim().toLowerCase());
26
- } finally {
27
- readlineInterface.close();
28
- }
12
+ return $askForConfirmation(question);
29
13
  }
30
14
 
31
15
  // Note: [🟡] Code for CLI npm package installation approval [$askForNpmPackageInstallationApproval](src/cli/cli-commands/common/npm/$askForNpmPackageInstallationApproval.ts) should never be published outside of `@promptbook/cli`
@@ -113,7 +113,7 @@ export async function ensureProjectGitignoreFile({
113
113
  }: EnsureProjectGitignoreFileOptions): Promise<ProjectInitializationStatus> {
114
114
  const gitignorePath = join(projectPath, GITIGNORE_FILE_PATH);
115
115
  const currentGitignoreContent = await readTextFileIfExists(gitignorePath);
116
- const missingRules = rules.filter((rule) => !hasGitignoreRule(currentGitignoreContent || '', rule));
116
+ const missingRules = getMissingGitignoreRules(currentGitignoreContent || '', rules);
117
117
 
118
118
  if (currentGitignoreContent !== undefined && missingRules.length === 0) {
119
119
  return 'unchanged';
@@ -132,6 +132,21 @@ export async function ensureProjectGitignoreFile({
132
132
  return currentGitignoreContent === undefined ? 'created' : 'updated';
133
133
  }
134
134
 
135
+ /**
136
+ * Lists the ignore rules which are not yet present in a project's `.gitignore` file.
137
+ *
138
+ * @private internal utility of Promptbook CLI project initialization
139
+ */
140
+ export async function getMissingProjectGitignoreRules(
141
+ projectPath: string,
142
+ rules: ReadonlyArray<string>,
143
+ ): Promise<ReadonlyArray<string>> {
144
+ const gitignorePath = join(projectPath, GITIGNORE_FILE_PATH);
145
+ const currentGitignoreContent = (await readTextFileIfExists(gitignorePath)) || '';
146
+
147
+ return getMissingGitignoreRules(currentGitignoreContent, rules);
148
+ }
149
+
135
150
  /**
136
151
  * Reads one text file when it exists, otherwise returns `undefined`.
137
152
  *
@@ -191,9 +206,17 @@ function parseEnvVariableNames(envContent: string): Set<string> {
191
206
  * Detects whether `.gitignore` already covers one exact rule.
192
207
  */
193
208
  function hasGitignoreRule(gitignoreContent: string, rule: string): boolean {
194
- const normalizedRulePattern = rule.startsWith('/') ? `/?${escapeRegExp(rule.slice(1))}` : escapeRegExp(rule);
209
+ const normalizedRule = rule.startsWith('/') ? rule.slice(1) : rule;
210
+ const normalizedRulePattern = `/?${escapeRegExp(normalizedRule)}`;
195
211
  return new RegExp(`(^|[\\r\\n])${normalizedRulePattern}(?:[\\r\\n]|$)`, 'u').test(gitignoreContent);
196
212
  }
197
213
 
214
+ /**
215
+ * Finds the unique rules which are not covered by existing `.gitignore` content.
216
+ */
217
+ function getMissingGitignoreRules(gitignoreContent: string, rules: ReadonlyArray<string>): ReadonlyArray<string> {
218
+ return Array.from(new Set(rules)).filter((rule) => !hasGitignoreRule(gitignoreContent, rule));
219
+ }
220
+
198
221
  // Note: [🟡] Code for CLI project initialization [projectInitialization](src/cli/cli-commands/common/projectInitialization.ts) should never be published outside of `@promptbook/cli`
199
222
  // Note: [💞] Ignore a discrepancy between file name and entity name