mirror of
https://github.com/di-sukharev/opencommit.git
synced 2026-01-12 23:28:16 -05:00
Compare commits
4 Commits
oco_find_v
...
update_oll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69b3c00a52 | ||
|
|
6f4afbfb52 | ||
|
|
796de7b07e | ||
|
|
9ad281a4ee |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,4 @@ uncaughtExceptions.log
|
||||
src/*.json
|
||||
.idea
|
||||
test.ts
|
||||
notes.md
|
||||
*.excalidraw
|
||||
*.tldr
|
||||
notes.md
|
||||
11
README.md
11
README.md
@@ -28,7 +28,9 @@ You can use OpenCommit by simply running it via the CLI like this `oco`. 2 secon
|
||||
npm install -g opencommit
|
||||
```
|
||||
|
||||
Alternatively run it via `npx opencommit` or `bunx opencommit`, but you need to create ~/.opencommit config file in place.
|
||||
Alternatively run it via `npx opencommit` or `bunx opencommit`
|
||||
|
||||
MacOS may ask to run the command with `sudo` when installing a package globally.
|
||||
|
||||
2. Get your API key from [OpenAI](https://platform.openai.com/account/api-keys). Make sure that you add your payment details, so the API works.
|
||||
|
||||
@@ -74,7 +76,8 @@ oco config set OCO_AI_PROVIDER='ollama'
|
||||
If you want to use a model other than mistral (default), you can do so by setting the `OCO_AI_PROVIDER` environment variable as follows:
|
||||
|
||||
```sh
|
||||
oco config set OCO_AI_PROVIDER='ollama/llama3:8b'
|
||||
oco config set OCO_AI_PROVIDER='ollama'
|
||||
oco config set OCO_MODEL='llama3:8b'
|
||||
```
|
||||
|
||||
If you have ollama that is set up in docker/ on another machine with GPUs (not locally), you can change the default endpoint url.
|
||||
@@ -125,12 +128,12 @@ OCO_TOKENS_MAX_OUTPUT=<max response tokens (default: 500)>
|
||||
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to OpenAI api>
|
||||
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>
|
||||
OCO_EMOJI=<boolean, add GitMoji>
|
||||
OCO_MODEL=<either 'gpt-4o', 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo' (default), 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview' or 'gpt-4-0125-preview' or any string basically, but it should be a valid model name>
|
||||
OCO_MODEL=<either 'gpt-4o', 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo' (default), 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview' or 'gpt-4-0125-preview' or any Anthropic or Ollama model or any string basically, but it should be a valid model name>
|
||||
OCO_LANGUAGE=<locale, scroll to the bottom to see options>
|
||||
OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'>
|
||||
OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit>
|
||||
OCO_ONE_LINE_COMMIT=<one line commit message, default: false>
|
||||
OCO_AI_PROVIDER=<openai (default), anthropic, azure, ollama or ollama/model>
|
||||
OCO_AI_PROVIDER=<openai (default), anthropic, azure, ollama>
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
10939
out/cli.cjs
10939
out/cli.cjs
File diff suppressed because one or more lines are too long
@@ -55994,42 +55994,42 @@ var OpenAIClient = class {
|
||||
// src/engine/azure.ts
|
||||
var AzureEngine = class {
|
||||
constructor(config6) {
|
||||
this.generateCommitMessage = async (messages) => {
|
||||
try {
|
||||
const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a3, b3) => a3 + b3, 0);
|
||||
if (REQUEST_TOKENS > this.config.maxTokensInput - this.config.maxTokensOutput) {
|
||||
throw new Error("TOO_MUCH_TOKENS" /* tooMuchTokens */);
|
||||
}
|
||||
const data = await this.client.getChatCompletions(
|
||||
this.config.model,
|
||||
messages
|
||||
);
|
||||
const message = data.choices[0].message;
|
||||
if (message?.content === null) {
|
||||
return void 0;
|
||||
}
|
||||
return message?.content;
|
||||
} catch (error) {
|
||||
ce(`${source_default.red("\u2716")} ${this.config.model}`);
|
||||
const err = error;
|
||||
ce(`${source_default.red("\u2716")} ${JSON.stringify(error)}`);
|
||||
if (axios_default.isAxiosError(error) && error.response?.status === 401) {
|
||||
const openAiError = error.response.data.error;
|
||||
if (openAiError?.message)
|
||||
ce(openAiError.message);
|
||||
ce(
|
||||
"For help look into README https://github.com/di-sukharev/opencommit#setup"
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
this.config = config6;
|
||||
this.client = new OpenAIClient(
|
||||
this.config.baseURL,
|
||||
new AzureKeyCredential(this.config.apiKey)
|
||||
);
|
||||
}
|
||||
async generateCommitMessage(messages) {
|
||||
try {
|
||||
const REQUEST_TOKENS = messages.map((msg) => tokenCount(msg.content) + 4).reduce((a3, b3) => a3 + b3, 0);
|
||||
if (REQUEST_TOKENS > this.config.maxTokensInput - this.config.maxTokensOutput) {
|
||||
throw new Error("TOO_MUCH_TOKENS" /* tooMuchTokens */);
|
||||
}
|
||||
const data = await this.client.getChatCompletions(
|
||||
this.config.model,
|
||||
messages
|
||||
);
|
||||
const message = data.choices[0].message;
|
||||
if (message?.content === null) {
|
||||
return void 0;
|
||||
}
|
||||
return message?.content;
|
||||
} catch (error) {
|
||||
ce(`${source_default.red("\u2716")} ${this.config.model}`);
|
||||
const err = error;
|
||||
ce(`${source_default.red("\u2716")} ${JSON.stringify(error)}`);
|
||||
if (axios_default.isAxiosError(error) && error.response?.status === 401) {
|
||||
const openAiError = error.response.data.error;
|
||||
if (openAiError?.message)
|
||||
ce(openAiError.message);
|
||||
ce(
|
||||
"For help look into README https://github.com/di-sukharev/opencommit#setup"
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// src/engine/flowise.ts
|
||||
@@ -56902,7 +56902,10 @@ var OllamaAi = class {
|
||||
stream: false
|
||||
};
|
||||
try {
|
||||
const response = await this.client.post("", params);
|
||||
const response = await this.client.post(
|
||||
this.client.getUri(this.config),
|
||||
params
|
||||
);
|
||||
const message = response.data.message;
|
||||
return message?.content;
|
||||
} catch (err) {
|
||||
|
||||
340
package-lock.json
generated
340
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "opencommit",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
@@ -956,6 +956,38 @@
|
||||
"resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.13.tgz",
|
||||
"integrity": "sha512-941kjlHjfI97l6NuH/AwuXV4mHuVnRooDcHNSlzi98hz+4ug3wT4gJcWjSwSZHqeGAEn90lC9sFD+8a9d5Jvxg=="
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
|
||||
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
@@ -3646,6 +3678,54 @@
|
||||
"esbuild-windows-arm64": "0.15.18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
|
||||
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
|
||||
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
|
||||
@@ -3662,6 +3742,262 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
|
||||
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
|
||||
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
|
||||
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
|
||||
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
|
||||
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
|
||||
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
|
||||
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
|
||||
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
|
||||
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
|
||||
"keywords": [
|
||||
"git",
|
||||
|
||||
5
src/CommandsEnum.ts
Normal file
5
src/CommandsEnum.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum COMMANDS {
|
||||
config = 'config',
|
||||
hook = 'hook',
|
||||
commitlint = 'commitlint'
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { configCommand } from './commands/config';
|
||||
import { hookCommand, isHookCalled } from './commands/githook.js';
|
||||
import { prepareCommitMessageHook } from './commands/prepare-commit-msg-hook';
|
||||
import { checkIsLatestVersion } from './utils/checkIsLatestVersion';
|
||||
import { findCommand } from './commands/find';
|
||||
|
||||
const extraArgs = process.argv.slice(2);
|
||||
|
||||
@@ -17,12 +16,7 @@ cli(
|
||||
{
|
||||
version: packageJSON.version,
|
||||
name: 'opencommit',
|
||||
commands: [
|
||||
configCommand,
|
||||
hookCommand,
|
||||
commitlintConfigCommand,
|
||||
findCommand
|
||||
],
|
||||
commands: [configCommand, hookCommand, commitlintConfigCommand],
|
||||
flags: {
|
||||
fgm: Boolean,
|
||||
yes: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export enum COMMANDS {
|
||||
config = 'config',
|
||||
hook = 'hook',
|
||||
commitlint = 'commitlint',
|
||||
find = 'find'
|
||||
commitlint = 'commitlint'
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const commitlintConfigCommand = command(
|
||||
parameters: ['<mode>']
|
||||
},
|
||||
async (argv) => {
|
||||
intro('OpenCommit — configure @commitlint');
|
||||
intro('opencommit — configure @commitlint');
|
||||
try {
|
||||
const { mode } = argv._;
|
||||
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
import {
|
||||
confirm,
|
||||
intro,
|
||||
isCancel,
|
||||
note,
|
||||
outro,
|
||||
select,
|
||||
spinner
|
||||
} from '@clack/prompts';
|
||||
import chalk from 'chalk';
|
||||
import { command } from 'cleye';
|
||||
import { execa } from 'execa';
|
||||
import { getIgnoredFolders } from '../utils/git';
|
||||
import { COMMANDS } from './ENUMS';
|
||||
import { OpenAiEngine } from '../engine/openAi';
|
||||
import { getConfig } from './config';
|
||||
|
||||
type Occurrence = {
|
||||
fileName: string;
|
||||
context: {
|
||||
number: number;
|
||||
content: string;
|
||||
};
|
||||
matches: {
|
||||
number: number;
|
||||
content: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- [ ] format declarations as file:line => context -> declaration
|
||||
- [ ] format usages as file:line => context -> usage
|
||||
- [ ] expand on usage to see it's call hierarchy
|
||||
- [ ] generate Mermaid diagram
|
||||
*/
|
||||
|
||||
const generateMermaid = async (stdout: string) => {
|
||||
const config = getConfig();
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
model: config.OCO_MODEL!,
|
||||
maxTokensOutput: config.OCO_TOKENS_MAX_OUTPUT!,
|
||||
maxTokensInput: config.OCO_TOKENS_MAX_INPUT!,
|
||||
baseURL: config.OCO_OPENAI_BASE_PATH!
|
||||
};
|
||||
const engine = new OpenAiEngine({
|
||||
...DEFAULT_CONFIG,
|
||||
apiKey: config.OCO_OPENAI_API_KEY!
|
||||
});
|
||||
|
||||
const diagram = await engine.generateCommitMessage([
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are to generate a mermaid diagram from the given function. Strictly answer in this json format: { "mermaid": "<mermaid diagram>" }. Where <mermaid diagram> is a valid mermaid diagram, e.g:
|
||||
graph TD
|
||||
A[Start] --> B[Generate Commit Message]
|
||||
B --> C{Token count >= Max?}
|
||||
C -->|Yes| D[Process file diffs]
|
||||
C -->|No| E[Generate single message]
|
||||
D --> F[Join messages]
|
||||
E --> G[Generate message]
|
||||
F --> H[End]
|
||||
G --> H
|
||||
B --> I{Error occurred?}
|
||||
I -->|Yes| J[Handle error]
|
||||
J --> H
|
||||
I -->|No| H
|
||||
`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: stdout
|
||||
}
|
||||
]);
|
||||
|
||||
return JSON.parse(diagram as string);
|
||||
};
|
||||
|
||||
export function extractFuncName(line: string) {
|
||||
const regex =
|
||||
/(?:function|export\s+const|const|let|var)?\s*(?:async\s+)?(\w+)\s*(?:=\s*(?:async\s*)?\(|\()/;
|
||||
const match = line.match(regex);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractSingle(lineContent: string): string | null {
|
||||
const match = lineContent.match(/\s*(?:public\s+)?(?:async\s+)?(\w+)\s*=/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function mapLinesToOccurrences(input: string[], step: number = 3) {
|
||||
const occurrences: Occurrence[] = [];
|
||||
let single;
|
||||
|
||||
for (let i = 0; i < input.length; i += step) {
|
||||
if (i + 1 >= input.length) break;
|
||||
|
||||
const [fileName, callerLineNumber, ...callerLineContent] =
|
||||
input[i].split(/[=:]/);
|
||||
const [, definitionLineNumber, ...definitionLineContent] =
|
||||
input[i + 1].split(/[:]/);
|
||||
|
||||
if (!single) single = extractSingle(definitionLineContent.join(':'));
|
||||
|
||||
occurrences.push({
|
||||
fileName,
|
||||
context: {
|
||||
number: parseInt(callerLineNumber, 10),
|
||||
content: callerLineContent.join('=').trim()
|
||||
},
|
||||
matches: [
|
||||
{
|
||||
number: parseInt(definitionLineNumber, 10),
|
||||
content: definitionLineContent.join(':').trim()
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return { occurrences, single };
|
||||
}
|
||||
|
||||
const findDeclarations = async (query: string[], ignoredFolders: string[]) => {
|
||||
const searchQuery = `(async|function|public).*${query.join('[^ \\n]*')}`;
|
||||
|
||||
outro(`Searching: ${searchQuery}`);
|
||||
|
||||
const occurrences = await findInFiles({ query: searchQuery, ignoredFolders });
|
||||
|
||||
if (!occurrences) return null;
|
||||
|
||||
const declarations = mapLinesToOccurrences(occurrences.split('\n'));
|
||||
|
||||
return declarations;
|
||||
};
|
||||
|
||||
const findUsagesByDeclaration = async (
|
||||
declaration: string,
|
||||
ignoredFolders: string[]
|
||||
) => {
|
||||
const searchQuery = `${declaration}\\(.*\\)`;
|
||||
|
||||
const occurrences = await findInFiles({
|
||||
query: searchQuery,
|
||||
ignoredFolders
|
||||
// grepOptions: ['--function-context']
|
||||
});
|
||||
|
||||
if (!occurrences) return null;
|
||||
|
||||
const usages = mapLinesToOccurrences(
|
||||
occurrences.split('\n').filter(Boolean),
|
||||
2
|
||||
);
|
||||
|
||||
return usages;
|
||||
};
|
||||
|
||||
const buildCallHierarchy = async (
|
||||
query: string[],
|
||||
ignoredFolders: string[]
|
||||
) => {};
|
||||
|
||||
const findInFiles = async ({
|
||||
query,
|
||||
ignoredFolders,
|
||||
grepOptions = []
|
||||
}: {
|
||||
query: string;
|
||||
ignoredFolders: string[];
|
||||
grepOptions?: string[];
|
||||
}): Promise<string | null> => {
|
||||
const withIgnoredFolders =
|
||||
ignoredFolders.length > 0
|
||||
? [
|
||||
'--',
|
||||
' ',
|
||||
'.',
|
||||
' ',
|
||||
ignoredFolders.map((folder) => `:^${folder}`).join(' ')
|
||||
]
|
||||
: [];
|
||||
|
||||
const params = [
|
||||
'--no-pager',
|
||||
'grep',
|
||||
'--show-function', // show function caller
|
||||
'-n',
|
||||
'-i',
|
||||
...grepOptions,
|
||||
'--break',
|
||||
'--color=never',
|
||||
|
||||
// '-C',
|
||||
// '1',
|
||||
|
||||
// '--full-name',
|
||||
// '--heading',
|
||||
'--threads',
|
||||
'10',
|
||||
'-E',
|
||||
query,
|
||||
...withIgnoredFolders
|
||||
];
|
||||
|
||||
try {
|
||||
const { stdout } = await execa('git', params);
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const generatePermutations = (arr: string[]): string[][] => {
|
||||
const n = arr.length;
|
||||
const result: string[][] = [];
|
||||
const indices = new Int32Array(n);
|
||||
|
||||
const current = new Array(n);
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
indices[i] = i;
|
||||
current[i] = arr[i];
|
||||
}
|
||||
result.push([...current]);
|
||||
|
||||
let i = 1;
|
||||
while (i < n) {
|
||||
if (indices[i] > 0) {
|
||||
const j = indices[i] % 2 === 1 ? 0 : indices[i];
|
||||
|
||||
[current[i], current[j]] = [current[j], current[i]];
|
||||
result.push([...current]);
|
||||
indices[i]--;
|
||||
i = 1;
|
||||
} else {
|
||||
indices[i] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const shuffleQuery = (query: string[]): string[][] => {
|
||||
return generatePermutations(query);
|
||||
};
|
||||
|
||||
export const findCommand = command(
|
||||
{
|
||||
name: COMMANDS.find,
|
||||
parameters: ['<query...>']
|
||||
},
|
||||
async (argv) => {
|
||||
const query = argv._;
|
||||
|
||||
intro(`OpenCommit — 🔦 find`);
|
||||
const ignoredFolders = getIgnoredFolders();
|
||||
|
||||
const searchSpinner = spinner();
|
||||
let declarations = await findDeclarations(query, ignoredFolders);
|
||||
|
||||
outro(`No matches found. Searching semantically similar queries.`);
|
||||
|
||||
searchSpinner.start(`Searching for matches...`);
|
||||
|
||||
if (!declarations?.occurrences.length) {
|
||||
const allPossibleQueries = shuffleQuery(query).reverse();
|
||||
for (const possibleQuery of allPossibleQueries) {
|
||||
declarations = await findDeclarations(possibleQuery, ignoredFolders);
|
||||
if (declarations?.occurrences.length) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!declarations?.occurrences.length) {
|
||||
searchSpinner.stop(`${chalk.red('✘')} No function declarations found.`);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
const usages = await findUsagesByDeclaration(
|
||||
declarations.single,
|
||||
ignoredFolders
|
||||
);
|
||||
|
||||
searchSpinner.stop(
|
||||
`${chalk.green('✔')} Found ${chalk.green(
|
||||
declarations.single
|
||||
)} definition and ${usages?.occurrences.length} usages.`
|
||||
);
|
||||
|
||||
note(
|
||||
declarations.occurrences
|
||||
.map((o) =>
|
||||
o.matches
|
||||
.map(
|
||||
(m) =>
|
||||
`${o.fileName}:${m.number} ${chalk.cyan(
|
||||
'==>'
|
||||
)} ${m.content.replace(
|
||||
declarations.single,
|
||||
chalk.green(declarations.single)
|
||||
)}`
|
||||
)
|
||||
.join('\n')
|
||||
)
|
||||
.join('\n'),
|
||||
'⍜ DECLARATIONS ⍜'
|
||||
);
|
||||
|
||||
note(
|
||||
usages?.occurrences
|
||||
.map((o) =>
|
||||
o.matches.map(
|
||||
(m) =>
|
||||
`${o.fileName}:${m.number} ${chalk.cyan(
|
||||
'==>'
|
||||
)} ${m.content.replace(
|
||||
declarations.single,
|
||||
chalk.green(declarations.single)
|
||||
)}`
|
||||
)
|
||||
)
|
||||
.join('\n'),
|
||||
'⌾ USAGES ⌾'
|
||||
);
|
||||
|
||||
const usage = (await select({
|
||||
message: chalk.cyan('Expand usage:'),
|
||||
options: usages!.occurrences
|
||||
.map((o) =>
|
||||
o.matches.map((m) => ({
|
||||
value: { o, m },
|
||||
label: `${chalk.yellow(`${o.fileName}:${m.number}`)} ${chalk.cyan(
|
||||
'==>'
|
||||
)} ${m.content.replace(
|
||||
declarations.single,
|
||||
chalk.green(declarations.single)
|
||||
)}`,
|
||||
hint: `parent: ${extractFuncName(o.context.content) ?? '404'}`
|
||||
}))
|
||||
)
|
||||
.flat()
|
||||
})) as { o: Occurrence; m: any };
|
||||
|
||||
if (isCancel(usage)) process.exit(1);
|
||||
|
||||
const { stdout } = await execa('git', [
|
||||
'--no-pager',
|
||||
'grep',
|
||||
'--function-context',
|
||||
'--heading',
|
||||
'-E',
|
||||
usage.m.content.replace('(', '\\(').replace(')', '\\)'),
|
||||
usage.o.fileName
|
||||
]);
|
||||
|
||||
const mermaidSpinner = spinner();
|
||||
mermaidSpinner.start('Generating mermaid diagram...');
|
||||
const mermaid: any = await generateMermaid(stdout);
|
||||
mermaidSpinner.stop();
|
||||
if (mermaid) console.log(mermaid.mermaid);
|
||||
else note('No mermaid diagram found.');
|
||||
|
||||
const isCommitConfirmedByUser = await confirm({
|
||||
message: 'Create Excalidraw file?'
|
||||
});
|
||||
|
||||
if (isCommitConfirmedByUser) outro('created diagram.excalidraw');
|
||||
else outro('Excalidraw file not created.');
|
||||
}
|
||||
);
|
||||
@@ -35,7 +35,7 @@ export const prepareCommitMessageHook = async (
|
||||
|
||||
if (!staged) return;
|
||||
|
||||
intro('OpenCommit');
|
||||
intro('opencommit');
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ export class AzureEngine implements AiEngine {
|
||||
);
|
||||
}
|
||||
|
||||
async generateCommitMessage(
|
||||
generateCommitMessage = async (
|
||||
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
|
||||
): Promise<string | undefined> {
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const REQUEST_TOKENS = messages
|
||||
.map((msg) => tokenCount(msg.content as string) + 4)
|
||||
@@ -73,5 +73,5 @@ export class AzureEngine implements AiEngine {
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ export class OllamaAi implements AiEngine {
|
||||
stream: false
|
||||
};
|
||||
try {
|
||||
const response = await this.client.post('', params);
|
||||
const response = await this.client.post(
|
||||
this.client.getUri(this.config),
|
||||
params
|
||||
);
|
||||
|
||||
const message = response.data.message;
|
||||
|
||||
|
||||
@@ -16,18 +16,13 @@ export const assertGitRepo = async () => {
|
||||
// (file) => `:(exclude)${file}`
|
||||
// );
|
||||
|
||||
export const getIgnoredFolders = (): string[] => {
|
||||
try {
|
||||
return readFileSync('.opencommitignore').toString().split('\n');
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getOpenCommitIgnore = (): Ignore => {
|
||||
const ig = ignore();
|
||||
const ignorePatterns = getIgnoredFolders();
|
||||
ig.add(ignorePatterns);
|
||||
|
||||
try {
|
||||
ig.add(readFileSync('.opencommitignore').toString().split('\n'));
|
||||
} catch (e) {}
|
||||
|
||||
return ig;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user