diffscribe 1.0.1
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 +161 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.js +56 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +29 -0
- package/dist/prompts.js.map +1 -0
- package/dist/service.d.ts +13 -0
- package/dist/service.js +180 -0
- package/dist/service.js.map +1 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +3 -0
- package/dist/test.js.map +1 -0
- package/dist/ui.d.ts +4 -0
- package/dist/ui.js +30 -0
- package/dist/ui.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# diffscribe
|
|
2
|
+
|
|
3
|
+
AI-powered commit message generator for Conventional Commits.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Conventional Commits compliance
|
|
8
|
+
- ✅ Concise and detailed modes
|
|
9
|
+
- ✅ Preview before committing
|
|
10
|
+
- ✅ Automatic clipboard copy
|
|
11
|
+
- ✅ Regeneration loop
|
|
12
|
+
- ✅ Smart model selection (free draft + paid refinement for long diffs)
|
|
13
|
+
- ✅ Mock mode for testing
|
|
14
|
+
- ✅ Error handling with clear messages
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
1. Reads your staged Git diffs
|
|
19
|
+
2. Generates a Conventional Commit message using AI
|
|
20
|
+
3. Preview and accept/reject
|
|
21
|
+
4. Copies to clipboard on accept
|
|
22
|
+
5. Run `git commit` and paste message
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Global Installation (Recommended)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g diffscribe
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After installation, you can run the `dcs` command from anywhere:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
dcs # If installed globally
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Zero-install via npx
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx diffscribe
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Setup
|
|
45
|
+
|
|
46
|
+
### 1. Get OpenRouter API Key
|
|
47
|
+
|
|
48
|
+
Visit https://openrouter.ai/keys to get your API key.
|
|
49
|
+
|
|
50
|
+
### 2. Set Environment Variable
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export OPENROUTER_API_KEY=your-api-key-here
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For permanent setup, add to your shell profile:
|
|
57
|
+
```bash
|
|
58
|
+
echo 'export OPENROUTER_API_KEY=your-api-key-here' >> ~/.bashrc # or ~/.zshrc
|
|
59
|
+
source ~/.bashrc
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Verify Setup
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
echo $OPENROUTER_API_KEY
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Basic Usage
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Stage your changes
|
|
74
|
+
git add .
|
|
75
|
+
|
|
76
|
+
# Generate commit message (if installed globally)
|
|
77
|
+
dcs
|
|
78
|
+
|
|
79
|
+
# Or using npx
|
|
80
|
+
npx diffscribe
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Mock Mode (Testing)
|
|
84
|
+
|
|
85
|
+
For testing without API calls:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
dcs --mock # If installed globally
|
|
89
|
+
npx diffscribe --mock # Using npx
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Model Strategy
|
|
93
|
+
|
|
94
|
+
The tool uses a smart two-stage approach for optimal cost and quality:
|
|
95
|
+
|
|
96
|
+
### Draft Stage (Free Models)
|
|
97
|
+
- **Primary**: `mistralai/devstral-2512:free` — Fast free model for initial commit message
|
|
98
|
+
- **Backup**: `qwen/qwen3-coder:free` — Falls back if primary hits rate limits
|
|
99
|
+
|
|
100
|
+
### Refinement Stage (Paid Model)
|
|
101
|
+
- **Refinement**: `google/gemini-2.5-flash-lite` — Polishes messages for large diffs (300+ lines or 12KB+)
|
|
102
|
+
|
|
103
|
+
### When Does Refinement Run?
|
|
104
|
+
Only for larger changes to improve clarity and structure. Smaller diffs skip refinement to save cost.
|
|
105
|
+
|
|
106
|
+
## Options
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
### dcs
|
|
110
|
+
|
|
111
|
+
Generate AI-powered commit messages for your staged changes.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
dcs [options]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
-V, --version output the version number
|
|
119
|
+
--mock Use mock generation instead of LLM (for testing)
|
|
120
|
+
-h, --help display help for command
|
|
121
|
+
|
|
122
|
+
### Examples
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Generate commit message with AI
|
|
126
|
+
dcs
|
|
127
|
+
|
|
128
|
+
# Test without API calls
|
|
129
|
+
dcs --mock
|
|
130
|
+
|
|
131
|
+
# Using npx (no installation needed)
|
|
132
|
+
npx diffscribe
|
|
133
|
+
npx diffscribe --mock
|
|
134
|
+
```
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Requirements
|
|
138
|
+
|
|
139
|
+
- Node.js >= 20
|
|
140
|
+
- Git repository
|
|
141
|
+
- OpenRouter API key (unless using --mock mode)
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Install dependencies
|
|
147
|
+
npm install
|
|
148
|
+
|
|
149
|
+
# Build
|
|
150
|
+
npm run build
|
|
151
|
+
|
|
152
|
+
# Watch mode
|
|
153
|
+
npm run dev
|
|
154
|
+
|
|
155
|
+
# Type check
|
|
156
|
+
npm run typecheck
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type CommitStyle = 'concise' | 'detailed';
|
|
2
|
+
export interface CommitMessage {
|
|
3
|
+
header: string;
|
|
4
|
+
body?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface GitResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
diff?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface OpenRouterConfig {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
siteUrl?: string;
|
|
14
|
+
siteName?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const DRAFT_MODEL_PRIMARY = "google/gemini-2.5-flash-lite";
|
|
17
|
+
export declare const DRAFT_MODEL_BACKUP = "mistralai/devstral-2512";
|
|
18
|
+
export declare const REFINEMENT_MODEL = "google/gemini-2.5-flash";
|
|
19
|
+
export declare function getOpenRouterConfig(): OpenRouterConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
config({ path: join(__dirname, '../.env') });
|
|
7
|
+
export const DRAFT_MODEL_PRIMARY = 'google/gemini-2.5-flash-lite';
|
|
8
|
+
export const DRAFT_MODEL_BACKUP = 'mistralai/devstral-2512';
|
|
9
|
+
export const REFINEMENT_MODEL = 'google/gemini-2.5-flash';
|
|
10
|
+
export function getOpenRouterConfig() {
|
|
11
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error('OPENROUTER_API_KEY environment variable is not set.\n' +
|
|
14
|
+
'Get your API key from: https://openrouter.ai/keys\n' +
|
|
15
|
+
'Set it with: export OPENROUTER_API_KEY=your-key-here');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
apiKey,
|
|
19
|
+
siteUrl: 'https://github.com/yourusername/diffscribe',
|
|
20
|
+
siteName: 'diffscribe'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AAqB7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAElE,MAAM,CAAC,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAE5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAE1D,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,uDAAuD;YACvD,qDAAqD;YACrD,sDAAsD,CACvD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO,EAAE,4CAA4C;QACrD,QAAQ,EAAE,YAAY;KACvB,CAAC;AACJ,CAAC"}
|
package/dist/git.d.ts
ADDED
package/dist/git.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Git operations and repository management
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
export class GitHelper {
|
|
4
|
+
cwd;
|
|
5
|
+
constructor(cwd) {
|
|
6
|
+
this.cwd = cwd;
|
|
7
|
+
}
|
|
8
|
+
async isInGitRepo() {
|
|
9
|
+
try {
|
|
10
|
+
await execa('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
11
|
+
cwd: this.cwd,
|
|
12
|
+
stdio: 'ignore'
|
|
13
|
+
});
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async getStagedDiff() {
|
|
21
|
+
try {
|
|
22
|
+
const { stdout } = await execa('git', ['diff', '--cached'], {
|
|
23
|
+
cwd: this.cwd,
|
|
24
|
+
maxBuffer: 10 * 1024 * 1024
|
|
25
|
+
});
|
|
26
|
+
if (!stdout.trim()) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
error: 'No staged changes found'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
diff: stdout
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: error.message || 'Failed to get staged diff'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async hasStagedChanges() {
|
|
45
|
+
try {
|
|
46
|
+
await execa('git', ['diff', '--cached', '--quiet', '--exit-code'], {
|
|
47
|
+
cwd: this.cwd
|
|
48
|
+
});
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return error.exitCode === 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# 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":"AAAA,2CAA2C;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAG9B,MAAM,OAAO,SAAS;IACZ,GAAG,CAAU;IAErB,YAAY,GAAY;QACtB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,WAAW;QACf,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;gBACzD,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,KAAK,EAAE,QAAQ;aAChB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE;gBAC1D,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;aAC5B,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,yBAAyB;iBACjC,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,MAAM;aACb,CAAC;QACJ,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK,CAAC,OAAO,IAAI,2BAA2B;aACpD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE;gBACjE,GAAG,EAAE,IAAI,CAAC,GAAG;aACd,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,QAAQ,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { GitHelper } from './git.js';
|
|
5
|
+
import { CommitService } from './service.js';
|
|
6
|
+
import { promptCommitStyle, promptAction, promptContinueRegeneration } from './ui.js';
|
|
7
|
+
import { getOpenRouterConfig, DRAFT_MODEL_PRIMARY, DRAFT_MODEL_BACKUP, REFINEMENT_MODEL } from './config.js';
|
|
8
|
+
const program = new Command()
|
|
9
|
+
.name('diffscribe')
|
|
10
|
+
.description('AI-powered commit message generator')
|
|
11
|
+
.version('1.0.0')
|
|
12
|
+
.option('--mock', 'Use mock generation instead of LLM (for testing)');
|
|
13
|
+
async function generateMockCommitMessage(style, diff) {
|
|
14
|
+
const hasTest = diff.includes('test') || diff.includes('spec');
|
|
15
|
+
const hasFix = diff.includes('fix') || diff.includes('bug');
|
|
16
|
+
const hasFeature = diff.includes('add') || diff.includes('new');
|
|
17
|
+
let type = 'chore';
|
|
18
|
+
let description = 'update code';
|
|
19
|
+
if (hasTest) {
|
|
20
|
+
type = 'test';
|
|
21
|
+
description = 'add test coverage';
|
|
22
|
+
}
|
|
23
|
+
else if (hasFix) {
|
|
24
|
+
type = 'fix';
|
|
25
|
+
description = 'fix bugs';
|
|
26
|
+
}
|
|
27
|
+
else if (hasFeature) {
|
|
28
|
+
type = 'feat';
|
|
29
|
+
description = 'add new feature';
|
|
30
|
+
}
|
|
31
|
+
const header = `${type}(): ${description}`;
|
|
32
|
+
if (style === 'detailed') {
|
|
33
|
+
return {
|
|
34
|
+
header,
|
|
35
|
+
body: '- update implementation\n- improve code quality'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return { header };
|
|
39
|
+
}
|
|
40
|
+
async function main() {
|
|
41
|
+
const options = program.opts();
|
|
42
|
+
const useMock = options.mock;
|
|
43
|
+
console.log(chalk.cyan.bold('diffscribe — AI-powered commit message generator\n'));
|
|
44
|
+
console.log(chalk.blue('ℹ Starting...\n'));
|
|
45
|
+
if (!useMock) {
|
|
46
|
+
try {
|
|
47
|
+
getOpenRouterConfig();
|
|
48
|
+
console.log(chalk.dim(`Draft model: ${DRAFT_MODEL_PRIMARY} (backup: ${DRAFT_MODEL_BACKUP})`));
|
|
49
|
+
console.log(chalk.dim(`Refinement model: ${REFINEMENT_MODEL}\n`));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`${chalk.bold.red('✖')} ${error.message}`);
|
|
53
|
+
console.log(chalk.dim('To use mock mode for testing, run: diffscribe --mock'));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk.dim('Running in mock mode (no API calls)\n'));
|
|
59
|
+
}
|
|
60
|
+
const git = new GitHelper();
|
|
61
|
+
if (!(await git.isInGitRepo())) {
|
|
62
|
+
console.error(`${chalk.bold.red('✖')} Not a Git repository.`);
|
|
63
|
+
console.log(chalk.dim('Run diffscribe inside a Git project.'));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const diffResult = await git.getStagedDiff();
|
|
67
|
+
if (!diffResult.success || !diffResult.diff) {
|
|
68
|
+
console.error(`${chalk.bold.red('✖')} ${diffResult.error || 'No staged changes found.'}`);
|
|
69
|
+
console.log(chalk.dim('Stage files using `git add` before running diffscribe.'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
console.log(`${chalk.bold.green('✓')} Found staged changes (${diffResult.diff.split('\n').length} lines)`);
|
|
73
|
+
const style = await promptCommitStyle();
|
|
74
|
+
console.log(chalk.blue(`ℹ Selected style: ${style}\n`));
|
|
75
|
+
let shouldContinue = true;
|
|
76
|
+
let attempt = 0;
|
|
77
|
+
const service = new CommitService();
|
|
78
|
+
while (shouldContinue) {
|
|
79
|
+
attempt++;
|
|
80
|
+
console.log(chalk.dim(`Generating commit message (attempt ${attempt})...`));
|
|
81
|
+
let message;
|
|
82
|
+
if (useMock) {
|
|
83
|
+
message = await generateMockCommitMessage(style, diffResult.diff);
|
|
84
|
+
service.displayCommitMessage(message);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const result = await service.generateCommitMessageWithLLM(diffResult.diff, style);
|
|
88
|
+
if (!result.success || !result.message) {
|
|
89
|
+
console.error(`${chalk.bold.red('✖')} ${result.error || 'Failed to generate commit message'}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
message = service.parseCommitMessage(result.message);
|
|
93
|
+
service.displayCommitMessage(message, result.model);
|
|
94
|
+
}
|
|
95
|
+
const action = await promptAction();
|
|
96
|
+
if (action === 'accept') {
|
|
97
|
+
await service.copyCommitMessage(message);
|
|
98
|
+
console.log(chalk.blue('ℹ Run `git commit` and paste the message.'));
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
else if (action === 'reject') {
|
|
102
|
+
console.log(chalk.blue('ℹ Cancelled.'));
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
else if (action === 'regenerate') {
|
|
106
|
+
shouldContinue = await promptContinueRegeneration();
|
|
107
|
+
if (!shouldContinue) {
|
|
108
|
+
console.log(chalk.blue('ℹ Cancelled.'));
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
program.action(() => {
|
|
115
|
+
main().catch((error) => {
|
|
116
|
+
console.error(`${chalk.bold.red('✖')} ${error.message}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
program.parse(process.argv);
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,gBAAgB,EAA8B,MAAM,aAAa,CAAC;AAEzI,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,qCAAqC,CAAC;KAClD,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,QAAQ,EAAE,kDAAkD,CAAC,CAAC;AAExE,KAAK,UAAU,yBAAyB,CAAC,KAAkB,EAAE,IAAY;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,WAAW,GAAG,aAAa,CAAC;IAEhC,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,GAAG,MAAM,CAAC;QACd,WAAW,GAAG,mBAAmB,CAAC;IACpC,CAAC;SAAM,IAAI,MAAM,EAAE,CAAC;QAClB,IAAI,GAAG,KAAK,CAAC;QACb,WAAW,GAAG,UAAU,CAAC;IAC3B,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,IAAI,GAAG,MAAM,CAAC;QACd,WAAW,GAAG,iBAAiB,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,IAAI,OAAO,WAAW,EAAE,CAAC;IAE3C,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QACzB,OAAO;YACL,MAAM;YACN,IAAI,EAAE,iDAAiD;SACxD,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAE3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,CAAC;YACH,mBAAmB,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,mBAAmB,aAAa,kBAAkB,GAAG,CAAC,CAAC,CAAC;YAC9F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,gBAAgB,IAAI,CAAC,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC;IAE5B,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC;IAE7C,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,KAAK,IAAI,0BAA0B,EAAE,CAAC,CAAC;QAC1F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC;IAE3G,MAAM,KAAK,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,IAAI,CAAC,CAAC,CAAC;IAExD,IAAI,cAAc,GAAG,IAAI,CAAC;IAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAG,IAAI,aAAa,EAAE,CAAC;IAEpC,OAAO,cAAc,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sCAAsC,OAAO,MAAM,CAAC,CAAC,CAAC;QAE5E,IAAI,OAAsB,CAAC;QAE3B,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,MAAM,yBAAyB,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;YAClE,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,4BAA4B,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAElF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACvC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,IAAI,mCAAmC,EAAE,CAAC,CAAC;gBAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACrD,OAAO,CAAC,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;QAEpC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;YACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;YACnC,cAAc,GAAG,MAAM,0BAA0B,EAAE,CAAC;YACpD,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;gBACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE;IAClB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CommitStyle } from './config.js';
|
|
2
|
+
export declare function promptCommitStyle(): Promise<CommitStyle>;
|
|
3
|
+
export declare function promptAction(): Promise<'accept' | 'regenerate' | 'reject'>;
|
|
4
|
+
export declare function promptContinueRegeneration(): Promise<boolean>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
2
|
+
export async function promptCommitStyle() {
|
|
3
|
+
const choices = [
|
|
4
|
+
{ name: 'Concise — single-line, clean history', value: 'concise' },
|
|
5
|
+
{ name: 'Detailed — header + body with context', value: 'detailed' }
|
|
6
|
+
];
|
|
7
|
+
return await select({
|
|
8
|
+
message: 'Choose commit message style:',
|
|
9
|
+
choices
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export async function promptAction() {
|
|
13
|
+
const choices = [
|
|
14
|
+
{ name: 'Accept & copy to clipboard', value: 'accept' },
|
|
15
|
+
{ name: 'Regenerate', value: 'regenerate' },
|
|
16
|
+
{ name: 'Reject & exit', value: 'reject' }
|
|
17
|
+
];
|
|
18
|
+
return await select({
|
|
19
|
+
message: 'What would you like to do?',
|
|
20
|
+
choices
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export async function promptContinueRegeneration() {
|
|
24
|
+
return await confirm({
|
|
25
|
+
message: 'Continue regenerating?',
|
|
26
|
+
default: true
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,OAAO,GAAG;QACd,EAAE,IAAI,EAAE,uCAAuC,EAAE,KAAK,EAAE,SAAwB,EAAE;QAClF,EAAE,IAAI,EAAE,uCAAuC,EAAE,KAAK,EAAE,UAAyB,EAAE;KACpF,CAAC;IAEF,OAAO,MAAM,MAAM,CAAC;QAClB,OAAO,EAAE,8BAA8B;QACvC,OAAO;KACR,CAAgB,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG;QACd,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,QAAQ,EAAE;QACvD,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;QAC3C,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE;KAC3C,CAAC;IAEF,OAAO,MAAM,MAAM,CAAC;QAClB,OAAO,EAAE,4BAA4B;QACrC,OAAO;KACR,CAAuC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,OAAO,MAAM,OAAO,CAAC;QACnB,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CommitMessage } from './config.js';
|
|
2
|
+
export interface CommitMessageGenerationResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
message?: string;
|
|
5
|
+
error?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class CommitService {
|
|
9
|
+
generateCommitMessageWithLLM(diff: string, style: 'concise' | 'detailed'): Promise<CommitMessageGenerationResult>;
|
|
10
|
+
parseCommitMessage(llmOutput: string): CommitMessage;
|
|
11
|
+
displayCommitMessage(message: CommitMessage, model?: string): void;
|
|
12
|
+
copyCommitMessage(message: CommitMessage): Promise<void>;
|
|
13
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import clipboardy from 'clipboardy';
|
|
3
|
+
import { OpenRouter } from '@openrouter/sdk';
|
|
4
|
+
import { getOpenRouterConfig, DRAFT_MODEL_PRIMARY, DRAFT_MODEL_BACKUP, REFINEMENT_MODEL } from './config.js';
|
|
5
|
+
const output = {
|
|
6
|
+
error: (message) => {
|
|
7
|
+
console.error(`${chalk.bold.red('✖')} ${message}`);
|
|
8
|
+
},
|
|
9
|
+
success: (message) => {
|
|
10
|
+
console.log(`${chalk.bold.green('✓')} ${message}`);
|
|
11
|
+
},
|
|
12
|
+
info: (message) => {
|
|
13
|
+
console.log(`${chalk.blue('ℹ')} ${message}`);
|
|
14
|
+
},
|
|
15
|
+
warning: (message) => {
|
|
16
|
+
console.log(`${chalk.yellow('⚠')} ${message}`);
|
|
17
|
+
},
|
|
18
|
+
dim: (message) => {
|
|
19
|
+
console.log(chalk.dim(message));
|
|
20
|
+
},
|
|
21
|
+
divider: () => {
|
|
22
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
23
|
+
},
|
|
24
|
+
header: (title) => {
|
|
25
|
+
console.log(`\n${chalk.bold.cyan(title)}\n`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function calculateDiffStats(diff) {
|
|
29
|
+
const lines = diff.split('\n').length;
|
|
30
|
+
const chars = diff.length;
|
|
31
|
+
return { lines, chars };
|
|
32
|
+
}
|
|
33
|
+
function isLongDiff(stats) {
|
|
34
|
+
return stats.lines > 300 || stats.chars > 12000;
|
|
35
|
+
}
|
|
36
|
+
async function callModel(model, systemPrompt, userPrompt, maxTokens = 500) {
|
|
37
|
+
try {
|
|
38
|
+
const config = getOpenRouterConfig();
|
|
39
|
+
const openRouter = new OpenRouter({
|
|
40
|
+
apiKey: config.apiKey
|
|
41
|
+
});
|
|
42
|
+
const result = openRouter.callModel({
|
|
43
|
+
model,
|
|
44
|
+
instructions: systemPrompt,
|
|
45
|
+
input: [
|
|
46
|
+
{
|
|
47
|
+
role: 'user',
|
|
48
|
+
content: userPrompt
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
temperature: 0.1,
|
|
52
|
+
maxOutputTokens: maxTokens
|
|
53
|
+
});
|
|
54
|
+
const message = await result.getText();
|
|
55
|
+
if (!message || message.trim() === '') {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: 'LLM returned empty message'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
message: message.trim(),
|
|
64
|
+
model
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.statusCode === 401) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: 'Invalid API key. Please check your OPENROUTER_API_KEY environment variable.'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (error.statusCode === 429) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: 'Rate limit exceeded. Please wait and try again.'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error.message || 'Failed to generate commit message'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function generateCommitMessage(diff, style, useFallback) {
|
|
87
|
+
const styleInstruction = style === 'concise'
|
|
88
|
+
? 'Return ONLY a single-line commit message in Conventional Commits format: <type>(<scope>): <description>'
|
|
89
|
+
: 'Return a commit message in Conventional Commits format with:\n- Header: <type>(<scope>): <description>\n- Body: 2-3 bullet points explaining what was changed and why';
|
|
90
|
+
const systemPrompt = `You are an expert developer who writes perfect Conventional Commits.
|
|
91
|
+
Your task is to analyze git diffs and generate clear, meaningful commit messages.
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
- Use valid Conventional Commit types: feat, fix, docs, style, refactor, test, chore, perf
|
|
95
|
+
- Scope should be the affected module or component (e.g., auth, api, ui, db)
|
|
96
|
+
- Description must be in imperative mood (e.g., "add" not "added" or "adds")
|
|
97
|
+
- Keep header under 72 characters
|
|
98
|
+
- No period at the end of header
|
|
99
|
+
- Focus on WHAT and WHY, not HOW`;
|
|
100
|
+
const userPrompt = `${styleInstruction}\n\nGit Diff:\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
101
|
+
const result = await callModel(DRAFT_MODEL_PRIMARY, systemPrompt, userPrompt);
|
|
102
|
+
if (result.success) {
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
if (useFallback) {
|
|
106
|
+
const backupResult = await callModel(DRAFT_MODEL_BACKUP, systemPrompt, userPrompt);
|
|
107
|
+
return backupResult;
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
async function refineCommitMessage(draftMessage) {
|
|
112
|
+
const systemPrompt = `You refine git commit messages.
|
|
113
|
+
Improve clarity, grammar, and structure.
|
|
114
|
+
Do NOT invent new changes or alter meaning.
|
|
115
|
+
Maintain Conventional Commits format.`;
|
|
116
|
+
const userPrompt = `Refine this commit message:\n\n${draftMessage}\n\nReturn the final commit message.`;
|
|
117
|
+
return await callModel(REFINEMENT_MODEL, systemPrompt, userPrompt, 300);
|
|
118
|
+
}
|
|
119
|
+
function parseCommitMessage(llmOutput) {
|
|
120
|
+
const lines = llmOutput.split('\n').filter(line => line.trim() !== '');
|
|
121
|
+
if (lines.length === 0) {
|
|
122
|
+
return { header: 'chore: update code' };
|
|
123
|
+
}
|
|
124
|
+
const header = lines[0].trim();
|
|
125
|
+
if (lines.length === 1) {
|
|
126
|
+
return { header };
|
|
127
|
+
}
|
|
128
|
+
const bodyLines = lines.slice(1).join('\n');
|
|
129
|
+
return { header, body: bodyLines };
|
|
130
|
+
}
|
|
131
|
+
async function copyToClipboard(text) {
|
|
132
|
+
try {
|
|
133
|
+
await clipboardy.write(text);
|
|
134
|
+
output.success('Commit message copied to clipboard!');
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
output.error('Failed to copy to clipboard');
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export class CommitService {
|
|
142
|
+
async generateCommitMessageWithLLM(diff, style) {
|
|
143
|
+
const stats = calculateDiffStats(diff);
|
|
144
|
+
const isLong = isLongDiff(stats);
|
|
145
|
+
if (!isLong) {
|
|
146
|
+
const result = await generateCommitMessage(diff, style, false);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
const draftResult = await generateCommitMessage(diff, style, true);
|
|
150
|
+
if (!draftResult.success || !draftResult.message) {
|
|
151
|
+
return draftResult;
|
|
152
|
+
}
|
|
153
|
+
const refinedResult = await refineCommitMessage(draftResult.message);
|
|
154
|
+
if (refinedResult.success && refinedResult.message) {
|
|
155
|
+
return refinedResult;
|
|
156
|
+
}
|
|
157
|
+
return draftResult;
|
|
158
|
+
}
|
|
159
|
+
parseCommitMessage(llmOutput) {
|
|
160
|
+
return parseCommitMessage(llmOutput);
|
|
161
|
+
}
|
|
162
|
+
displayCommitMessage(message, model) {
|
|
163
|
+
output.header('Generated Commit Message');
|
|
164
|
+
if (model) {
|
|
165
|
+
output.dim(`Model: ${model}`);
|
|
166
|
+
console.log();
|
|
167
|
+
}
|
|
168
|
+
console.log(chalk.cyan(message.header));
|
|
169
|
+
if (message.body) {
|
|
170
|
+
console.log();
|
|
171
|
+
console.log(chalk.dim(message.body));
|
|
172
|
+
}
|
|
173
|
+
output.divider();
|
|
174
|
+
}
|
|
175
|
+
async copyCommitMessage(message) {
|
|
176
|
+
const fullMessage = message.body ? `${message.header}\n\n${message.body}` : message.header;
|
|
177
|
+
await copyToClipboard(fullMessage);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
package/dist/test.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA"}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CommitStyle } from './config.js';
|
|
2
|
+
export declare function promptCommitStyle(): Promise<CommitStyle>;
|
|
3
|
+
export declare function promptAction(): Promise<'accept' | 'regenerate' | 'reject'>;
|
|
4
|
+
export declare function promptContinueRegeneration(): Promise<boolean>;
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// User interaction prompts and CLI flow
|
|
2
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
3
|
+
export async function promptCommitStyle() {
|
|
4
|
+
const choices = [
|
|
5
|
+
{ name: 'Concise — single-line, clean history', value: 'concise' },
|
|
6
|
+
{ name: 'Detailed — header + body with context', value: 'detailed' }
|
|
7
|
+
];
|
|
8
|
+
return await select({
|
|
9
|
+
message: 'Choose commit message style:',
|
|
10
|
+
choices
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function promptAction() {
|
|
14
|
+
const choices = [
|
|
15
|
+
{ name: 'Accept & copy to clipboard', value: 'accept' },
|
|
16
|
+
{ name: 'Regenerate', value: 'regenerate' },
|
|
17
|
+
{ name: 'Reject & exit', value: 'reject' }
|
|
18
|
+
];
|
|
19
|
+
return await select({
|
|
20
|
+
message: 'What would you like to do?',
|
|
21
|
+
choices
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function promptContinueRegeneration() {
|
|
25
|
+
return await confirm({
|
|
26
|
+
message: 'Continue regenerating?',
|
|
27
|
+
default: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,OAAO,GAAG;QACd,EAAE,IAAI,EAAE,uCAAuC,EAAE,KAAK,EAAE,SAAwB,EAAE;QAClF,EAAE,IAAI,EAAE,uCAAuC,EAAE,KAAK,EAAE,UAAyB,EAAE;KACpF,CAAC;IAEF,OAAO,MAAM,MAAM,CAAC;QAClB,OAAO,EAAE,8BAA8B;QACvC,OAAO;KACR,CAAgB,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG;QACd,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,QAAQ,EAAE;QACvD,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;QAC3C,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE;KAC3C,CAAC;IAEF,OAAO,MAAM,MAAM,CAAC;QAClB,OAAO,EAAE,4BAA4B;QACrC,OAAO;KACR,CAAuC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,OAAO,MAAM,OAAO,CAAC;QACnB,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diffscribe",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "AI-powered commit message generator for Conventional Commits",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dcs": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": "./dist/index.js",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && shx chmod +x dist/index.js",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepare": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@inquirer/prompts": "^7.2.0",
|
|
25
|
+
"@openrouter/sdk": "^0.5.1",
|
|
26
|
+
"chalk": "^5.4.1",
|
|
27
|
+
"clipboardy": "^4.0.0",
|
|
28
|
+
"commander": "^12.1.0",
|
|
29
|
+
"dotenv": "^17.2.3",
|
|
30
|
+
"execa": "^9.5.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.10.2",
|
|
34
|
+
"shx": "^0.3.4",
|
|
35
|
+
"typescript": "^5.7.2"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"git",
|
|
39
|
+
"commit",
|
|
40
|
+
"conventional-commits",
|
|
41
|
+
"cli",
|
|
42
|
+
"ai",
|
|
43
|
+
"diffscribe",
|
|
44
|
+
"dcs"
|
|
45
|
+
],
|
|
46
|
+
"preferGlobal": true,
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/raghavvvgaba/diffscribe.git"
|
|
53
|
+
},
|
|
54
|
+
"author": "Raghav Gaba <raghav@example.com>",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/raghavvvgaba/diffscribe/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/raghavvvgaba/diffscribe#readme",
|
|
59
|
+
"license": "MIT"
|
|
60
|
+
}
|