@shxd/mmit 0.1.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 +84 -0
- package/bin/mmit +8 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/engine.d.ts +8 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +116 -0
- package/dist/engine.js.map +1 -0
- package/dist/git.d.ts +16 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +90 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +11 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +96 -0
- package/dist/provider.js.map +1 -0
- package/dist/providers/anthropic.d.ts +3 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +17 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +3 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +20 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +3 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +18 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +3 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +21 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/types.d.ts +10 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
```
|
|
2
|
+
█▓ █▓▄
|
|
3
|
+
██▀██▀▓▄ ██▀██▀▓▄ ▄▄▄ ██
|
|
4
|
+
██ ██ ██ ██ ██ ██ ██ ▀██▄
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
# mmit
|
|
8
|
+
|
|
9
|
+
AI-powered conventional commit message generator. Works with OpenAI, Anthropic, Gemini, and OpenRouter.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
mmit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @shxd/mmit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mmit init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This walks you through selecting a provider and API key. Alternatively, set an environment variable:
|
|
28
|
+
|
|
29
|
+
- **OpenAI** — `OPENAI_API_KEY`
|
|
30
|
+
- **Anthropic** — `ANTHROPIC_API_KEY`
|
|
31
|
+
- **Gemini** — `GEMINI_API_KEY`
|
|
32
|
+
- **OpenRouter** — `OPENROUTER_API_KEY`
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Stage your changes and run:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git add .
|
|
40
|
+
mmit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
mmit analyzes the diff, generates a conventional commit message, and lets you review, edit, or regenerate before committing.
|
|
44
|
+
|
|
45
|
+
### Multi-line bodies
|
|
46
|
+
|
|
47
|
+
When the AI generates a body explaining the change, you can choose to commit with the subject only or include the body.
|
|
48
|
+
|
|
49
|
+
### Non-interactive
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
mmit --dry-run # preview without committing
|
|
53
|
+
mmit --auto # skip the interactive prompt
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Options
|
|
57
|
+
|
|
58
|
+
- `-p, --provider <name>` — Use a specific provider (openai, anthropic, gemini, openrouter)
|
|
59
|
+
- `-m, --model <name>` — Override the default model
|
|
60
|
+
- `--dry-run` — Generate the message without committing
|
|
61
|
+
- `--auto` — Skip the interactive review prompt
|
|
62
|
+
- `--diff-only` — Print the staged diff and exit
|
|
63
|
+
- `--config` — Print the current config
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
Global: `~/.mmit.json`
|
|
68
|
+
Project: `.mmit.json` in the project root (overrides global)
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"provider": "gemini",
|
|
73
|
+
"model": "gemini-3.1-flash-lite",
|
|
74
|
+
"commitTypes": ["feat", "fix", "chore", "refactor", "docs", "style", "test", "perf", "ci", "build", "revert"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How it works
|
|
79
|
+
|
|
80
|
+
1. mmit reads your staged git diff
|
|
81
|
+
2. Sends it to the configured AI provider with a conventional commits prompt
|
|
82
|
+
3. Parses the response into a subject and optional body
|
|
83
|
+
4. Presents the message for review, editing, or regeneration
|
|
84
|
+
5. Commits when you confirm
|
package/bin/mmit
ADDED
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
provider?: string;
|
|
3
|
+
model?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
maxDiffTokens?: number;
|
|
6
|
+
commitTypes?: string[];
|
|
7
|
+
autoConfirm?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function loadConfig(): Config;
|
|
10
|
+
export declare function saveGlobalConfig(config: Config): void;
|
|
11
|
+
declare const ENV_KEY_MAP: Record<string, string>;
|
|
12
|
+
export declare function detectProviderFromEnv(preferred?: string): string | null;
|
|
13
|
+
export { ENV_KEY_MAP as envKeyMap };
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,MAAM;IAErB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAgCD,wBAAgB,UAAU,IAAI,MAAM,CAWnC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,QAAA,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKvC,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASvE;AAED,OAAO,EAAE,WAAW,IAAI,SAAS,EAAE,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
const GLOBAL_CONFIG_PATH = join(homedir(), '.mmit.json');
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
maxDiffTokens: 8000,
|
|
7
|
+
commitTypes: [
|
|
8
|
+
'feat', 'fix', 'chore', 'docs', 'refactor',
|
|
9
|
+
'test', 'style', 'perf', 'ci', 'build', 'revert',
|
|
10
|
+
],
|
|
11
|
+
autoConfirm: false,
|
|
12
|
+
};
|
|
13
|
+
function findLocalConfig() {
|
|
14
|
+
let dir = process.cwd();
|
|
15
|
+
while (true) {
|
|
16
|
+
const file = join(dir, '.mmit.json');
|
|
17
|
+
if (existsSync(file)) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(file, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const parent = resolve(dir, '..');
|
|
26
|
+
if (parent === dir)
|
|
27
|
+
return null;
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let cached = null;
|
|
32
|
+
export function loadConfig() {
|
|
33
|
+
if (cached)
|
|
34
|
+
return cached;
|
|
35
|
+
const global = existsSync(GLOBAL_CONFIG_PATH)
|
|
36
|
+
? JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, 'utf-8'))
|
|
37
|
+
: {};
|
|
38
|
+
const local = findLocalConfig();
|
|
39
|
+
cached = { ...DEFAULT_CONFIG, ...global, ...local };
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
export function saveGlobalConfig(config) {
|
|
43
|
+
mkdirSync(homedir(), { recursive: true });
|
|
44
|
+
writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
45
|
+
cached = config;
|
|
46
|
+
}
|
|
47
|
+
const ENV_KEY_MAP = {
|
|
48
|
+
openai: 'OPENAI_API_KEY',
|
|
49
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
50
|
+
gemini: 'GEMINI_API_KEY',
|
|
51
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
52
|
+
};
|
|
53
|
+
export function detectProviderFromEnv(preferred) {
|
|
54
|
+
const order = preferred && ENV_KEY_MAP[preferred]
|
|
55
|
+
? [preferred, ...Object.keys(ENV_KEY_MAP).filter(k => k !== preferred)]
|
|
56
|
+
: Object.keys(ENV_KEY_MAP);
|
|
57
|
+
for (const name of order) {
|
|
58
|
+
if (process.env[ENV_KEY_MAP[name]])
|
|
59
|
+
return name;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
export { ENV_KEY_MAP as envKeyMap };
|
|
64
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface GeneratedMessage {
|
|
2
|
+
subject: string;
|
|
3
|
+
body?: string;
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function generateCommitMessage(diff: string, truncated: boolean, overrideProvider?: string, overrideModel?: string): Promise<GeneratedMessage>;
|
|
8
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAyED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,gBAAgB,CAAC,CA4D3B"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { providers } from './provider.js';
|
|
2
|
+
import { loadConfig, detectProviderFromEnv, envKeyMap } from './config.js';
|
|
3
|
+
const COMMIT_PATTERN = /^[a-zA-Z]+(\([a-zA-Z0-9_.\-,/]+\))?!?:\s.+/;
|
|
4
|
+
function isValidCommitMessage(msg) {
|
|
5
|
+
return COMMIT_PATTERN.test(msg) && msg.length <= 100;
|
|
6
|
+
}
|
|
7
|
+
function splitSubjectBody(raw) {
|
|
8
|
+
const cleaned = raw
|
|
9
|
+
.replace(/^```[\w]*\n?/gm, '')
|
|
10
|
+
.replace(/```$/gm, '')
|
|
11
|
+
.replace(/^['"]|['"]$/g, '')
|
|
12
|
+
.trim();
|
|
13
|
+
const lines = cleaned.split('\n');
|
|
14
|
+
const subject = lines[0].trim();
|
|
15
|
+
const rest = lines.slice(1).map(l => l.trim()).filter(Boolean).join('\n');
|
|
16
|
+
return { subject, body: rest || undefined };
|
|
17
|
+
}
|
|
18
|
+
function buildPrompt(diff, commitTypes, truncated, strict = false) {
|
|
19
|
+
const types = commitTypes.join(', ');
|
|
20
|
+
const strictRule = strict
|
|
21
|
+
? '\nCRITICAL: Respond with ONLY the first line of the commit message. No body, no explanations.'
|
|
22
|
+
: '\nYou may optionally include a body paragraph explaining the change.';
|
|
23
|
+
return `Generate a conventional commit message for the following git diff.
|
|
24
|
+
|
|
25
|
+
Commit types available: ${types}
|
|
26
|
+
|
|
27
|
+
Format:
|
|
28
|
+
<type>(<scope>): <description>
|
|
29
|
+
|
|
30
|
+
<optional body — explain why, not how>
|
|
31
|
+
|
|
32
|
+
<optional footers>
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
- feat(api): add user authentication endpoint
|
|
37
|
+
|
|
38
|
+
- Required for mobile clients that need long-lived sessions
|
|
39
|
+
- Prevents abuse of the auth endpoint under heavy load
|
|
40
|
+
Closes #142
|
|
41
|
+
|
|
42
|
+
- fix(parser): handle null input gracefully
|
|
43
|
+
|
|
44
|
+
- Null values from the upstream API were crashing the parser
|
|
45
|
+
Co-authored-by: Alice <alice@example.com>
|
|
46
|
+
|
|
47
|
+
- feat(api)!: remove deprecated /v2/users endpoint
|
|
48
|
+
|
|
49
|
+
BREAKING CHANGE: The /v2/users endpoint has been removed. Use /v3/users instead.
|
|
50
|
+
|
|
51
|
+
Rules:
|
|
52
|
+
|
|
53
|
+
- Use the imperative mood ("add" not "added" / "adds")
|
|
54
|
+
- Subject line max 72 characters
|
|
55
|
+
- Scope is optional — infer from the files changed
|
|
56
|
+
- Always separate the subject and body with a blank line
|
|
57
|
+
- Write the body as bullet points (each prefixed with "-")
|
|
58
|
+
- Use the body to explain WHY the change was made, not WHAT (the diff already shows the what)
|
|
59
|
+
- Use footers for issue references (Closes, Refs), breaking changes (BREAKING CHANGE:), and co-authors
|
|
60
|
+
- For breaking changes, add "!" after the type/scope AND optionally a BREAKING CHANGE footer
|
|
61
|
+
- Respond with only the commit message — no intro, no explanation${strictRule}
|
|
62
|
+
|
|
63
|
+
${truncated ? '(Note: the diff was truncated due to size. Generate a message for what is visible.)\n' : ''}
|
|
64
|
+
Diff:
|
|
65
|
+
${diff}`;
|
|
66
|
+
}
|
|
67
|
+
export async function generateCommitMessage(diff, truncated, overrideProvider, overrideModel) {
|
|
68
|
+
const config = loadConfig();
|
|
69
|
+
// Determine provider and model
|
|
70
|
+
const providerName = overrideProvider
|
|
71
|
+
|| config.provider
|
|
72
|
+
|| detectProviderFromEnv(config.provider)
|
|
73
|
+
|| 'openai';
|
|
74
|
+
const provider = providers[providerName];
|
|
75
|
+
if (!provider) {
|
|
76
|
+
throw new Error(`Unknown provider "${providerName}". Available: ${Object.keys(providers).join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
const apiKey = process.env[envKeyMap[providerName]] || config.apiKey;
|
|
79
|
+
if (!apiKey) {
|
|
80
|
+
throw new Error(`Missing ${envKeyMap[providerName]} for provider "${providerName}". Run \`mmit init\` to set one up.`);
|
|
81
|
+
}
|
|
82
|
+
// Determine model with good defaults
|
|
83
|
+
const defaultModels = {
|
|
84
|
+
openai: 'gpt-4o-mini',
|
|
85
|
+
anthropic: 'claude-sonnet-4-20250514',
|
|
86
|
+
gemini: 'gemini-3.1-flash-lite',
|
|
87
|
+
openrouter: 'openrouter/free',
|
|
88
|
+
};
|
|
89
|
+
const model = overrideModel || config.model || defaultModels[providerName];
|
|
90
|
+
const providerConfig = {
|
|
91
|
+
apiKey,
|
|
92
|
+
model,
|
|
93
|
+
maxTokens: 500,
|
|
94
|
+
};
|
|
95
|
+
// Attempt 1: normal prompt
|
|
96
|
+
let prompt = buildPrompt(diff, config.commitTypes ?? [], truncated);
|
|
97
|
+
let raw = await provider.generate(prompt, providerConfig);
|
|
98
|
+
let parsed = splitSubjectBody(raw);
|
|
99
|
+
// Attempt 2: stricter prompt if model rambled
|
|
100
|
+
if (!isValidCommitMessage(parsed.subject)) {
|
|
101
|
+
prompt = buildPrompt(diff, config.commitTypes ?? [], truncated, true);
|
|
102
|
+
raw = await provider.generate(prompt, providerConfig);
|
|
103
|
+
parsed = splitSubjectBody(raw);
|
|
104
|
+
}
|
|
105
|
+
// Final check
|
|
106
|
+
if (!isValidCommitMessage(parsed.subject)) {
|
|
107
|
+
throw new Error(`Model returned an invalid response. Try a different model.\n Got: ${parsed.subject}`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
subject: parsed.subject,
|
|
111
|
+
body: parsed.body,
|
|
112
|
+
provider: providerName,
|
|
113
|
+
model,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface DiffResult {
|
|
2
|
+
staged: boolean;
|
|
3
|
+
diff: string;
|
|
4
|
+
truncated: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface DiffStats {
|
|
7
|
+
files: number;
|
|
8
|
+
insertions: number;
|
|
9
|
+
deletions: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function isGitRepo(): boolean;
|
|
12
|
+
export declare function getGitDiff(maxTokens?: number): DiffResult;
|
|
13
|
+
export declare function stageAllAndDiff(): DiffResult;
|
|
14
|
+
export declare function getDiffStats(): DiffStats;
|
|
15
|
+
export declare function createCommit(subject: string, body?: string): string;
|
|
16
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAUD,wBAAgB,UAAU,CAAC,SAAS,GAAE,MAAa,GAAG,UAAU,CA6B/D;AAoBD,wBAAgB,eAAe,IAAI,UAAU,CAG5C;AAED,wBAAgB,YAAY,IAAI,SAAS,CAcxC;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAUnE"}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
const GIT_OPTS = { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: 'pipe' };
|
|
3
|
+
export function isGitRepo() {
|
|
4
|
+
try {
|
|
5
|
+
execSync('git rev-parse --git-dir', { stdio: 'pipe', encoding: 'utf-8' });
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function git(args) {
|
|
13
|
+
try {
|
|
14
|
+
return execSync(`git ${args}`, GIT_OPTS).trim();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function getGitDiff(maxTokens = 8000) {
|
|
21
|
+
if (!isGitRepo()) {
|
|
22
|
+
return { staged: false, diff: '', truncated: false };
|
|
23
|
+
}
|
|
24
|
+
let diff = git('diff --cached');
|
|
25
|
+
let staged = !!diff;
|
|
26
|
+
if (!diff) {
|
|
27
|
+
diff = git('diff HEAD');
|
|
28
|
+
}
|
|
29
|
+
if (!diff) {
|
|
30
|
+
diff = git('diff');
|
|
31
|
+
}
|
|
32
|
+
if (!diff) {
|
|
33
|
+
return { staged: false, diff: '', truncated: false };
|
|
34
|
+
}
|
|
35
|
+
const estTokens = Math.ceil(diff.length / 4);
|
|
36
|
+
let truncated = false;
|
|
37
|
+
if (estTokens > maxTokens) {
|
|
38
|
+
diff = truncateDiff(diff, maxTokens);
|
|
39
|
+
truncated = true;
|
|
40
|
+
}
|
|
41
|
+
return { staged, diff, truncated };
|
|
42
|
+
}
|
|
43
|
+
function truncateDiff(diff, maxTokens) {
|
|
44
|
+
const chars = maxTokens * 4;
|
|
45
|
+
const lines = diff.split('\n');
|
|
46
|
+
const kept = [];
|
|
47
|
+
let length = 0;
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const next = length + line.length + 1;
|
|
50
|
+
if (next > chars)
|
|
51
|
+
break;
|
|
52
|
+
kept.push(line);
|
|
53
|
+
length = next;
|
|
54
|
+
}
|
|
55
|
+
kept.push('# ... diff truncated due to size ...');
|
|
56
|
+
return kept.join('\n');
|
|
57
|
+
}
|
|
58
|
+
export function stageAllAndDiff() {
|
|
59
|
+
git('add -A');
|
|
60
|
+
return getGitDiff();
|
|
61
|
+
}
|
|
62
|
+
export function getDiffStats() {
|
|
63
|
+
const staged = !!git('diff --cached --stat');
|
|
64
|
+
const numstat = staged ? git('diff --cached --numstat') : git('diff HEAD --numstat') || git('diff --numstat');
|
|
65
|
+
if (!numstat)
|
|
66
|
+
return { files: 0, insertions: 0, deletions: 0 };
|
|
67
|
+
const lines = numstat.split('\n').filter(Boolean);
|
|
68
|
+
let files = 0, insertions = 0, deletions = 0;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const [add, del] = line.split('\t');
|
|
71
|
+
if (add && add !== '-')
|
|
72
|
+
insertions += parseInt(add, 10);
|
|
73
|
+
if (del && del !== '-')
|
|
74
|
+
deletions += parseInt(del, 10);
|
|
75
|
+
files++;
|
|
76
|
+
}
|
|
77
|
+
return { files, insertions, deletions };
|
|
78
|
+
}
|
|
79
|
+
export function createCommit(subject, body) {
|
|
80
|
+
const esc = (s) => s.replace(/["`$\\]/g, '\\$&');
|
|
81
|
+
const cmd = body
|
|
82
|
+
? `git commit -m "${esc(subject)}" -m "${esc(body)}"`
|
|
83
|
+
: `git commit -m "${esc(subject)}"`;
|
|
84
|
+
execSync(cmd, {
|
|
85
|
+
stdio: 'inherit',
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
});
|
|
88
|
+
return git('rev-parse --short HEAD');
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA0BA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA4NzC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import pico from 'picocolors';
|
|
5
|
+
import { loadConfig, saveGlobalConfig, detectProviderFromEnv } from './config.js';
|
|
6
|
+
import { generateCommitMessage } from './engine.js';
|
|
7
|
+
import { getGitDiff, stageAllAndDiff, createCommit, getDiffStats, isGitRepo } from './git.js';
|
|
8
|
+
const LOGO = `\
|
|
9
|
+
█▓ █▓▄
|
|
10
|
+
██▀██▀▓▄ ██▀██▀▓▄ ▄▄▄ ██
|
|
11
|
+
██ ██ ██ ██ ██ ██ ██ ▀██▄`;
|
|
12
|
+
function renderHeader(version) {
|
|
13
|
+
const lines = LOGO.split('\n');
|
|
14
|
+
const maxWidth = Math.max(...lines.map(l => l.length));
|
|
15
|
+
const v = pico.dim(`v${version}`);
|
|
16
|
+
return pico.dim(lines.map((l, i) => {
|
|
17
|
+
if (i === 0)
|
|
18
|
+
return l + ' '.repeat(maxWidth - l.length + 2) + v;
|
|
19
|
+
return l;
|
|
20
|
+
}).join('\n'));
|
|
21
|
+
}
|
|
22
|
+
const VERSION = '0.1.0';
|
|
23
|
+
export async function run() {
|
|
24
|
+
// Handle `mmit init` before commander parsing
|
|
25
|
+
if (process.argv.includes('init')) {
|
|
26
|
+
await handleInit();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!isGitRepo()) {
|
|
30
|
+
process.stderr.write(renderHeader(VERSION) + '\n');
|
|
31
|
+
p.intro('');
|
|
32
|
+
p.outro(pico.red('Not a git repository'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
program
|
|
36
|
+
.name('mmit')
|
|
37
|
+
.description('AI-powered git commit message generator')
|
|
38
|
+
.version(VERSION)
|
|
39
|
+
.option('-p, --provider <name>', 'AI provider (openai, anthropic, gemini, openrouter)')
|
|
40
|
+
.option('-m, --model <name>', 'Model name override')
|
|
41
|
+
.option('-d, --diff-only', 'Print diff and exit')
|
|
42
|
+
.option('--dry-run', 'Generate message but do not commit')
|
|
43
|
+
.option('--config', 'Open global config for editing')
|
|
44
|
+
.option('--auto', 'Auto-confirm without interactive prompt');
|
|
45
|
+
program.parse(process.argv);
|
|
46
|
+
const opts = program.opts();
|
|
47
|
+
// Handle --config
|
|
48
|
+
if (opts.config) {
|
|
49
|
+
const config = loadConfig();
|
|
50
|
+
if (config.apiKey) {
|
|
51
|
+
const key = config.apiKey;
|
|
52
|
+
const prefix = key.slice(0, 4);
|
|
53
|
+
const suffix = key.slice(-4);
|
|
54
|
+
config.apiKey = `${prefix}...${suffix}`;
|
|
55
|
+
}
|
|
56
|
+
console.log(JSON.stringify(config, null, 2));
|
|
57
|
+
console.error('\n Global config: ~/.mmit.json');
|
|
58
|
+
console.error(' Local config: .mmit.json in project root (overrides global)');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Handle --diff-only
|
|
62
|
+
if (opts.diffOnly) {
|
|
63
|
+
const { diff } = getGitDiff();
|
|
64
|
+
console.log(diff || '(no diff)');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const provider = opts.provider || loadConfig().provider || detectProviderFromEnv();
|
|
68
|
+
const model = opts.model;
|
|
69
|
+
process.stderr.write(renderHeader(VERSION) + '\n');
|
|
70
|
+
p.intro('');
|
|
71
|
+
const diffResult = getGitDiff();
|
|
72
|
+
let diff = diffResult.diff;
|
|
73
|
+
let staged = diffResult.staged;
|
|
74
|
+
if (!diff) {
|
|
75
|
+
const shouldStage = await p.confirm({
|
|
76
|
+
message: 'No staged changes. Stage all and proceed?',
|
|
77
|
+
initialValue: true,
|
|
78
|
+
});
|
|
79
|
+
if (p.isCancel(shouldStage) || !shouldStage) {
|
|
80
|
+
p.outro('Cancelled.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const result = stageAllAndDiff();
|
|
84
|
+
diff = result.diff;
|
|
85
|
+
staged = result.staged;
|
|
86
|
+
if (!diff) {
|
|
87
|
+
p.outro(pico.red('No diff to commit.'));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Detect if we need a provider setup
|
|
92
|
+
if (!provider) {
|
|
93
|
+
p.log.warn('No API key found.');
|
|
94
|
+
const runInit = await p.confirm({
|
|
95
|
+
message: 'Run `mmit init` to set one up?',
|
|
96
|
+
initialValue: true,
|
|
97
|
+
});
|
|
98
|
+
if (p.isCancel(runInit) || !runInit) {
|
|
99
|
+
p.outro('Cancelled.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await handleInit();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const diffStats = getDiffStats();
|
|
106
|
+
const statsLine = `${diffStats.files} file${diffStats.files !== 1 ? 's' : ''} changed`;
|
|
107
|
+
p.log.step(`${pico.dim(statsLine)}, ${pico.green(`+${diffStats.insertions}`)} ${pico.red(`-${diffStats.deletions}`)}`);
|
|
108
|
+
const spin = p.spinner();
|
|
109
|
+
spin.start('Generating commit message...');
|
|
110
|
+
let msg;
|
|
111
|
+
try {
|
|
112
|
+
msg = await generateCommitMessage(diff, diffResult.truncated, provider, model);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
spin.stop('Error');
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
p.outro(pico.red(`Generation failed: ${message}`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
spin.stop('Done');
|
|
122
|
+
p.log.step(pico.dim(`${msg.provider} · ${msg.model}`));
|
|
123
|
+
// Interactive loop
|
|
124
|
+
while (true) {
|
|
125
|
+
p.log.step(msg.subject);
|
|
126
|
+
if (msg.body) {
|
|
127
|
+
const bullets = msg.body
|
|
128
|
+
.split('\n')
|
|
129
|
+
.map(l => ` ${l.replace(/^-\s*/, '• ')}`)
|
|
130
|
+
.join('\n');
|
|
131
|
+
p.log.message(bullets);
|
|
132
|
+
}
|
|
133
|
+
if (opts.dryRun || opts.auto || loadConfig().autoConfirm) {
|
|
134
|
+
if (opts.dryRun) {
|
|
135
|
+
console.log(msg.body ? `${msg.subject}\n\n${msg.body}` : msg.subject);
|
|
136
|
+
p.outro('Dry-run — not committing.');
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const hash = createCommit(msg.subject, msg.body);
|
|
140
|
+
p.outro(pico.green(`Committed as ${hash} (${statsLine}, ${pico.green(`+${diffStats.insertions}`)} ${pico.red(`-${diffStats.deletions}`)})`));
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
const action = await p.select({
|
|
145
|
+
message: 'What do you want to do?',
|
|
146
|
+
options: [
|
|
147
|
+
{ value: 'subject', label: 'Commit (subject only)', hint: 'first line only' },
|
|
148
|
+
...(msg.body ? [{ value: 'body', label: 'Commit (subject + body)', hint: 'includes body' }] : []),
|
|
149
|
+
{ value: 'edit', label: 'Edit', hint: 'edit the message manually' },
|
|
150
|
+
{ value: 'regenerate', label: 'Regenerate', hint: 'generate a new message' },
|
|
151
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
if (p.isCancel(action) || action === 'cancel') {
|
|
155
|
+
p.outro('Cancelled.');
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (action === 'subject') {
|
|
159
|
+
const hash = createCommit(msg.subject, undefined);
|
|
160
|
+
p.outro(pico.green(`Committed as ${hash} (${statsLine}, ${pico.green(`+${diffStats.insertions}`)} ${pico.red(`-${diffStats.deletions}`)})`));
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (action === 'body') {
|
|
164
|
+
const hash = createCommit(msg.subject, msg.body);
|
|
165
|
+
p.outro(pico.green(`Committed as ${hash} (${statsLine}, ${pico.green(`+${diffStats.insertions}`)} ${pico.red(`-${diffStats.deletions}`)})`));
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (action === 'regenerate') {
|
|
169
|
+
spin.start('Regenerating...');
|
|
170
|
+
try {
|
|
171
|
+
msg = await generateCommitMessage(diff, diffResult.truncated, provider, model);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
spin.stop('Error');
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
p.outro(pico.red(`Generation failed: ${message}`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
spin.stop('Done');
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (action === 'edit') {
|
|
183
|
+
const edited = await p.text({
|
|
184
|
+
message: 'Edit the commit message',
|
|
185
|
+
initialValue: msg.subject,
|
|
186
|
+
validate: (val) => {
|
|
187
|
+
if (!val.trim())
|
|
188
|
+
return 'Message cannot be empty';
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (p.isCancel(edited)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
msg.subject = edited.trim();
|
|
195
|
+
p.log.step(msg.subject);
|
|
196
|
+
const confirmEdit = await p.confirm({
|
|
197
|
+
message: 'Commit with this message?',
|
|
198
|
+
initialValue: true,
|
|
199
|
+
});
|
|
200
|
+
if (p.isCancel(confirmEdit)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (confirmEdit) {
|
|
204
|
+
const hash = createCommit(msg.subject, msg.body);
|
|
205
|
+
p.outro(pico.green(`Committed as ${hash} (${statsLine}, ${pico.green(`+${diffStats.insertions}`)} ${pico.red(`-${diffStats.deletions}`)})`));
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const PROVIDER_INFO = {
|
|
213
|
+
openai: { env: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
|
|
214
|
+
anthropic: { env: 'ANTHROPIC_API_KEY', defaultModel: 'claude-sonnet-4-20250514' },
|
|
215
|
+
gemini: { env: 'GEMINI_API_KEY', defaultModel: 'gemini-3.1-flash-lite' },
|
|
216
|
+
openrouter: { env: 'OPENROUTER_API_KEY', defaultModel: 'openrouter/free' },
|
|
217
|
+
};
|
|
218
|
+
async function handleInit() {
|
|
219
|
+
process.stderr.write(renderHeader(VERSION) + '\n');
|
|
220
|
+
p.intro('');
|
|
221
|
+
const provider = await p.select({
|
|
222
|
+
message: 'AI provider',
|
|
223
|
+
options: Object.entries(PROVIDER_INFO).map(([name, info]) => ({
|
|
224
|
+
value: name,
|
|
225
|
+
label: name,
|
|
226
|
+
hint: process.env[info.env] ? pico.green('✓ env var set') : `env: ${info.env}`,
|
|
227
|
+
})),
|
|
228
|
+
});
|
|
229
|
+
if (p.isCancel(provider)) {
|
|
230
|
+
p.outro('Cancelled.');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const info = PROVIDER_INFO[provider];
|
|
234
|
+
if (!info)
|
|
235
|
+
return;
|
|
236
|
+
let apiKey = process.env[info.env] || '';
|
|
237
|
+
if (!apiKey) {
|
|
238
|
+
const input = await p.password({
|
|
239
|
+
message: `Paste your ${info.env} API key`,
|
|
240
|
+
validate: (val) => {
|
|
241
|
+
if (!val.trim())
|
|
242
|
+
return 'API key cannot be empty';
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
if (p.isCancel(input)) {
|
|
246
|
+
p.outro('Cancelled.');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
apiKey = input.trim();
|
|
250
|
+
}
|
|
251
|
+
const model = await p.text({
|
|
252
|
+
message: `Default model for ${provider}`,
|
|
253
|
+
initialValue: info.defaultModel,
|
|
254
|
+
validate: (val) => {
|
|
255
|
+
if (!val.trim())
|
|
256
|
+
return 'Model name cannot be empty';
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
if (p.isCancel(model))
|
|
260
|
+
return;
|
|
261
|
+
const config = loadConfig();
|
|
262
|
+
config.provider = provider;
|
|
263
|
+
config.model = model.trim();
|
|
264
|
+
if (apiKey && !process.env[info.env]) {
|
|
265
|
+
config.apiKey = apiKey;
|
|
266
|
+
}
|
|
267
|
+
saveGlobalConfig(config);
|
|
268
|
+
p.outro(pico.green(`Config saved to ~/.mmit.json`));
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ProviderConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
model: string;
|
|
4
|
+
maxTokens?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Provider {
|
|
7
|
+
name: string;
|
|
8
|
+
generate(prompt: string, config: ProviderConfig): Promise<string>;
|
|
9
|
+
}
|
|
10
|
+
export declare const providers: Record<string, Provider>;
|
|
11
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE;AAsGD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAK9C,CAAC"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
4
|
+
const systemPrompt = 'You are a git commit message generator. Respond only with the commit message, no extra text.';
|
|
5
|
+
const openaiProvider = {
|
|
6
|
+
name: 'openai',
|
|
7
|
+
async generate(prompt, config) {
|
|
8
|
+
const client = new OpenAI({ apiKey: config.apiKey });
|
|
9
|
+
const res = await client.chat.completions.create({
|
|
10
|
+
model: config.model,
|
|
11
|
+
messages: [
|
|
12
|
+
{ role: 'system', content: systemPrompt },
|
|
13
|
+
{ role: 'user', content: prompt },
|
|
14
|
+
],
|
|
15
|
+
temperature: 0.3,
|
|
16
|
+
max_tokens: config.maxTokens ?? 300,
|
|
17
|
+
});
|
|
18
|
+
return res.choices[0]?.message?.content?.trim() ?? '';
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
const anthropicProvider = {
|
|
22
|
+
name: 'anthropic',
|
|
23
|
+
async generate(prompt, config) {
|
|
24
|
+
const client = new Anthropic({ apiKey: config.apiKey });
|
|
25
|
+
const res = await client.messages.create({
|
|
26
|
+
model: config.model,
|
|
27
|
+
system: systemPrompt,
|
|
28
|
+
messages: [{ role: 'user', content: prompt }],
|
|
29
|
+
temperature: 0.3,
|
|
30
|
+
max_tokens: config.maxTokens ?? 300,
|
|
31
|
+
});
|
|
32
|
+
const block = res.content[0];
|
|
33
|
+
return block?.type === 'text' ? block.text.trim() : '';
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const geminiProvider = {
|
|
37
|
+
name: 'gemini',
|
|
38
|
+
async generate(prompt, config) {
|
|
39
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
40
|
+
const model = genAI.getGenerativeModel({
|
|
41
|
+
model: config.model,
|
|
42
|
+
systemInstruction: systemPrompt,
|
|
43
|
+
});
|
|
44
|
+
const res = await model.generateContent({
|
|
45
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
46
|
+
generationConfig: {
|
|
47
|
+
temperature: 0.3,
|
|
48
|
+
maxOutputTokens: config.maxTokens ?? 300,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return res.response.text().trim();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const openrouterProvider = {
|
|
55
|
+
name: 'openrouter',
|
|
56
|
+
async generate(prompt, config) {
|
|
57
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'HTTP-Referer': 'https://github.com/mmit',
|
|
63
|
+
'X-OpenRouter-Title': 'mmit',
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
model: config.model,
|
|
67
|
+
messages: [
|
|
68
|
+
{ role: 'system', content: systemPrompt },
|
|
69
|
+
{ role: 'user', content: prompt },
|
|
70
|
+
],
|
|
71
|
+
temperature: 0.3,
|
|
72
|
+
max_tokens: config.maxTokens ?? 300,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
const body = await res.text().catch(() => '');
|
|
77
|
+
throw new Error(`OpenRouter returned ${res.status}: ${body || res.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
if (data.error) {
|
|
81
|
+
throw new Error(`OpenRouter error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
82
|
+
}
|
|
83
|
+
const choice = data.choices?.[0];
|
|
84
|
+
if (choice?.finish_reason === 'error') {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
return choice?.message?.content?.trim() ?? '';
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
export const providers = {
|
|
91
|
+
openai: openaiProvider,
|
|
92
|
+
anthropic: anthropicProvider,
|
|
93
|
+
gemini: geminiProvider,
|
|
94
|
+
openrouter: openrouterProvider,
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,iBAAiB,EAAE,QAiB/B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
export const anthropicProvider = {
|
|
3
|
+
name: 'anthropic',
|
|
4
|
+
async generate(prompt, config) {
|
|
5
|
+
const client = new Anthropic({ apiKey: config.apiKey });
|
|
6
|
+
const res = await client.messages.create({
|
|
7
|
+
model: config.model,
|
|
8
|
+
system: 'You are a git commit message generator. Respond only with the commit message, no extra text.',
|
|
9
|
+
messages: [{ role: 'user', content: prompt }],
|
|
10
|
+
temperature: 0.3,
|
|
11
|
+
max_tokens: config.maxTokens ?? 300,
|
|
12
|
+
});
|
|
13
|
+
const block = res.content[0];
|
|
14
|
+
return block?.type === 'text' ? block.text.trim() : '';
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAG1C,MAAM,CAAC,MAAM,iBAAiB,GAAa;IACzC,IAAI,EAAE,WAAW;IAEjB,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAsB;QACnD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAExD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACvC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,8FAA8F;YACtG,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC7C,WAAW,EAAE,GAAG;YAChB,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,GAAG;SACpC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,KAAK,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,cAAc,EAAE,QAoB5B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
export const geminiProvider = {
|
|
3
|
+
name: 'gemini',
|
|
4
|
+
async generate(prompt, config) {
|
|
5
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
6
|
+
const model = genAI.getGenerativeModel({
|
|
7
|
+
model: config.model,
|
|
8
|
+
systemInstruction: 'You are a git commit message generator. Respond only with the commit message, no extra text.',
|
|
9
|
+
});
|
|
10
|
+
const res = await model.generateContent({
|
|
11
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
12
|
+
generationConfig: {
|
|
13
|
+
temperature: 0.3,
|
|
14
|
+
maxOutputTokens: config.maxTokens ?? 300,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
return res.response.text().trim();
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,CAAC,MAAM,cAAc,GAAa;IACtC,IAAI,EAAE,QAAQ;IAEd,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAsB;QACnD,MAAM,KAAK,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,KAAK,CAAC,kBAAkB,CAAC;YACrC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,iBAAiB,EAAE,8FAA8F;SAClH,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC;YACtC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YACvD,gBAAgB,EAAE;gBAChB,WAAW,EAAE,GAAG;gBAChB,eAAe,EAAE,MAAM,CAAC,SAAS,IAAI,GAAG;aACzC;SACF,CAAC,CAAC;QAEH,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;IACpC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAK9C,CAAC;AAEF,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { openaiProvider } from './openai.js';
|
|
2
|
+
import { anthropicProvider } from './anthropic.js';
|
|
3
|
+
import { geminiProvider } from './gemini.js';
|
|
4
|
+
import { openrouterProvider } from './openrouter.js';
|
|
5
|
+
export const providers = {
|
|
6
|
+
openai: openaiProvider,
|
|
7
|
+
anthropic: anthropicProvider,
|
|
8
|
+
gemini: geminiProvider,
|
|
9
|
+
openrouter: openrouterProvider,
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,CAAC,MAAM,SAAS,GAA6B;IACjD,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,iBAAiB;IAC5B,MAAM,EAAE,cAAc;IACtB,UAAU,EAAE,kBAAkB;CAC/B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,cAAc,EAAE,QAkB5B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
export const openaiProvider = {
|
|
3
|
+
name: 'openai',
|
|
4
|
+
async generate(prompt, config) {
|
|
5
|
+
const client = new OpenAI({ apiKey: config.apiKey });
|
|
6
|
+
const res = await client.chat.completions.create({
|
|
7
|
+
model: config.model,
|
|
8
|
+
messages: [
|
|
9
|
+
{ role: 'system', content: 'You are a git commit message generator. Respond only with the commit message, no extra text.' },
|
|
10
|
+
{ role: 'user', content: prompt },
|
|
11
|
+
],
|
|
12
|
+
temperature: 0.3,
|
|
13
|
+
max_tokens: config.maxTokens ?? 300,
|
|
14
|
+
});
|
|
15
|
+
return res.choices[0]?.message?.content?.trim() ?? '';
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,MAAM,CAAC,MAAM,cAAc,GAAa;IACtC,IAAI,EAAE,QAAQ;IAEd,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAsB;QACnD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAErD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YAC/C,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,8FAA8F,EAAE;gBAC3H,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;aAClC;YACD,WAAW,EAAE,GAAG;YAChB,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,GAAG;SACpC,CAAC,CAAC;QAEH,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../src/providers/openrouter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,kBAAkB,EAAE,QAqBhC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
export const openrouterProvider = {
|
|
3
|
+
name: 'openrouter',
|
|
4
|
+
async generate(prompt, config) {
|
|
5
|
+
const client = new OpenAI({
|
|
6
|
+
apiKey: config.apiKey,
|
|
7
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
8
|
+
});
|
|
9
|
+
const res = await client.chat.completions.create({
|
|
10
|
+
model: config.model,
|
|
11
|
+
messages: [
|
|
12
|
+
{ role: 'system', content: 'You are a git commit message generator. Respond only with the commit message, no extra text.' },
|
|
13
|
+
{ role: 'user', content: prompt },
|
|
14
|
+
],
|
|
15
|
+
temperature: 0.3,
|
|
16
|
+
max_tokens: config.maxTokens ?? 300,
|
|
17
|
+
});
|
|
18
|
+
return res.choices[0]?.message?.content?.trim() ?? '';
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=openrouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../src/providers/openrouter.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,IAAI,EAAE,YAAY;IAElB,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,MAAsB;QACnD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;YACxB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,8BAA8B;SACxC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YAC/C,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,8FAA8F,EAAE;gBAC3H,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE;aAClC;YACD,WAAW,EAAE,GAAG;YAChB,UAAU,EAAE,MAAM,CAAC,SAAS,IAAI,GAAG;SACpC,CAAC,CAAC;QAEH,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shxd/mmit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered git commit message generator",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mmit": "./bin/mmit"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@anthropic-ai/sdk": "^0.32.0",
|
|
15
|
+
"@clack/prompts": "^0.7.0",
|
|
16
|
+
"@google/generative-ai": "^0.21.0",
|
|
17
|
+
"commander": "^12.0.0",
|
|
18
|
+
"openai": "^4.73.0",
|
|
19
|
+
"picocolors": "^1.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.6.0"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"start": "node bin/mmit"
|
|
29
|
+
}
|
|
30
|
+
}
|