gitpt 1.1.0 → 1.3.0

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 (100) hide show
  1. package/README.md +52 -32
  2. package/dist/commands/commit/context/systemPrompt.d.ts +1 -0
  3. package/dist/commands/commit/context/systemPrompt.js +38 -0
  4. package/dist/commands/commit/context/userPrompt.d.ts +1 -0
  5. package/dist/commands/commit/context/userPrompt.js +5 -0
  6. package/dist/commands/commit/generateCommitMessage.d.ts +1 -0
  7. package/dist/commands/commit/generateCommitMessage.js +39 -0
  8. package/dist/commands/{commit.d.ts → commit/index.d.ts} +1 -1
  9. package/dist/commands/commit/index.js +183 -0
  10. package/dist/commands/config.d.ts +1 -0
  11. package/dist/commands/config.js +11 -0
  12. package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.d.ts +1 -0
  13. package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.js +23 -0
  14. package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.d.ts +1 -0
  15. package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.js +12 -0
  16. package/dist/commands/middleware/capabilitiesMiddleware/index.d.ts +3 -0
  17. package/dist/commands/middleware/capabilitiesMiddleware/index.js +15 -0
  18. package/dist/commands/middleware/hasStagedChangesMiddleware.d.ts +1 -0
  19. package/dist/commands/middleware/hasStagedChangesMiddleware.js +8 -0
  20. package/dist/commands/middleware/setupMiddleware/getAvailableModels.d.ts +4 -0
  21. package/dist/commands/middleware/setupMiddleware/getAvailableModels.js +11 -0
  22. package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.d.ts +1 -0
  23. package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.js +39 -0
  24. package/dist/commands/middleware/setupMiddleware/index.d.ts +4 -0
  25. package/dist/commands/middleware/setupMiddleware/index.js +40 -0
  26. package/dist/commands/middleware/setupMiddleware/selectModel.d.ts +5 -0
  27. package/dist/commands/middleware/setupMiddleware/selectModel.js +38 -0
  28. package/dist/commands/middleware/setupMiddleware/setupLocalLLM.d.ts +5 -0
  29. package/dist/commands/middleware/setupMiddleware/setupLocalLLM.js +60 -0
  30. package/dist/commands/middleware/setupMiddleware/setupOpenRouter.d.ts +2 -0
  31. package/dist/commands/middleware/setupMiddleware/setupOpenRouter.js +66 -0
  32. package/dist/commands/middleware/setupMiddleware/types.d.ts +13 -0
  33. package/dist/commands/middleware/setupMiddleware/types.js +1 -0
  34. package/dist/commands/model.d.ts +1 -1
  35. package/dist/commands/model.js +6 -114
  36. package/dist/commands/pr/context/systemPrompt.d.ts +1 -0
  37. package/dist/commands/pr/context/systemPrompt.js +18 -0
  38. package/dist/commands/pr/context/userPrompt.d.ts +1 -0
  39. package/dist/commands/pr/context/userPrompt.js +20 -0
  40. package/dist/commands/pr/generatePRDetails.d.ts +4 -0
  41. package/dist/commands/pr/generatePRDetails.js +35 -0
  42. package/dist/commands/pr/getPRContext.d.ts +1 -0
  43. package/dist/commands/pr/getPRContext.js +65 -0
  44. package/dist/commands/{pr.d.ts → pr/index.d.ts} +1 -1
  45. package/dist/commands/pr/index.js +66 -0
  46. package/dist/commands/setup.d.ts +3 -1
  47. package/dist/commands/setup.js +15 -60
  48. package/dist/config.d.ts +18 -0
  49. package/dist/config.js +58 -0
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.js +37 -49
  52. package/dist/llm/index.d.ts +5 -0
  53. package/dist/llm/index.js +14 -0
  54. package/dist/services/gh/createPullRequest.d.ts +1 -0
  55. package/dist/services/gh/createPullRequest.js +63 -0
  56. package/dist/services/gh/index.d.ts +4 -0
  57. package/dist/services/gh/index.js +6 -0
  58. package/dist/services/gh/isAvailable.d.ts +1 -0
  59. package/dist/services/gh/isAvailable.js +10 -0
  60. package/dist/services/git/executeGitAdd.d.ts +1 -0
  61. package/dist/services/git/executeGitAdd.js +15 -0
  62. package/dist/services/git/executeGitCommit.d.ts +1 -0
  63. package/dist/services/git/executeGitCommit.js +12 -0
  64. package/dist/services/git/getChangedFiles.d.ts +1 -0
  65. package/dist/services/git/getChangedFiles.js +64 -0
  66. package/dist/services/git/getCommitsSinceBaseBranch.d.ts +1 -0
  67. package/dist/services/git/getCommitsSinceBaseBranch.js +59 -0
  68. package/dist/services/git/getCurrentBranch.d.ts +1 -0
  69. package/dist/services/git/getCurrentBranch.js +11 -0
  70. package/dist/services/git/getDefaultBranch.d.ts +1 -0
  71. package/dist/services/git/getDefaultBranch.js +63 -0
  72. package/dist/services/git/getStagedChanges.d.ts +1 -0
  73. package/dist/services/git/getStagedChanges.js +11 -0
  74. package/dist/services/git/getStagedFiles.d.ts +1 -0
  75. package/dist/services/git/getStagedFiles.js +12 -0
  76. package/dist/services/git/hasStagedChanges.d.ts +1 -0
  77. package/dist/services/git/hasStagedChanges.js +10 -0
  78. package/dist/services/git/index.d.ts +13 -0
  79. package/dist/services/git/index.js +24 -0
  80. package/dist/services/git/isAvailable.d.ts +1 -0
  81. package/dist/services/git/isAvailable.js +10 -0
  82. package/dist/services/git/isGitRepository.d.ts +1 -0
  83. package/dist/services/git/isGitRepository.js +10 -0
  84. package/dist/utils/commitlint.d.ts +25 -0
  85. package/dist/utils/commitlint.js +148 -0
  86. package/dist/utils/formatBaseURL.d.ts +1 -0
  87. package/dist/utils/formatBaseURL.js +7 -0
  88. package/dist/utils/maskApiKey.d.ts +1 -0
  89. package/dist/utils/maskApiKey.js +8 -0
  90. package/package.json +9 -6
  91. package/dist/commands/add.d.ts +0 -1
  92. package/dist/commands/add.js +0 -16
  93. package/dist/commands/commit.js +0 -74
  94. package/dist/commands/pr.js +0 -458
  95. package/dist/utils/api.d.ts +0 -1
  96. package/dist/utils/api.js +0 -58
  97. package/dist/utils/config.d.ts +0 -7
  98. package/dist/utils/config.js +0 -24
  99. package/dist/utils/git.d.ts +0 -6
  100. package/dist/utils/git.js +0 -62
package/README.md CHANGED
@@ -1,15 +1,21 @@
1
1
  # GitPT
2
2
 
3
- Git Prompt Tool is a CLI tool that helps you write commit messages using AI through [OpenRouter](https://openrouter.ai/). It acts as a complete git wrapper, enhancing specific commands with AI while passing through all other git commands directly.
3
+ Git Prompt Tool is a CLI tool that helps you write commit messages using AI through [OpenRouter](https://openrouter.ai/) or a local LLM. It acts as a complete git wrapper, enhancing specific commands with AI while passing through all other git commands directly.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - Acts as a complete git replacement - all git commands are supported
8
8
  - Generate commit messages with AI based on your code changes
9
+
10
+ `gitpt commit`
9
11
  - Create pull requests with AI-generated titles and descriptions
12
+
13
+ `gitpt pr create`
10
14
  - Compatible with all regular git options (flags, arguments, etc.)
11
15
  - Edit suggested messages before committing
12
16
  - Works with various AI models via OpenRouter
17
+ - Support for local LLMs with OpenAI-compatible API
18
+ - [Commitlint](https://commitlint.js.org/) support - read directly from your repository
13
19
 
14
20
  ## Installation
15
21
 
@@ -33,6 +39,9 @@ This will guide you through:
33
39
  1. Entering your OpenRouter API key
34
40
  2. Selecting an AI model from popular options or specifying a custom one
35
41
 
42
+ _It's recommended using small models, such as `openai/gpt-4.1-mini` as your model choice. Mainly due to its large context window, fast response times & cost-effective pricing._
43
+
44
+
36
45
  You'll need an [OpenRouter](https://openrouter.ai/) account to get an API key.
37
46
 
38
47
  ## Usage
@@ -59,14 +68,8 @@ gitpt merge --no-ff feature-branch
59
68
  Add files to the staging area just like you would with git:
60
69
 
61
70
  ```bash
62
- # Same as git add .
71
+ # Same as git add . (supports all the regular git add options)
63
72
  gitpt add .
64
-
65
- # Same as git add -p
66
- gitpt add -p
67
-
68
- # Same as git add src/*.ts
69
- gitpt add src/*.ts
70
73
  ```
71
74
 
72
75
  ### Creating Commits
@@ -75,44 +78,45 @@ Generate an AI-powered commit message based on your staged changes:
75
78
 
76
79
  ```bash
77
80
  gitpt commit
81
+
82
+ # Or supply -m argument, if you want to avoid gitpt generating the message
83
+ gitpt commit -m "feat: file hash validation"
84
+
85
+ # Pass any other git commit options
86
+ gitpt commit --amend
78
87
  ```
79
88
 
80
89
  The tool will:
81
90
  1. Analyze your staged changes
82
91
  2. Generate a commit message using the configured AI model
83
- 3. Show you the suggested message
84
- 4. Let you edit the message before committing
85
- 5. Create the commit with your approved message
92
+ 3. Validate against commitlint rules (if configured)
93
+ 4. Regenerate the message if it fails validation
94
+ 5. Show you the suggested message
95
+ 6. Let you edit the message before committing
96
+ 7. Create the commit with your approved message
86
97
 
87
98
  ### Changing Models
88
99
 
89
- You can change the AI model at any time without re-entering your API key:
100
+ You can change the AI model at any time:
90
101
 
91
102
  ```bash
92
- # Select model interactively (fetches available models from OpenRouter)
103
+ # Select model interactively (either OpenRouter or a local LLM)
93
104
  gitpt model
94
-
95
- # Specify model directly
96
- gitpt model openai/gpt-4o
97
-
98
- # Switch to a different Claude model
99
- gitpt model anthropic/claude-3-haiku
100
105
  ```
101
106
 
102
- ### Commit Options
107
+ _It's recommended using small models, such as `openai/gpt-4.1-mini` as your model choice. Mainly due to its large context window, fast response times & cost-effective pricing._
103
108
 
104
- You can use any standard git commit options with the `gitpt commit` command:
109
+ #### Using a Local LLM
105
110
 
106
- ```bash
107
- # Skip editing the message
108
- gitpt commit --no-edit
111
+ GitPT works with any local LLM that provides an OpenAI-compatible API endpoint, such as:
112
+ - [Ollama](https://ollama.ai/)
113
+ - [LM Studio](https://lmstudio.ai/)
114
+ - [LocalAI](https://localai.io/)
115
+ - Custom setups with tools like llama.cpp
109
116
 
110
- # Provide your own message instead of generating one
111
- gitpt commit -m "Your message here"
117
+ ## GitHub Usage
112
118
 
113
- # Pass any other git commit options
114
- gitpt commit --amend
115
- ```
119
+ If you have GitHub CLI (`gh`) installed, you can use GitPT to interact with GitHub (e.g. generate full pull requests).
116
120
 
117
121
  ### Creating Pull Requests
118
122
 
@@ -145,20 +149,22 @@ gitpt pr create --no-edit
145
149
  gitpt pr create --title "Your PR title here"
146
150
  ```
147
151
 
148
- > **Note:** This command requires GitHub CLI (`gh`) to be installed and authenticated.
149
-
150
152
  ## How It Works
151
153
 
152
- GitPT leverages AI via OpenRouter to enhance your Git workflow while acting as a complete git wrapper:
154
+ GitPT leverages AI to enhance your Git workflow while acting as a complete git wrapper:
153
155
 
154
156
  - **Command Handling:** GitPT intelligently routes commands - enhanced commands (commit, pr) use AI capabilities while all other git commands are passed directly to git.
155
157
 
156
158
  - **For commits:** Sends a diff of your staged changes to the AI, which generates a contextual commit message following best practices.
157
159
 
160
+ - **Commitlint Integration:** Automatically detects commitlint configuration files and validates generated commit messages against your project's commit conventions. If validation fails, it regenerates a compliant message.
161
+
158
162
  - **For pull requests:** Analyzes the commits and file changes between your branch and the base branch, then generates a suitable title and detailed description for your PR.
159
163
 
160
164
  - **For other git commands:** Passes them through directly to git with all arguments and options preserved, ensuring complete compatibility with your existing git workflow.
161
165
 
166
+ - **Local LLM Support:** Can use locally running LLM servers with OpenAI-compatible APIs instead of cloud-based services, providing privacy and offline capabilities.
167
+
162
168
  ## Development
163
169
 
164
170
  ```bash
@@ -176,6 +182,20 @@ npm run build
176
182
  npm link
177
183
  ```
178
184
 
185
+ ## Commitlint Integration
186
+
187
+ GitPT automatically detects and integrates with [commitlint](https://commitlint.js.org/) if it's configured in your repository:
188
+
189
+ - **Automatic Detection:** GitPT checks for common commitlint configuration files (commitlint.config.js, .commitlintrc.*, etc.)
190
+
191
+ - **Rule-Aware Generation:** When commitlint is detected, GitPT instructs the AI to generate messages that follow your specific commit conventions
192
+
193
+ - **Validation & Regeneration:** Generated messages are validated against your commitlint rules before committing. If validation fails, GitPT automatically regenerates a compliant message
194
+
195
+ - **Error Feedback:** Validation errors are sent to the AI to help it understand how to fix the message
196
+
197
+ This integration ensures that all AI-generated commit messages follow your team's established commit conventions without requiring manual corrections.
198
+
179
199
  ## License
180
200
 
181
201
  MIT
@@ -0,0 +1 @@
1
+ export declare const systemPrompt = "\nYou are a helpful assistant that generates concise, informative Git commit messages.\n\nFollow these strict rules:\n1. Use conventional commit format: type: description\n2. Types are: feat, fix, docs, style, refactor, test, chore\n3. NO scopes in parentheses - do not use feat(scope)\n4. Keep the entire message under 100 characters\n5. Use present tense (e.g., \"add feature\" not \"added feature\")\n6. Be brief but descriptive about WHAT changed\n7. Do not include detailed explanations\n\nCritical Rules:\n- Return a SINGLE LINE commit message only, with no additional explanations or paragraphs\n- Do NOT include a detailed message body section, just the commit title line\n- Do NOT use multiple lines, even for a single message\n\nExamples of Good Commit Messages:\n- feat: add user authentication system\n- fix: resolve crash when opening settings menu\n- refactor: simplify data processing pipeline\n- docs: update installation instructions in README\n- chore: update npm dependencies to latest versions\n- style: fix indentation in CSS files\n- test: add unit tests for payment processing\n- perf: optimize database queries for faster loading\n- build: update webpack configuration\n- ci: fix GitHub Actions workflow\n\nExamples of Bad Commit Messages:\n- added login screen \u274C (missing type prefix)\n- feat(auth): implement OAuth login \u274C (using scope parentheses)\n- This is a really long commit message that exceeds the limit and contains too much information \u274C (too long)\n- feat: Adding user auth\n \n This implements the login page... \u274C (contains multiple lines)\n- \"fix: update styling\" \u274C (includes quotes)\n";
@@ -0,0 +1,38 @@
1
+ export const systemPrompt = `
2
+ You are a helpful assistant that generates concise, informative Git commit messages.
3
+
4
+ Follow these strict rules:
5
+ 1. Use conventional commit format: type: description
6
+ 2. Types are: feat, fix, docs, style, refactor, test, chore
7
+ 3. NO scopes in parentheses - do not use feat(scope)
8
+ 4. Keep the entire message under 100 characters
9
+ 5. Use present tense (e.g., "add feature" not "added feature")
10
+ 6. Be brief but descriptive about WHAT changed
11
+ 7. Do not include detailed explanations
12
+
13
+ Critical Rules:
14
+ - Return a SINGLE LINE commit message only, with no additional explanations or paragraphs
15
+ - Do NOT include a detailed message body section, just the commit title line
16
+ - Do NOT use multiple lines, even for a single message
17
+
18
+ Examples of Good Commit Messages:
19
+ - feat: add user authentication system
20
+ - fix: resolve crash when opening settings menu
21
+ - refactor: simplify data processing pipeline
22
+ - docs: update installation instructions in README
23
+ - chore: update npm dependencies to latest versions
24
+ - style: fix indentation in CSS files
25
+ - test: add unit tests for payment processing
26
+ - perf: optimize database queries for faster loading
27
+ - build: update webpack configuration
28
+ - ci: fix GitHub Actions workflow
29
+
30
+ Examples of Bad Commit Messages:
31
+ - added login screen ❌ (missing type prefix)
32
+ - feat(auth): implement OAuth login ❌ (using scope parentheses)
33
+ - This is a really long commit message that exceeds the limit and contains too much information ❌ (too long)
34
+ - feat: Adding user auth
35
+
36
+ This implements the login page... ❌ (contains multiple lines)
37
+ - "fix: update styling" ❌ (includes quotes)
38
+ `;
@@ -0,0 +1 @@
1
+ export declare const userPrompt: (diff: string) => string;
@@ -0,0 +1,5 @@
1
+ export const userPrompt = (diff) => `
2
+ Generate a single-line commit message for the following git diff:
3
+
4
+ ${diff}
5
+ `;
@@ -0,0 +1 @@
1
+ export declare const generateCommitMessage: (diff: string, validationErrors?: string) => Promise<string>;
@@ -0,0 +1,39 @@
1
+ import { getConfig } from "../../config.js";
2
+ import { getLLMClient } from "../../llm/index.js";
3
+ import { getCommitlintRules, hasCommitlintConfig, } from "../../utils/commitlint.js";
4
+ import { systemPrompt } from "./context/systemPrompt.js";
5
+ import { userPrompt } from "./context/userPrompt.js";
6
+ export const generateCommitMessage = async (diff, validationErrors) => {
7
+ // Check if commitlint is configured
8
+ const hasCommitlint = hasCommitlintConfig();
9
+ const config = getConfig();
10
+ // Check if we have a configured model
11
+ if (!config.model) {
12
+ throw new Error('GitPT is not configured properly. Please run "gitpt setup" first.');
13
+ }
14
+ const { model } = config;
15
+ const baseRules = hasCommitlint ? getCommitlintRules() : "";
16
+ const errorInfo = validationErrors
17
+ ? `\n\nYOUR PREVIOUS MESSAGE FAILED VALIDATION WITH THESE ERRORS:\n${validationErrors}\n\nFIX THESE ISSUES IN YOUR NEW MESSAGE.`
18
+ : "";
19
+ const llmClient = getLLMClient();
20
+ const response = await llmClient.chat.completions.create({
21
+ model: model,
22
+ messages: [
23
+ {
24
+ role: "system",
25
+ content: [systemPrompt, baseRules, errorInfo].join("\n\n"),
26
+ },
27
+ {
28
+ role: "user",
29
+ content: userPrompt(diff),
30
+ },
31
+ ],
32
+ max_tokens: 300,
33
+ });
34
+ const message = response.choices[0].message.content;
35
+ if (!message) {
36
+ throw new Error("No message returned from LLM");
37
+ }
38
+ return message;
39
+ };
@@ -3,5 +3,5 @@ interface CommitOptions {
3
3
  edit?: boolean;
4
4
  [key: string]: any;
5
5
  }
6
- export declare function commitCommand(options: CommitOptions): Promise<void>;
6
+ export declare const commitCommand: (options: CommitOptions) => Promise<void>;
7
7
  export {};
@@ -0,0 +1,183 @@
1
+ import chalk from "chalk";
2
+ import inquirer from "inquirer";
3
+ import { capabilitiesMiddleware } from "../middleware/capabilitiesMiddleware/index.js";
4
+ import { setupMiddleware } from "../middleware/setupMiddleware/index.js";
5
+ import { git } from "../../services/git/index.js";
6
+ import { hasCommitlintConfig, validateCommitMessage, } from "../../utils/commitlint.js";
7
+ import { hasStagedChangesMiddleware } from "../middleware/hasStagedChangesMiddleware.js";
8
+ import { generateCommitMessage } from "./generateCommitMessage.js";
9
+ export const commitCommand = async (options) => {
10
+ capabilitiesMiddleware(["git"]);
11
+ await setupMiddleware();
12
+ hasStagedChangesMiddleware();
13
+ let commitMessage;
14
+ // If message is provided, use that
15
+ if (options.message) {
16
+ commitMessage = options.message;
17
+ }
18
+ else {
19
+ try {
20
+ // Get staged changes
21
+ const diff = git.getStagedChanges();
22
+ console.log(chalk.blue("Generating commit message..."));
23
+ // Check if commitlint is configured
24
+ let hasCommitlint = false;
25
+ try {
26
+ hasCommitlint = hasCommitlintConfig();
27
+ if (hasCommitlint) {
28
+ console.log(chalk.blue("Commitlint configuration detected. Generating message according to rules..."));
29
+ }
30
+ }
31
+ catch (error) {
32
+ console.warn(chalk.yellow("Warning: Error detecting commitlint config, proceeding without commitlint validation."));
33
+ }
34
+ // Generate first commit message
35
+ commitMessage = await generateCommitMessage(diff);
36
+ // If commitlint is configured, try to validate and regenerate up to 3 times
37
+ if (hasCommitlint) {
38
+ try {
39
+ console.log(chalk.blue("Validating commit message against commitlint rules..."));
40
+ // Try up to 3 times to get a valid message
41
+ let validMessage = false;
42
+ let attempts = 0;
43
+ const MAX_ATTEMPTS = 3;
44
+ let validationErrors;
45
+ while (!validMessage && attempts < MAX_ATTEMPTS) {
46
+ const validation = await validateCommitMessage(commitMessage);
47
+ if (validation.valid) {
48
+ console.log(chalk.green("✓ Commit message passed validation"));
49
+ validMessage = true;
50
+ break;
51
+ }
52
+ else {
53
+ attempts++;
54
+ console.log(chalk.yellow(`Commit message failed validation. ${attempts < MAX_ATTEMPTS
55
+ ? "Regenerating..."
56
+ : "Max attempts reached."}`));
57
+ if (validation.errors) {
58
+ console.log(chalk.gray(validation.errors));
59
+ validationErrors = validation.errors;
60
+ }
61
+ if (attempts < MAX_ATTEMPTS) {
62
+ // Try regenerating with validation errors
63
+ try {
64
+ commitMessage = await generateCommitMessage(diff, validationErrors);
65
+ }
66
+ catch (error) {
67
+ console.warn(chalk.yellow("Error regenerating message, breaking validation loop."));
68
+ break;
69
+ }
70
+ }
71
+ else {
72
+ // Ask user what to do after max attempts
73
+ const { action } = await inquirer.prompt([
74
+ {
75
+ type: "list",
76
+ name: "action",
77
+ message: "Failed to generate a valid commit message after 3 attempts. What would you like to do?",
78
+ choices: [
79
+ {
80
+ name: "Proceed with invalid message",
81
+ value: "proceed",
82
+ },
83
+ { name: "Edit message manually", value: "edit" },
84
+ { name: "Abort commit", value: "abort" },
85
+ ],
86
+ },
87
+ ]);
88
+ if (action === "abort") {
89
+ console.log(chalk.red("Commit aborted due to validation failures."));
90
+ process.exit(1);
91
+ }
92
+ else if (action === "edit") {
93
+ options.edit = true; // Force editor to open
94
+ }
95
+ else {
96
+ console.log(chalk.yellow("Proceeding with invalid message..."));
97
+ }
98
+ break;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ // If there's an error with commitlint validation, continue without it
105
+ console.warn(chalk.yellow("Warning: Error during commitlint validation, proceeding without validation."));
106
+ console.warn(chalk.gray("This may be due to an ESM module cycle conflict or missing commitlint dependencies."));
107
+ }
108
+ }
109
+ console.log(chalk.green("✓ Commit message generated"));
110
+ console.log("");
111
+ console.log(chalk.cyan("Generated message:"));
112
+ console.log(commitMessage);
113
+ console.log("");
114
+ }
115
+ catch (error) {
116
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
117
+ process.exit(1);
118
+ }
119
+ }
120
+ // If edit is true or not specified, prompt user to edit the message
121
+ if (options.edit !== false) {
122
+ const answer = await inquirer.prompt([
123
+ {
124
+ type: "editor",
125
+ name: "message",
126
+ message: "Edit commit message:",
127
+ default: commitMessage,
128
+ },
129
+ ]);
130
+ commitMessage = answer.message;
131
+ // Validate the edited message once
132
+ try {
133
+ const hasCommitlint = hasCommitlintConfig();
134
+ if (hasCommitlint) {
135
+ console.log(chalk.blue("Validating edited commit message..."));
136
+ const validation = await validateCommitMessage(commitMessage);
137
+ if (!validation.valid) {
138
+ console.log(chalk.yellow("Warning: Edited message still has validation issues."));
139
+ if (validation.errors) {
140
+ console.log(chalk.gray(validation.errors));
141
+ }
142
+ // Ask if user wants to proceed anyway
143
+ const { proceed } = await inquirer.prompt([
144
+ {
145
+ type: "confirm",
146
+ name: "proceed",
147
+ message: "Proceed with invalid commit message?",
148
+ default: false,
149
+ },
150
+ ]);
151
+ if (!proceed) {
152
+ console.log(chalk.red("Commit aborted by user."));
153
+ process.exit(1);
154
+ }
155
+ }
156
+ else {
157
+ console.log(chalk.green("✓ Edited message passed validation"));
158
+ }
159
+ }
160
+ }
161
+ catch (error) {
162
+ // If validation fails, just warn and continue
163
+ console.warn(chalk.yellow("Could not validate edited message, proceeding anyway."));
164
+ }
165
+ }
166
+ // Extract other git options to pass through
167
+ const gitOptions = Object.keys(options)
168
+ .filter((key) => !["message", "edit"].includes(key))
169
+ .map((key) => {
170
+ if (typeof options[key] === "boolean") {
171
+ return options[key] ? `--${key}` : `--no-${key}`;
172
+ }
173
+ return `--${key}=${options[key]}`;
174
+ });
175
+ try {
176
+ git.commit(commitMessage, gitOptions);
177
+ console.log(chalk.green("✓ Changes committed successfully"));
178
+ }
179
+ catch (error) {
180
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
181
+ process.exit(1);
182
+ }
183
+ };
@@ -0,0 +1 @@
1
+ export declare const configCommand: () => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import chalk from "chalk";
2
+ import { getConfig } from "../config.js";
3
+ import { maskApiKey } from "../utils/maskApiKey.js";
4
+ export const configCommand = async () => {
5
+ console.log(chalk.blue("GitPT Configuration"));
6
+ const { apiKey, ...config } = getConfig();
7
+ console.log({
8
+ apiKey: apiKey ? maskApiKey(apiKey) : undefined,
9
+ ...config,
10
+ });
11
+ };
@@ -0,0 +1 @@
1
+ export declare const ghCapability: () => void;
@@ -0,0 +1,23 @@
1
+ import chalk from "chalk";
2
+ import { execSync } from "child_process";
3
+ import { gh } from "../../../services/gh/index.js";
4
+ export const ghCapability = () => {
5
+ if (!gh.isAvailable()) {
6
+ console.error(chalk.red("GitHub CLI (gh) is not installed or available in PATH"));
7
+ console.log(chalk.yellow("Please install GitHub CLI from https://cli.github.com/"));
8
+ process.exit(1);
9
+ }
10
+ // Check if user is authenticated with GitHub CLI
11
+ try {
12
+ const authStatus = execSync("gh auth status -h github.com 2>&1 || true").toString();
13
+ if (authStatus.includes("not logged")) {
14
+ console.error(chalk.red("Error: You are not authenticated with GitHub CLI."));
15
+ console.log(chalk.yellow("Please run `gh auth login` to authenticate."));
16
+ process.exit(1);
17
+ }
18
+ }
19
+ catch (error) {
20
+ console.log(chalk.yellow("Warning: Could not verify GitHub CLI authentication."));
21
+ console.log(chalk.yellow("If PR creation fails, please run `gh auth login` first."));
22
+ }
23
+ };
@@ -0,0 +1 @@
1
+ export declare const gitCapability: () => void;
@@ -0,0 +1,12 @@
1
+ import chalk from "chalk";
2
+ import { git } from "../../../services/git/index.js";
3
+ export const gitCapability = () => {
4
+ if (!git.isAvailable()) {
5
+ console.error(chalk.red("Git command is not available, install it from https://git-scm.com/"));
6
+ process.exit(1);
7
+ }
8
+ if (!git.isGitRepository()) {
9
+ console.error(chalk.red("Not a git repository"));
10
+ process.exit(1);
11
+ }
12
+ };
@@ -0,0 +1,3 @@
1
+ type Capability = "git" | "gh";
2
+ export declare const capabilitiesMiddleware: (requiredCapabilities: Capability[]) => void;
3
+ export {};
@@ -0,0 +1,15 @@
1
+ import { ghCapability } from "./ghCapability.js";
2
+ import { gitCapability } from "./gitCapability.js";
3
+ const capabilities = {
4
+ git: gitCapability,
5
+ gh: ghCapability,
6
+ };
7
+ export const capabilitiesMiddleware = (requiredCapabilities) => {
8
+ requiredCapabilities.forEach((capabilityKey) => {
9
+ const resolver = capabilities[capabilityKey];
10
+ if (!resolver) {
11
+ throw new Error(`Capability ${capabilityKey} not found`);
12
+ }
13
+ resolver();
14
+ });
15
+ };
@@ -0,0 +1 @@
1
+ export declare const hasStagedChangesMiddleware: () => void;
@@ -0,0 +1,8 @@
1
+ import chalk from "chalk";
2
+ import { git } from "../../services/git/index.js";
3
+ export const hasStagedChangesMiddleware = () => {
4
+ if (!git.hasStagedChanges()) {
5
+ console.error(chalk.red("Error: No staged changes"));
6
+ process.exit(1);
7
+ }
8
+ };
@@ -0,0 +1,4 @@
1
+ import { Model } from "openai/resources/models";
2
+ export declare const getAvailableModels: (options?: {
3
+ baseURLOverride?: string;
4
+ }) => Promise<Model[]>;
@@ -0,0 +1,11 @@
1
+ import { getLLMClient } from "../../../llm/index.js";
2
+ export const getAvailableModels = async (options) => {
3
+ const { baseURLOverride } = options || {};
4
+ let modelsList = await getLLMClient({ baseURLOverride }).models.list();
5
+ const modelsData = modelsList.data;
6
+ while (modelsList.hasNextPage()) {
7
+ modelsList = await modelsList.getNextPage();
8
+ modelsData.push(...modelsList.data);
9
+ }
10
+ return modelsData;
11
+ };
@@ -0,0 +1 @@
1
+ export declare const getOrUpdateApiKey: (existingApiKey?: string) => Promise<string>;
@@ -0,0 +1,39 @@
1
+ import inquirer from "inquirer";
2
+ import { maskApiKey } from "../../../utils/maskApiKey.js";
3
+ export const getOrUpdateApiKey = async (existingApiKey) => {
4
+ // For setup command, handle API key selection
5
+ if (existingApiKey) {
6
+ // If we have an existing API key, ask if the user wants to keep it or use a new one
7
+ const useExistingKeyAnswer = await inquirer.prompt([
8
+ {
9
+ type: "list",
10
+ name: "useExistingKey",
11
+ message: "OpenRouter API key:",
12
+ choices: [
13
+ {
14
+ name: `Use existing key (${maskApiKey(existingApiKey)})`,
15
+ value: true,
16
+ },
17
+ { name: "Enter a new API key", value: false },
18
+ ],
19
+ },
20
+ ]);
21
+ if (useExistingKeyAnswer.useExistingKey) {
22
+ return existingApiKey;
23
+ }
24
+ }
25
+ // If no existing key or user wants a new one, prompt for a new API key
26
+ const apiKeyAnswer = await inquirer.prompt([
27
+ {
28
+ type: "input",
29
+ name: "apiKey",
30
+ message: "Enter your OpenRouter API key:",
31
+ validate: (input) => {
32
+ if (!input)
33
+ return "API key is required";
34
+ return true;
35
+ },
36
+ },
37
+ ]);
38
+ return apiKeyAnswer.apiKey;
39
+ };
@@ -0,0 +1,4 @@
1
+ import { GitPTConfig } from "../../../config.js";
2
+ export declare const setupMiddleware: (options?: {
3
+ context?: "setup" | "model" | "command";
4
+ }) => Promise<GitPTConfig>;
@@ -0,0 +1,40 @@
1
+ import inquirer from "inquirer";
2
+ import { getConfig, validateConfig } from "../../../config.js";
3
+ import { setupLocalLLM } from "./setupLocalLLM.js";
4
+ import { setupOpenRouter } from "./setupOpenRouter.js";
5
+ export const setupMiddleware = async (options) => {
6
+ const context = options?.context || "command";
7
+ // Always get the current config, even if it's empty
8
+ const existingConfig = getConfig();
9
+ // If we're running a command, and the config is valid, continue with the command
10
+ if (context === "command") {
11
+ const isValidConfig = validateConfig();
12
+ if (isValidConfig.isValid) {
13
+ return getConfig();
14
+ }
15
+ }
16
+ // For initial setup or model command, start by selecting the provider
17
+ const useLocalLLMAnswer = await inquirer.prompt([
18
+ {
19
+ type: "list",
20
+ name: "useLocalLLM",
21
+ message: "Select LLM provider:",
22
+ choices: [
23
+ { name: "OpenRouter (remote)", value: false },
24
+ { name: "Local LLM", value: true },
25
+ ],
26
+ default: existingConfig.provider === "local" ? 1 : 0,
27
+ },
28
+ ]);
29
+ // Update config based on selected provider
30
+ existingConfig.provider = useLocalLLMAnswer.useLocalLLM
31
+ ? "local"
32
+ : "openrouter";
33
+ // Proceed based on selected provider
34
+ if (useLocalLLMAnswer.useLocalLLM) {
35
+ return await setupLocalLLM(existingConfig);
36
+ }
37
+ else {
38
+ return await setupOpenRouter(existingConfig);
39
+ }
40
+ };