gitpt 1.2.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 +20 -10
  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 +4 -3
  85. package/dist/utils/commitlint.js +62 -38
  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 -10
  91. package/dist/commands/add.d.ts +0 -1
  92. package/dist/commands/add.js +0 -16
  93. package/dist/commands/commit.js +0 -99
  94. package/dist/commands/pr.js +0 -458
  95. package/dist/utils/api.d.ts +0 -1
  96. package/dist/utils/api.js +0 -61
  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,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 without re-entering your API key:
100
+ You can change the AI model at any time:
97
101
 
98
102
  ```bash
99
- # Select model interactively (fetches available models from OpenRouter)
103
+ # Select model interactively (either OpenRouter or a local LLM)
100
104
  gitpt model
105
+ ```
101
106
 
102
- # Specify model directly
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
- # Switch to a different Claude model
106
- gitpt model anthropic/claude-3-haiku
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 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:
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,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
+ };
@@ -0,0 +1,5 @@
1
+ import { Model } from "./types.js";
2
+ /**
3
+ * Show a list of models to select from
4
+ */
5
+ export declare const selectModel: (models: Model[], existingModel?: string) => Promise<string>;
@@ -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
+ };
@@ -0,0 +1,5 @@
1
+ import { GitPTConfig } from "../../../config.js";
2
+ /**
3
+ * Set up a local LLM configuration
4
+ */
5
+ export declare const setupLocalLLM: (existingConfig: GitPTConfig) => Promise<GitPTConfig>;