gitpt 1.2.0 → 1.4.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.
- package/README.md +20 -10
- package/dist/commands/commit/context/systemPrompt.d.ts +1 -0
- package/dist/commands/commit/context/systemPrompt.js +38 -0
- package/dist/commands/commit/context/userPrompt.d.ts +1 -0
- package/dist/commands/commit/context/userPrompt.js +5 -0
- package/dist/commands/commit/generateCommitMessage.d.ts +1 -0
- package/dist/commands/commit/generateCommitMessage.js +39 -0
- package/dist/commands/{commit.d.ts → commit/index.d.ts} +1 -1
- package/dist/commands/commit/index.js +183 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +11 -0
- package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.d.ts +1 -0
- package/dist/commands/middleware/capabilitiesMiddleware/ghCapability.js +23 -0
- package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.d.ts +1 -0
- package/dist/commands/middleware/capabilitiesMiddleware/gitCapability.js +12 -0
- package/dist/commands/middleware/capabilitiesMiddleware/index.d.ts +3 -0
- package/dist/commands/middleware/capabilitiesMiddleware/index.js +15 -0
- package/dist/commands/middleware/hasStagedChangesMiddleware.d.ts +1 -0
- package/dist/commands/middleware/hasStagedChangesMiddleware.js +8 -0
- package/dist/commands/middleware/setupMiddleware/getAvailableModels.d.ts +4 -0
- package/dist/commands/middleware/setupMiddleware/getAvailableModels.js +11 -0
- package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.d.ts +1 -0
- package/dist/commands/middleware/setupMiddleware/getOrUpdateApiKey.js +39 -0
- package/dist/commands/middleware/setupMiddleware/index.d.ts +4 -0
- package/dist/commands/middleware/setupMiddleware/index.js +40 -0
- package/dist/commands/middleware/setupMiddleware/selectModel.d.ts +5 -0
- package/dist/commands/middleware/setupMiddleware/selectModel.js +38 -0
- package/dist/commands/middleware/setupMiddleware/setupLocalLLM.d.ts +5 -0
- package/dist/commands/middleware/setupMiddleware/setupLocalLLM.js +60 -0
- package/dist/commands/middleware/setupMiddleware/setupOpenRouter.d.ts +2 -0
- package/dist/commands/middleware/setupMiddleware/setupOpenRouter.js +66 -0
- package/dist/commands/middleware/setupMiddleware/types.d.ts +13 -0
- package/dist/commands/middleware/setupMiddleware/types.js +1 -0
- package/dist/commands/model.d.ts +1 -1
- package/dist/commands/model.js +6 -114
- package/dist/commands/pr/context/systemPrompt.d.ts +1 -0
- package/dist/commands/pr/context/systemPrompt.js +18 -0
- package/dist/commands/pr/context/userPrompt.d.ts +1 -0
- package/dist/commands/pr/context/userPrompt.js +22 -0
- package/dist/commands/pr/generatePRDetails.d.ts +4 -0
- package/dist/commands/pr/generatePRDetails.js +35 -0
- package/dist/commands/pr/getPRContext.d.ts +1 -0
- package/dist/commands/pr/getPRContext.js +65 -0
- package/dist/commands/{pr.d.ts → pr/index.d.ts} +1 -1
- package/dist/commands/pr/index.js +66 -0
- package/dist/commands/setup.d.ts +3 -1
- package/dist/commands/setup.js +15 -60
- package/dist/config.d.ts +18 -0
- package/dist/config.js +58 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +37 -49
- package/dist/llm/index.d.ts +5 -0
- package/dist/llm/index.js +14 -0
- package/dist/services/gh/createPullRequest.d.ts +1 -0
- package/dist/services/gh/createPullRequest.js +63 -0
- package/dist/services/gh/index.d.ts +4 -0
- package/dist/services/gh/index.js +6 -0
- package/dist/services/gh/isAvailable.d.ts +1 -0
- package/dist/services/gh/isAvailable.js +10 -0
- package/dist/services/git/executeGitAdd.d.ts +1 -0
- package/dist/services/git/executeGitAdd.js +15 -0
- package/dist/services/git/executeGitCommit.d.ts +1 -0
- package/dist/services/git/executeGitCommit.js +12 -0
- package/dist/services/git/getChangedFiles.d.ts +1 -0
- package/dist/services/git/getChangedFiles.js +64 -0
- package/dist/services/git/getCommitsSinceBaseBranch.d.ts +1 -0
- package/dist/services/git/getCommitsSinceBaseBranch.js +59 -0
- package/dist/services/git/getCurrentBranch.d.ts +1 -0
- package/dist/services/git/getCurrentBranch.js +11 -0
- package/dist/services/git/getDefaultBranch.d.ts +1 -0
- package/dist/services/git/getDefaultBranch.js +63 -0
- package/dist/services/git/getStagedChanges.d.ts +1 -0
- package/dist/services/git/getStagedChanges.js +11 -0
- package/dist/services/git/getStagedFiles.d.ts +1 -0
- package/dist/services/git/getStagedFiles.js +12 -0
- package/dist/services/git/hasStagedChanges.d.ts +1 -0
- package/dist/services/git/hasStagedChanges.js +10 -0
- package/dist/services/git/index.d.ts +13 -0
- package/dist/services/git/index.js +24 -0
- package/dist/services/git/isAvailable.d.ts +1 -0
- package/dist/services/git/isAvailable.js +10 -0
- package/dist/services/git/isGitRepository.d.ts +1 -0
- package/dist/services/git/isGitRepository.js +10 -0
- package/dist/utils/commitlint.d.ts +4 -3
- package/dist/utils/commitlint.js +62 -38
- package/dist/utils/formatBaseURL.d.ts +1 -0
- package/dist/utils/formatBaseURL.js +7 -0
- package/dist/utils/maskApiKey.d.ts +1 -0
- package/dist/utils/maskApiKey.js +8 -0
- package/package.json +9 -10
- package/dist/commands/add.d.ts +0 -1
- package/dist/commands/add.js +0 -16
- package/dist/commands/commit.js +0 -99
- package/dist/commands/pr.js +0 -458
- package/dist/utils/api.d.ts +0 -1
- package/dist/utils/api.js +0 -61
- package/dist/utils/config.d.ts +0 -7
- package/dist/utils/config.js +0 -24
- package/dist/utils/git.d.ts +0 -6
- package/dist/utils/git.js +0 -62
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
|
@@ -14,6 +14,7 @@ Git Prompt Tool is a CLI tool that helps you write commit messages using AI thro
|
|
|
14
14
|
- Compatible with all regular git options (flags, arguments, etc.)
|
|
15
15
|
- Edit suggested messages before committing
|
|
16
16
|
- Works with various AI models via OpenRouter
|
|
17
|
+
- Support for local LLMs with OpenAI-compatible API
|
|
17
18
|
- [Commitlint](https://commitlint.js.org/) support - read directly from your repository
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
@@ -38,6 +39,9 @@ This will guide you through:
|
|
|
38
39
|
1. Entering your OpenRouter API key
|
|
39
40
|
2. Selecting an AI model from popular options or specifying a custom one
|
|
40
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
|
+
|
|
41
45
|
You'll need an [OpenRouter](https://openrouter.ai/) account to get an API key.
|
|
42
46
|
|
|
43
47
|
## Usage
|
|
@@ -93,18 +97,22 @@ The tool will:
|
|
|
93
97
|
|
|
94
98
|
### Changing Models
|
|
95
99
|
|
|
96
|
-
You can change the AI model at any time
|
|
100
|
+
You can change the AI model at any time:
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
|
-
# Select model interactively (
|
|
103
|
+
# Select model interactively (either OpenRouter or a local LLM)
|
|
100
104
|
gitpt model
|
|
105
|
+
```
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
gitpt model openai/gpt-4o
|
|
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._
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
#### Using a Local LLM
|
|
110
|
+
|
|
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
|
|
108
116
|
|
|
109
117
|
## GitHub Usage
|
|
110
118
|
|
|
@@ -143,7 +151,7 @@ gitpt pr create --title "Your PR title here"
|
|
|
143
151
|
|
|
144
152
|
## How It Works
|
|
145
153
|
|
|
146
|
-
GitPT leverages AI
|
|
154
|
+
GitPT leverages AI to enhance your Git workflow while acting as a complete git wrapper:
|
|
147
155
|
|
|
148
156
|
- **Command Handling:** GitPT intelligently routes commands - enhanced commands (commit, pr) use AI capabilities while all other git commands are passed directly to git.
|
|
149
157
|
|
|
@@ -155,6 +163,8 @@ GitPT leverages AI via OpenRouter to enhance your Git workflow while acting as a
|
|
|
155
163
|
|
|
156
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.
|
|
157
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
|
+
|
|
158
168
|
## Development
|
|
159
169
|
|
|
160
170
|
```bash
|
|
@@ -188,4 +198,4 @@ This integration ensures that all AI-generated commit messages follow your team'
|
|
|
188
198
|
|
|
189
199
|
## License
|
|
190
200
|
|
|
191
|
-
MIT
|
|
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 @@
|
|
|
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
|
+
};
|
|
@@ -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,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,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,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
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
/**
|
|
3
|
+
* Show a list of models to select from
|
|
4
|
+
*/
|
|
5
|
+
export const selectModel = async (models, existingModel) => {
|
|
6
|
+
const modelChoices = models.map((model) => ({
|
|
7
|
+
name: model.name
|
|
8
|
+
? `${model.name} (Context: ${model.context_length})`
|
|
9
|
+
: model.id,
|
|
10
|
+
value: model.id,
|
|
11
|
+
}));
|
|
12
|
+
modelChoices.push({
|
|
13
|
+
name: "Other (specify model identifier)",
|
|
14
|
+
value: "custom",
|
|
15
|
+
});
|
|
16
|
+
const answers = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: "list",
|
|
19
|
+
name: "modelChoice",
|
|
20
|
+
message: "Select an AI model:",
|
|
21
|
+
choices: modelChoices,
|
|
22
|
+
default: () => {
|
|
23
|
+
const currentIndex = modelChoices.findIndex((choice) => choice.value === existingModel);
|
|
24
|
+
return currentIndex >= 0 ? currentIndex : 0;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "input",
|
|
29
|
+
name: "customModel",
|
|
30
|
+
message: "Enter model identifier:",
|
|
31
|
+
when: (answers) => answers.modelChoice === "custom",
|
|
32
|
+
validate: (input) => input ? true : "Model identifier is required",
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
return answers.modelChoice === "custom"
|
|
36
|
+
? answers.customModel
|
|
37
|
+
: answers.modelChoice;
|
|
38
|
+
};
|