@johnowennixon/diffdash 1.11.0 → 1.13.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 +21 -19
- package/dist/package.json +20 -20
- package/dist/src/lib_ansi.js +0 -1
- package/dist/src/lib_cli.js +50 -28
- package/dist/src/lib_datetime.js +14 -0
- package/dist/src/lib_debug.js +2 -1
- package/dist/src/lib_diffdash_cli.js +31 -24
- package/dist/src/lib_diffdash_config.js +6 -3
- package/dist/src/lib_diffdash_llm.js +5 -6
- package/dist/src/lib_diffdash_sequence.js +4 -4
- package/dist/src/lib_git_message_prompt.js +6 -4
- package/dist/src/lib_language.js +59 -0
- package/dist/src/lib_llm_api.js +2 -0
- package/dist/src/lib_llm_chat.js +4 -5
- package/dist/src/lib_llm_list.js +2 -2
- package/dist/src/lib_llm_model.js +114 -36
- package/dist/src/lib_llm_results.js +3 -2
- package/dist/src/lib_secret_check.js +5 -5
- package/dist/src/lib_tui_table.js +3 -0
- package/package.json +36 -36
- package/dist/src/lib_string_types.js +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# DiffDash
|
|
2
4
|
|
|
3
5
|

|
|
@@ -7,23 +9,20 @@
|
|
|
7
9
|
|
|
8
10
|
A command-line tool to generate Git commit messages using AI.
|
|
9
11
|
|
|
10
|
-
## Demonstration
|
|
11
|
-
|
|
12
|
-

|
|
13
|
-
|
|
14
12
|
## Features
|
|
15
13
|
|
|
16
|
-
* Generate Git commit messages in
|
|
17
|
-
* Add a
|
|
18
|
-
* Add a
|
|
14
|
+
* Generate Git commit messages in natural language prose
|
|
15
|
+
* Add a prefix or suffix to the summary line to support tickets from project management tools
|
|
16
|
+
* Add a metadata footer to the generated commit messages
|
|
17
|
+
* Support for 42 human languages including English, Chinese, and Hindi
|
|
19
18
|
* Select from a choice of LLM models
|
|
20
19
|
* Compare messages generated from all configured models
|
|
21
20
|
* Disable or auto-approve various stages
|
|
22
21
|
* Option to output just the commit message for use in scripts
|
|
23
22
|
* Configuration using standard API provider environment variables
|
|
24
|
-
* Uses the Vercel AI SDK (version
|
|
23
|
+
* Uses the Vercel AI SDK (version 6)
|
|
25
24
|
* Uses structured JSON with compatible models
|
|
26
|
-
* Substantially written using AI coding (Claude Code, Roo Code, and Amp)
|
|
25
|
+
* Substantially written using AI coding (Claude Code, Roo Code, and Amp Code)
|
|
27
26
|
|
|
28
27
|
## Installation from npmjs.com
|
|
29
28
|
|
|
@@ -33,13 +32,10 @@ npm install -g @johnowennixon/diffdash
|
|
|
33
32
|
|
|
34
33
|
## LLM Models
|
|
35
34
|
|
|
36
|
-
Currently, for this application, the best LLM model is **gpt-
|
|
35
|
+
Currently, for this application, the best LLM model is **gpt-5-mini-minimal** (GPT-5 Mini with reasoning disabled) from OpenAI.
|
|
37
36
|
It is set as the default model.
|
|
38
37
|
I can only presume they have done a ton of training on diffs.
|
|
39
38
|
|
|
40
|
-
I am now testing the GPT-5 models and **gpt-5-mini-minimal** (GPT-5 Mini with reasoning disabled) is behaving much the same.
|
|
41
|
-
It will probably become the default model soon.
|
|
42
|
-
|
|
43
39
|
## API Keys
|
|
44
40
|
|
|
45
41
|
DiffDash requires at least one API key for an LLM provider. These must be provided as environment variables.
|
|
@@ -98,7 +94,10 @@ diffdash --no-verify
|
|
|
98
94
|
diffdash --add-prefix "[FIX]"
|
|
99
95
|
|
|
100
96
|
# Add a suffix to the commit message summary line
|
|
101
|
-
diffdash --add-suffix "(closes #
|
|
97
|
+
diffdash --add-suffix "(closes #DEV-1234)"
|
|
98
|
+
|
|
99
|
+
# Generate commit message in a different language
|
|
100
|
+
diffdash --language af # Afrikaans
|
|
102
101
|
|
|
103
102
|
# Display commit messages generated by all models
|
|
104
103
|
diffdash --llm-compare
|
|
@@ -131,6 +130,7 @@ All command-line arguments are optional.
|
|
|
131
130
|
| `--disable-push` | disable pushing changes - exit after making the commit |
|
|
132
131
|
| `--add-prefix PREFIX` | add a prefix to the commit message summary line |
|
|
133
132
|
| `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
|
|
133
|
+
| `--language CODE` | choose the language for commit messages (defaults to 'en' for English) |
|
|
134
134
|
| `--llm-list` | display a list of available Large Language Models and exit |
|
|
135
135
|
| `--llm-compare` | compare the generated messages from all models - but do not commit |
|
|
136
136
|
| `--llm-model MODEL` | choose the LLM model by name (the default is normally best) |
|
|
@@ -158,6 +158,8 @@ There is a rudimentary check for secrets in diffs before submitting to the LLM.
|
|
|
158
158
|
|
|
159
159
|
## Development
|
|
160
160
|
|
|
161
|
+
You will need to install [bun](https://bun.com/docs/pm/cli/install).
|
|
162
|
+
|
|
161
163
|
To install on your laptop:
|
|
162
164
|
|
|
163
165
|
```bash
|
|
@@ -166,10 +168,10 @@ git clone https://github.com/johnowennixon/diffdash.git
|
|
|
166
168
|
cd diffdash
|
|
167
169
|
|
|
168
170
|
# Install dependencies
|
|
169
|
-
|
|
171
|
+
bun install
|
|
170
172
|
|
|
171
173
|
# Build the project
|
|
172
|
-
|
|
174
|
+
bun run build
|
|
173
175
|
|
|
174
176
|
# Make binaries executable
|
|
175
177
|
npm link
|
|
@@ -179,13 +181,13 @@ To rebuild after editing:
|
|
|
179
181
|
|
|
180
182
|
```bash
|
|
181
183
|
# Lint the code
|
|
182
|
-
|
|
184
|
+
bun run lint
|
|
183
185
|
|
|
184
186
|
# Fix formatting issues (if required)
|
|
185
|
-
|
|
187
|
+
bun run fix
|
|
186
188
|
|
|
187
189
|
# Build the project
|
|
188
|
-
|
|
190
|
+
bun run build
|
|
189
191
|
```
|
|
190
192
|
|
|
191
193
|
## License
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A command-line tool to generate Git commit messages using AI",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"author": "John Owen Nixon",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"build:chmod": "echo 'Changing bin files to be executable' && chmodx --package",
|
|
24
24
|
"build:clean": "echo 'Removing dist' && rimraf dist",
|
|
25
25
|
"build:shebang": "echo 'Fixing the shebangs' && add-shebangs --node --exclude 'dist/**/lib_*.js' 'dist/**/*.js'",
|
|
26
|
-
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc
|
|
26
|
+
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc",
|
|
27
27
|
"build:tsgo": "echo 'Transpiling TypeScript to dist (using tsgo)' && tsgo || (rimraf dist && false)",
|
|
28
28
|
"fix": "run-s -ls fix:biome fix:markdownlint",
|
|
29
29
|
"fix:biome": "echo 'Fixing with Biome' && biome check --write",
|
|
30
30
|
"fix:docbot": "echo 'Fixing with DocBot' && docbot --prune --generate",
|
|
31
|
-
"fix:markdownlint": "echo 'Fixing with
|
|
31
|
+
"fix:markdownlint": "echo 'Fixing with Markdownlint' && markdownlint-cli2 '**/*.md' --fix",
|
|
32
32
|
"fix:oxlint": "echo 'Fixing with Oxlint' && oxlint --fix",
|
|
33
33
|
"lint": "run-s -ls lint:biome lint:oxlint lint:tsgolint lint:knip lint:markdownlint",
|
|
34
34
|
"lint:biome": "echo 'Linting with Biome' && biome check",
|
|
@@ -36,39 +36,39 @@
|
|
|
36
36
|
"lint:knip": "echo 'Linting with Knip' && knip",
|
|
37
37
|
"lint:markdownlint": "echo 'Linting with Markdownlint' && markdownlint-cli2 '**/*.md'",
|
|
38
38
|
"lint:oxlint": "echo 'Linting with Oxlint' && oxlint",
|
|
39
|
-
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit
|
|
39
|
+
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit",
|
|
40
40
|
"lint:tsgo": "echo 'Linting with tsgo' && tsgo --noEmit",
|
|
41
41
|
"lint:tsgolint": "echo 'Linting with tsgolint' && candide-tsgolint",
|
|
42
42
|
"test": "run-s -ls lint build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/anthropic": "
|
|
46
|
-
"@ai-sdk/deepseek": "
|
|
47
|
-
"@ai-sdk/google": "
|
|
48
|
-
"@ai-sdk/openai": "
|
|
49
|
-
"@inquirer/prompts": "8.
|
|
50
|
-
"@openrouter/ai-sdk-provider": "
|
|
51
|
-
"ai": "
|
|
45
|
+
"@ai-sdk/anthropic": "3.0.44",
|
|
46
|
+
"@ai-sdk/deepseek": "2.0.20",
|
|
47
|
+
"@ai-sdk/google": "3.0.29",
|
|
48
|
+
"@ai-sdk/openai": "3.0.29",
|
|
49
|
+
"@inquirer/prompts": "8.2.1",
|
|
50
|
+
"@openrouter/ai-sdk-provider": "2.2.3",
|
|
51
|
+
"ai": "6.0.86",
|
|
52
52
|
"ansis": "4.2.0",
|
|
53
53
|
"argparse": "2.0.1",
|
|
54
54
|
"cli-table3": "0.6.5",
|
|
55
55
|
"json5": "2.2.3",
|
|
56
56
|
"magic-regexp": "0.10.0",
|
|
57
|
-
"simple-git": "3.
|
|
58
|
-
"zod": "4.
|
|
57
|
+
"simple-git": "3.31.1",
|
|
58
|
+
"zod": "4.3.6"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@biomejs/biome": "2.3.
|
|
62
|
-
"@candide/tsgolint": "1.
|
|
61
|
+
"@biomejs/biome": "2.3.13",
|
|
62
|
+
"@candide/tsgolint": "1.5.0",
|
|
63
63
|
"@johnowennixon/add-shebangs": "1.1.0",
|
|
64
64
|
"@johnowennixon/chmodx": "2.1.0",
|
|
65
65
|
"@types/argparse": "2.0.17",
|
|
66
|
-
"@types/node": "
|
|
67
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
68
|
-
"knip": "5.
|
|
69
|
-
"markdownlint-cli2": "0.
|
|
66
|
+
"@types/node": "25.2.3",
|
|
67
|
+
"@typescript/native-preview": "7.0.0-dev.20260103.1",
|
|
68
|
+
"knip": "5.83.1",
|
|
69
|
+
"markdownlint-cli2": "0.20.0",
|
|
70
70
|
"npm-run-all2": "8.0.4",
|
|
71
|
-
"oxlint": "1.
|
|
71
|
+
"oxlint": "1.47.0",
|
|
72
72
|
"rimraf": "6.1.2",
|
|
73
73
|
"typescript": "5.9.3"
|
|
74
74
|
}
|
package/dist/src/lib_ansi.js
CHANGED
package/dist/src/lib_cli.js
CHANGED
|
@@ -1,33 +1,70 @@
|
|
|
1
1
|
import { ArgumentParser } from "argparse";
|
|
2
2
|
import { EMPTY } from "./lib_char_empty.js";
|
|
3
|
-
import { DASH, PLUS, UNDERSCORE } from "./lib_char_punctuation.js";
|
|
3
|
+
import { DASH, PLUS, QUESTION, UNDERSCORE } from "./lib_char_punctuation.js";
|
|
4
4
|
import { debug_channels, debug_inspect_when } from "./lib_debug.js";
|
|
5
|
-
export function
|
|
5
|
+
export function cli_string_optional(params) {
|
|
6
|
+
const { help, metavar } = params;
|
|
7
|
+
const options = { help, metavar };
|
|
6
8
|
return { kind: "string", options, value: "" };
|
|
7
9
|
}
|
|
8
|
-
export function
|
|
10
|
+
export function cli_string_required(params) {
|
|
11
|
+
const { help, metavar } = params;
|
|
12
|
+
const options = { help, metavar, required: true };
|
|
9
13
|
return { kind: "string", options, value: "" };
|
|
10
14
|
}
|
|
11
|
-
export function
|
|
15
|
+
export function cli_string_default(params) {
|
|
16
|
+
const { help, metavar, default: _default } = params;
|
|
17
|
+
const options = { help, metavar, default: _default };
|
|
18
|
+
return { kind: "string", options, value: "" };
|
|
19
|
+
}
|
|
20
|
+
export function cli_string_positional_required(params) {
|
|
21
|
+
const { help, metavar } = params;
|
|
22
|
+
const options = { help, metavar, positional: true };
|
|
23
|
+
return { kind: "string", options, value: "" };
|
|
24
|
+
}
|
|
25
|
+
export function cli_string_positional_optional(params) {
|
|
26
|
+
const { help, metavar } = params;
|
|
27
|
+
const options = { help, metavar, positional: true, nargs: QUESTION };
|
|
28
|
+
return { kind: "string", options, value: "" };
|
|
29
|
+
}
|
|
30
|
+
export function cli_integer_optional(params) {
|
|
31
|
+
const { help, metavar } = params;
|
|
32
|
+
const options = { help, metavar };
|
|
12
33
|
return { kind: "integer", options, value: 0 };
|
|
13
34
|
}
|
|
14
|
-
export function
|
|
35
|
+
export function cli_integer_default(params) {
|
|
36
|
+
const { help, metavar, default: _default } = params;
|
|
37
|
+
const options = { help, metavar, default: _default };
|
|
15
38
|
return { kind: "integer", options, value: 0 };
|
|
16
39
|
}
|
|
17
|
-
export function
|
|
40
|
+
export function cli_boolean_always(params) {
|
|
41
|
+
const { help } = params;
|
|
42
|
+
const options = { help };
|
|
18
43
|
return { kind: "boolean", options, value: false };
|
|
19
44
|
}
|
|
20
|
-
export function
|
|
45
|
+
export function cli_boolean_default(params) {
|
|
46
|
+
const { help, default: _default } = params;
|
|
47
|
+
const options = { help, default: _default };
|
|
48
|
+
return { kind: "boolean", options, value: false };
|
|
49
|
+
}
|
|
50
|
+
export function cli_choice_optional(params) {
|
|
51
|
+
const { help, metavar, choices } = params;
|
|
52
|
+
const options = { help, metavar, choices };
|
|
21
53
|
return { kind: "choice", options, value: undefined };
|
|
22
54
|
}
|
|
23
|
-
export function
|
|
55
|
+
export function cli_choice_required(params) {
|
|
56
|
+
const { help, metavar, choices } = params;
|
|
57
|
+
const options = { help, metavar, choices, required: true };
|
|
24
58
|
return { kind: "choice", options, value: undefined };
|
|
25
59
|
}
|
|
26
|
-
export function
|
|
27
|
-
|
|
60
|
+
export function cli_choice_default(params) {
|
|
61
|
+
const { help, metavar, choices, default: _default } = params;
|
|
62
|
+
const options = { help, metavar, choices, default: _default };
|
|
28
63
|
return { kind: "choice", options, value: undefined };
|
|
29
64
|
}
|
|
30
|
-
export function
|
|
65
|
+
export function cli_list_positional(params) {
|
|
66
|
+
const { help } = params;
|
|
67
|
+
const options = { help, positional: true };
|
|
31
68
|
return { kind: "list", options, value: [] };
|
|
32
69
|
}
|
|
33
70
|
export function cli_meg_optional(meg_schema) {
|
|
@@ -36,14 +73,11 @@ export function cli_meg_optional(meg_schema) {
|
|
|
36
73
|
export function cli_meg_required(meg_schema) {
|
|
37
74
|
return { kind: "meg", options: { required: true }, value: meg_schema };
|
|
38
75
|
}
|
|
39
|
-
export function cli_meg_required_predicate(meg_schema, predicate) {
|
|
40
|
-
return { kind: "meg", options: { required: true, predicate }, value: meg_schema };
|
|
41
|
-
}
|
|
42
76
|
function cli_omit(obj, key_to_omit) {
|
|
43
77
|
const { [key_to_omit]: _, ...rest } = obj;
|
|
44
78
|
return rest;
|
|
45
79
|
}
|
|
46
|
-
function cli_add_keys({ cli_schema, parser_group
|
|
80
|
+
function cli_add_keys({ cli_schema, parser_group }) {
|
|
47
81
|
for (const key in cli_schema) {
|
|
48
82
|
if (!Object.hasOwn(cli_schema, key)) {
|
|
49
83
|
continue;
|
|
@@ -52,11 +86,6 @@ function cli_add_keys({ cli_schema, parser_group, predicate, }) {
|
|
|
52
86
|
if (!cli) {
|
|
53
87
|
continue;
|
|
54
88
|
}
|
|
55
|
-
if (predicate !== undefined) {
|
|
56
|
-
if (!predicate(key)) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
89
|
const key_replaced = key.replaceAll(UNDERSCORE, DASH);
|
|
61
90
|
const key_amended = `${cli.options.positional === true ? EMPTY : "--"}${key_replaced}`;
|
|
62
91
|
const options = cli_omit(cli.options, "positional");
|
|
@@ -82,7 +111,6 @@ function cli_add_keys({ cli_schema, parser_group, predicate, }) {
|
|
|
82
111
|
cli_add_keys({
|
|
83
112
|
cli_schema: cli.value,
|
|
84
113
|
parser_group: mutually_exclusive_group,
|
|
85
|
-
predicate: cli.options.predicate,
|
|
86
114
|
});
|
|
87
115
|
}
|
|
88
116
|
break;
|
|
@@ -91,7 +119,7 @@ function cli_add_keys({ cli_schema, parser_group, predicate, }) {
|
|
|
91
119
|
}
|
|
92
120
|
}
|
|
93
121
|
}
|
|
94
|
-
function cli_recursive_parse({ schema, namespace,
|
|
122
|
+
function cli_recursive_parse({ schema, namespace, }) {
|
|
95
123
|
const result = {};
|
|
96
124
|
for (const key in schema) {
|
|
97
125
|
if (!Object.hasOwn(schema, key)) {
|
|
@@ -101,17 +129,11 @@ function cli_recursive_parse({ schema, namespace, predicate, }) {
|
|
|
101
129
|
if (!cli) {
|
|
102
130
|
continue;
|
|
103
131
|
}
|
|
104
|
-
if (predicate !== undefined) {
|
|
105
|
-
if (!predicate(key)) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
132
|
if (cli.kind === "meg") {
|
|
110
133
|
const nested_schema = cli.value;
|
|
111
134
|
result[key] = cli_recursive_parse({
|
|
112
135
|
schema: nested_schema,
|
|
113
136
|
namespace,
|
|
114
|
-
predicate: cli.options.predicate,
|
|
115
137
|
});
|
|
116
138
|
}
|
|
117
139
|
else {
|
package/dist/src/lib_datetime.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { EMPTY } from "./lib_char_empty.js";
|
|
2
2
|
import { COLON, DASH, SPACE } from "./lib_char_punctuation.js";
|
|
3
|
+
export const DATETIME_WEEKDAYS = {
|
|
4
|
+
SUNDAY: 0,
|
|
5
|
+
MONDAY: 1,
|
|
6
|
+
TUESDAY: 2,
|
|
7
|
+
WEDNESDAY: 3,
|
|
8
|
+
THURSDAY: 4,
|
|
9
|
+
FRIDAY: 5,
|
|
10
|
+
SATURDAY: 6,
|
|
11
|
+
};
|
|
3
12
|
export function datetime_now() {
|
|
4
13
|
return new Date();
|
|
5
14
|
}
|
|
@@ -8,6 +17,11 @@ export function datetime_now_minus_days(days) {
|
|
|
8
17
|
date.setDate(date.getDate() - days);
|
|
9
18
|
return date;
|
|
10
19
|
}
|
|
20
|
+
export function datetime_now_plus_days(days) {
|
|
21
|
+
const date = datetime_now();
|
|
22
|
+
date.setDate(date.getDate() + days);
|
|
23
|
+
return date;
|
|
24
|
+
}
|
|
11
25
|
export function datetime_parse(s) {
|
|
12
26
|
return new Date(s);
|
|
13
27
|
}
|
package/dist/src/lib_debug.js
CHANGED
|
@@ -4,6 +4,7 @@ import { enabled_from_env } from "./lib_enabled.js";
|
|
|
4
4
|
import { inspect_obj_to_string } from "./lib_inspect.js";
|
|
5
5
|
import { stdio_write_stderr_linefeed } from "./lib_stdio_write.js";
|
|
6
6
|
import { tell_debug } from "./lib_tell.js";
|
|
7
|
+
import { tui_quote_smart_single as qss } from "./lib_tui_quote.js";
|
|
7
8
|
export const debug_channels = {
|
|
8
9
|
api: false,
|
|
9
10
|
backups: false,
|
|
@@ -37,7 +38,7 @@ export const debug_channels = {
|
|
|
37
38
|
export function debug_enable_if(channel, enabled) {
|
|
38
39
|
if (enabled && !debug_channels[channel]) {
|
|
39
40
|
debug_channels[channel] = true;
|
|
40
|
-
tell_debug(`Debugging enabled for
|
|
41
|
+
tell_debug(`Debugging enabled for ${qss(channel)}`);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
function debug_init() {
|
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cli_boolean_always, cli_choice_default, cli_make_parser, cli_string_optional } from "./lib_cli.js";
|
|
2
2
|
import { diffdash_llm_model_choices, diffdash_llm_model_default } from "./lib_diffdash_llm.js";
|
|
3
|
+
import { LANGUAGE_CODE_ENGLISH, language_get_code_choices } from "./lib_language.js";
|
|
4
|
+
import { tui_quote_smart_single as qss } from "./lib_tui_quote.js";
|
|
3
5
|
const diffdash_cli_schema = {
|
|
4
|
-
version:
|
|
5
|
-
auto_add:
|
|
6
|
-
auto_commit:
|
|
7
|
-
auto_push:
|
|
8
|
-
disable_add:
|
|
9
|
-
disable_status:
|
|
10
|
-
disable_preview:
|
|
11
|
-
disable_commit:
|
|
12
|
-
disable_push:
|
|
13
|
-
add_prefix:
|
|
14
|
-
add_suffix:
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
version: cli_boolean_always({ help: "show program version information and exit" }),
|
|
7
|
+
auto_add: cli_boolean_always({ help: "automatically stage all changes without confirmation" }),
|
|
8
|
+
auto_commit: cli_boolean_always({ help: "automatically commit changes without confirmation" }),
|
|
9
|
+
auto_push: cli_boolean_always({ help: "automatically push changes after commit without confirmation" }),
|
|
10
|
+
disable_add: cli_boolean_always({ help: "disable adding unstaged changes - exit if no changes staged" }),
|
|
11
|
+
disable_status: cli_boolean_always({ help: "disable listing the staged files before generating a message" }),
|
|
12
|
+
disable_preview: cli_boolean_always({ help: "disable previewing the generated message" }),
|
|
13
|
+
disable_commit: cli_boolean_always({ help: "disable committing changes - exit after generating the message" }),
|
|
14
|
+
disable_push: cli_boolean_always({ help: "disable pushing changes - exit after making the commit" }),
|
|
15
|
+
add_prefix: cli_string_optional({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
|
|
16
|
+
add_suffix: cli_string_optional({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
|
|
17
|
+
language: cli_choice_default({
|
|
18
|
+
help: `choose the language for commit messages (defaults to ${qss(LANGUAGE_CODE_ENGLISH)})`,
|
|
19
|
+
choices: language_get_code_choices(),
|
|
20
|
+
default: LANGUAGE_CODE_ENGLISH,
|
|
21
|
+
}),
|
|
22
|
+
llm_list: cli_boolean_always({ help: "display a list of available Large Language Models and exit" }),
|
|
23
|
+
llm_compare: cli_boolean_always({ help: "compare the generated messages from all models - but do not commit" }),
|
|
17
24
|
llm_model: cli_choice_default({
|
|
18
|
-
help: `choose the Large Language Model by name (defaults to ${diffdash_llm_model_default})`,
|
|
25
|
+
help: `choose the Large Language Model by name (defaults to ${qss(diffdash_llm_model_default)})`,
|
|
19
26
|
choices: diffdash_llm_model_choices,
|
|
20
27
|
default: diffdash_llm_model_default,
|
|
21
28
|
}),
|
|
22
|
-
llm_excludes:
|
|
23
|
-
no_secret_check:
|
|
24
|
-
no_verify:
|
|
25
|
-
force:
|
|
26
|
-
just_output:
|
|
27
|
-
silent:
|
|
28
|
-
debug_llm_prompts:
|
|
29
|
-
debug_llm_inputs:
|
|
30
|
-
debug_llm_outputs:
|
|
29
|
+
llm_excludes: cli_string_optional({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
|
|
30
|
+
no_secret_check: cli_boolean_always({ help: "bypass checking for secrets in diffs" }),
|
|
31
|
+
no_verify: cli_boolean_always({ help: "bypass git hooks when committing or pushing to Git" }),
|
|
32
|
+
force: cli_boolean_always({ help: "apply force when pushing to Git" }),
|
|
33
|
+
just_output: cli_boolean_always({ help: "just output the commit message for use in scripts" }),
|
|
34
|
+
silent: cli_boolean_always({ help: "suppress all normal output - errors and aborts still display" }),
|
|
35
|
+
debug_llm_prompts: cli_boolean_always({ help: "debug prompts sent to the LLM" }),
|
|
36
|
+
debug_llm_inputs: cli_boolean_always({ help: "debug inputs object sent to the LLM" }),
|
|
37
|
+
debug_llm_outputs: cli_boolean_always({ help: "debug outputs object received from the LLM" }),
|
|
31
38
|
};
|
|
32
39
|
export const diffdash_cli_parser = cli_make_parser({
|
|
33
40
|
cli_schema: diffdash_cli_schema,
|
|
@@ -6,11 +6,12 @@ import { diffdash_llm_model_details } from "./lib_diffdash_llm.js";
|
|
|
6
6
|
import { file_io_read_text } from "./lib_file_io.js";
|
|
7
7
|
import { file_is_file } from "./lib_file_is.js";
|
|
8
8
|
import { json5_parse } from "./lib_json5.js";
|
|
9
|
+
import { language_get_name_from_code } from "./lib_language.js";
|
|
9
10
|
import { llm_config_get, llm_config_get_all } from "./lib_llm_config.js";
|
|
10
11
|
import { llm_list_models } from "./lib_llm_list.js";
|
|
11
12
|
import { PACKAGE_NAME, PACKAGE_VERSION } from "./lib_package.js";
|
|
12
13
|
import { tell_plain } from "./lib_tell.js";
|
|
13
|
-
import { tui_quote_smart_single } from "./lib_tui_quote.js";
|
|
14
|
+
import { tui_quote_smart_single as qss } from "./lib_tui_quote.js";
|
|
14
15
|
const diffdash_config_file_schema = z
|
|
15
16
|
.object({
|
|
16
17
|
extra_prompts: z.string().array().optional(),
|
|
@@ -25,7 +26,7 @@ function diffdash_config_file_read(config) {
|
|
|
25
26
|
const parsed_json = json5_parse(config_content);
|
|
26
27
|
const validation_result = diffdash_config_file_schema.safeParse(parsed_json);
|
|
27
28
|
if (!validation_result.success) {
|
|
28
|
-
abort_with_error(`Unable to parse DiffDash config file: ${
|
|
29
|
+
abort_with_error(`Unable to parse DiffDash config file: ${qss(config_file_name)}`);
|
|
29
30
|
}
|
|
30
31
|
const data = validation_result.data;
|
|
31
32
|
if (data.extra_prompts) {
|
|
@@ -33,7 +34,7 @@ function diffdash_config_file_read(config) {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
export function diffdash_config_get() {
|
|
36
|
-
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, add_prefix, add_suffix, llm_list, llm_compare, llm_model, llm_excludes, no_secret_check, no_verify, force, just_output, silent, debug_llm_prompts, debug_llm_inputs, debug_llm_outputs, } = diffdash_cli_parsed_args;
|
|
37
|
+
const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, add_prefix, add_suffix, language, llm_list, llm_compare, llm_model, llm_excludes, no_secret_check, no_verify, force, just_output, silent, debug_llm_prompts, debug_llm_inputs, debug_llm_outputs, } = diffdash_cli_parsed_args;
|
|
37
38
|
if (version) {
|
|
38
39
|
tell_plain(`${PACKAGE_NAME} v${PACKAGE_VERSION}`);
|
|
39
40
|
process.exit(0);
|
|
@@ -42,6 +43,7 @@ export function diffdash_config_get() {
|
|
|
42
43
|
llm_list_models({ llm_model_details: diffdash_llm_model_details });
|
|
43
44
|
process.exit(0);
|
|
44
45
|
}
|
|
46
|
+
const language_name = language_get_name_from_code(language);
|
|
45
47
|
const llm_config = llm_config_get({ llm_model_details: diffdash_llm_model_details, llm_model_name: llm_model });
|
|
46
48
|
const all_llm_configs = llm_config_get_all({ llm_model_details: diffdash_llm_model_details, llm_excludes });
|
|
47
49
|
debug_channels.llm_prompts = debug_llm_prompts;
|
|
@@ -58,6 +60,7 @@ export function diffdash_config_get() {
|
|
|
58
60
|
disable_push,
|
|
59
61
|
add_prefix,
|
|
60
62
|
add_suffix,
|
|
63
|
+
language_name,
|
|
61
64
|
no_secret_check,
|
|
62
65
|
no_verify,
|
|
63
66
|
force,
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { env_get_substitute } from "./lib_env.js";
|
|
2
2
|
import { llm_model_get_choices, llm_model_get_details } from "./lib_llm_model.js";
|
|
3
|
-
const model_name_default = "gpt-
|
|
3
|
+
const model_name_default = "gpt-5-mini-minimal";
|
|
4
4
|
const model_name_options = [
|
|
5
|
-
"claude-
|
|
5
|
+
"claude-haiku-4.5", // fallback
|
|
6
6
|
"deepseek-chat",
|
|
7
|
-
"gemini-2.0-flash",
|
|
8
7
|
"gemini-2.5-flash",
|
|
9
|
-
"
|
|
8
|
+
"gemini-3-flash-preview-low",
|
|
9
|
+
"gpt-4.1-mini", // fallback
|
|
10
10
|
"gpt-4.1-nano",
|
|
11
11
|
"gpt-5-mini",
|
|
12
|
-
"gpt-5-mini-minimal", //
|
|
12
|
+
"gpt-5-mini-minimal", // the best
|
|
13
13
|
"gpt-5-nano",
|
|
14
14
|
"gpt-5-nano-minimal",
|
|
15
15
|
"grok-code-fast-1",
|
|
16
|
-
"llama-4-maverick@groq",
|
|
17
16
|
];
|
|
18
17
|
export const diffdash_llm_model_details = llm_model_get_details({ llm_model_names: model_name_options });
|
|
19
18
|
export const diffdash_llm_model_choices = llm_model_get_choices({ llm_model_details: diffdash_llm_model_details });
|
|
@@ -93,7 +93,7 @@ async function phase_status({ config, git }) {
|
|
|
93
93
|
}
|
|
94
94
|
async function phase_compare({ config, git }) {
|
|
95
95
|
const { silent } = config;
|
|
96
|
-
const { all_llm_configs, add_prefix, add_suffix, no_secret_check, extra_prompts } = config;
|
|
96
|
+
const { all_llm_configs, add_prefix, add_suffix, no_secret_check, extra_prompts, language_name } = config;
|
|
97
97
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
98
98
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
99
99
|
if (!no_secret_check) {
|
|
@@ -104,7 +104,7 @@ async function phase_compare({ config, git }) {
|
|
|
104
104
|
abort_with_error(`Aborting: ${error_get_message(error)}`);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
const inputs = { diffstat, diff, extra_prompts };
|
|
107
|
+
const inputs = { diffstat, diff, extra_prompts, language_name };
|
|
108
108
|
if (!silent) {
|
|
109
109
|
tell_action("Generating Git commit messages using all models in parallel");
|
|
110
110
|
}
|
|
@@ -129,7 +129,7 @@ async function phase_compare({ config, git }) {
|
|
|
129
129
|
llm_results_summary(all_results);
|
|
130
130
|
}
|
|
131
131
|
async function phase_generate({ config, git }) {
|
|
132
|
-
const { disable_preview, add_prefix, add_suffix, llm_config, no_secret_check, just_output, silent, extra_prompts } = config;
|
|
132
|
+
const { disable_preview, add_prefix, add_suffix, llm_config, no_secret_check, just_output, silent, extra_prompts, language_name, } = config;
|
|
133
133
|
const { llm_model_name } = llm_config;
|
|
134
134
|
const diffstat = await git_simple_staging_get_staged_diffstat(git);
|
|
135
135
|
const diff = await git_simple_staging_get_staged_diff(git);
|
|
@@ -141,7 +141,7 @@ async function phase_generate({ config, git }) {
|
|
|
141
141
|
abort_with_error(`Aborting: ${error_get_message(error)}`);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
const inputs = { diffstat, diff, extra_prompts };
|
|
144
|
+
const inputs = { diffstat, diff, extra_prompts, language_name };
|
|
145
145
|
if (!silent && !just_output) {
|
|
146
146
|
tell_action(`Generating the Git commit message using ${llm_model_name}`);
|
|
147
147
|
}
|
|
@@ -2,10 +2,12 @@ import { LF } from "./lib_char_control.js";
|
|
|
2
2
|
import { EMPTY } from "./lib_char_empty.js";
|
|
3
3
|
import { tell_warning } from "./lib_tell.js";
|
|
4
4
|
const LF_LF = LF + LF;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function portion_role(language_name) {
|
|
6
|
+
return (`
|
|
7
|
+
Your role is to generate a Git commit message in conversational ${language_name}.
|
|
7
8
|
The user does not want Conventional Commits - the summary line must be a normal sentence.
|
|
8
|
-
`.trim() + LF_LF;
|
|
9
|
+
`.trim() + LF_LF);
|
|
10
|
+
}
|
|
9
11
|
const portion_inputs = `
|
|
10
12
|
The user will send you a <diffstat> block, the output of a 'git diff --staged --stat' command.
|
|
11
13
|
The user will send you a <diff> block, the output of a 'git diff --staged' command.
|
|
@@ -54,7 +56,7 @@ Therefore, you must just output the Git message itself without any introductory
|
|
|
54
56
|
`.trim() + LF_LF;
|
|
55
57
|
export function git_message_prompt_get_system({ has_structured_json, inputs, }) {
|
|
56
58
|
let system_prompt = EMPTY;
|
|
57
|
-
system_prompt += portion_role;
|
|
59
|
+
system_prompt += portion_role(inputs.language_name);
|
|
58
60
|
system_prompt += portion_inputs;
|
|
59
61
|
system_prompt += portion_reminders;
|
|
60
62
|
system_prompt += portion_format(has_structured_json);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { abort_with_error } from "./lib_abort.js";
|
|
2
|
+
import { tui_quote_smart_single as qss } from "./lib_tui_quote.js";
|
|
3
|
+
export const LANGUAGE_CODE_ENGLISH = "en";
|
|
4
|
+
export const LANGUAGE_DETAILS = [
|
|
5
|
+
{ code: "af", name: "Afrikaans" },
|
|
6
|
+
{ code: "bg", name: "Bulgarian" },
|
|
7
|
+
{ code: "bn", name: "Bengali" },
|
|
8
|
+
{ code: "ca", name: "Catalan" },
|
|
9
|
+
{ code: "cs", name: "Czech" },
|
|
10
|
+
{ code: "da", name: "Danish" },
|
|
11
|
+
{ code: "de", name: "German" },
|
|
12
|
+
{ code: "el", name: "Greek" },
|
|
13
|
+
{ code: "en", name: "English" },
|
|
14
|
+
{ code: "es", name: "Spanish" },
|
|
15
|
+
{ code: "et", name: "Estonian" },
|
|
16
|
+
{ code: "fi", name: "Finnish" },
|
|
17
|
+
{ code: "fr", name: "French" },
|
|
18
|
+
{ code: "hi", name: "Hindi" },
|
|
19
|
+
{ code: "hr", name: "Croatian" },
|
|
20
|
+
{ code: "hu", name: "Hungarian" },
|
|
21
|
+
{ code: "id", name: "Indonesian" },
|
|
22
|
+
{ code: "it", name: "Italian" },
|
|
23
|
+
{ code: "ja", name: "Japanese" },
|
|
24
|
+
{ code: "ko", name: "Korean" },
|
|
25
|
+
{ code: "lt", name: "Lithuanian" },
|
|
26
|
+
{ code: "lv", name: "Latvian" },
|
|
27
|
+
{ code: "ms", name: "Malay" },
|
|
28
|
+
{ code: "nl", name: "Dutch" },
|
|
29
|
+
{ code: "no", name: "Norwegian" },
|
|
30
|
+
{ code: "pl", name: "Polish" },
|
|
31
|
+
{ code: "pt", name: "Portuguese" },
|
|
32
|
+
{ code: "ro", name: "Romanian" },
|
|
33
|
+
{ code: "ru", name: "Russian" },
|
|
34
|
+
{ code: "sk", name: "Slovak" },
|
|
35
|
+
{ code: "sl", name: "Slovenian" },
|
|
36
|
+
{ code: "sr", name: "Serbian" },
|
|
37
|
+
{ code: "sv", name: "Swedish" },
|
|
38
|
+
{ code: "sw", name: "Swahili" },
|
|
39
|
+
{ code: "ta", name: "Tamil" },
|
|
40
|
+
{ code: "te", name: "Telugu" },
|
|
41
|
+
{ code: "th", name: "Thai" },
|
|
42
|
+
{ code: "tr", name: "Turkish" },
|
|
43
|
+
{ code: "uk", name: "Ukrainian" },
|
|
44
|
+
{ code: "vi", name: "Vietnamese" },
|
|
45
|
+
{ code: "zh-CN", name: "Chinese (Simplified)" },
|
|
46
|
+
{ code: "zh-HK", name: "Chinese (Traditional)" },
|
|
47
|
+
{ code: "zh-SG", name: "Chinese (Simplified)" },
|
|
48
|
+
{ code: "zh-TW", name: "Chinese (Traditional)" },
|
|
49
|
+
];
|
|
50
|
+
export function language_get_code_choices() {
|
|
51
|
+
return LANGUAGE_DETAILS.map((language) => language.code);
|
|
52
|
+
}
|
|
53
|
+
export function language_get_name_from_code(code) {
|
|
54
|
+
const language_entry = LANGUAGE_DETAILS.find((language) => language.code === code);
|
|
55
|
+
if (!language_entry) {
|
|
56
|
+
abort_with_error(`Unknown language code: ${qss(code)}`);
|
|
57
|
+
}
|
|
58
|
+
return language_entry.name;
|
|
59
|
+
}
|
package/dist/src/lib_llm_api.js
CHANGED
|
@@ -5,6 +5,8 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
5
5
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
6
6
|
import { abort_with_error } from "./lib_abort.js";
|
|
7
7
|
import { env_get } from "./lib_env.js";
|
|
8
|
+
// Disable AI SDK warning logs temporarily
|
|
9
|
+
globalThis.AI_SDK_LOG_WARNINGS = false;
|
|
8
10
|
export function llm_api_get_api_key_env(llm_api_code) {
|
|
9
11
|
switch (llm_api_code) {
|
|
10
12
|
case "anthropic":
|
package/dist/src/lib_llm_chat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateText, Output, stepCountIs } from "ai";
|
|
2
2
|
import { debug_channels, debug_inspect_when } from "./lib_debug.js";
|
|
3
3
|
import { Duration } from "./lib_duration.js";
|
|
4
4
|
import { env_get_empty, env_get_substitute } from "./lib_env.js";
|
|
@@ -93,8 +93,7 @@ export async function llm_chat_generate_object({ llm_config, user_prompt, system
|
|
|
93
93
|
model: ai_sdk_language_model,
|
|
94
94
|
system: system_prompt,
|
|
95
95
|
prompt: user_prompt,
|
|
96
|
-
output:
|
|
97
|
-
schema,
|
|
96
|
+
output: Output.object({ schema }),
|
|
98
97
|
maxOutputTokens: max_output_tokens_env ?? max_output_tokens,
|
|
99
98
|
temperature,
|
|
100
99
|
providerOptions: provider_options,
|
|
@@ -102,8 +101,8 @@ export async function llm_chat_generate_object({ llm_config, user_prompt, system
|
|
|
102
101
|
};
|
|
103
102
|
debug_inspect_when(debug_channels.llm_inputs, llm_inputs, `LLM inputs object (for ${llm_model_name})`);
|
|
104
103
|
// This is liable to throw an error
|
|
105
|
-
const llm_outputs = await
|
|
104
|
+
const llm_outputs = await generateText(llm_inputs);
|
|
106
105
|
debug_inspect_when(debug_channels.llm_outputs, llm_outputs, `LLM outputs object (for ${llm_model_name})`);
|
|
107
|
-
const {
|
|
106
|
+
const { output: generated_object, usage: total_usage, providerMetadata: provider_metadata } = llm_outputs;
|
|
108
107
|
return { generated_object, total_usage, provider_metadata };
|
|
109
108
|
}
|
package/dist/src/lib_llm_list.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DOLLAR } from "./lib_char_punctuation.js";
|
|
2
2
|
import { stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
|
|
3
3
|
import { tell_info, tell_warning } from "./lib_tell.js";
|
|
4
|
-
import { TuiTable } from "./lib_tui_table.js";
|
|
4
|
+
import { LEFT, RIGHT, TuiTable } from "./lib_tui_table.js";
|
|
5
5
|
export function llm_list_models({ llm_model_details }) {
|
|
6
6
|
const headings = ["NAME", "API", "CONTEXT", "INPUT", "OUTPUT", "REASONING"];
|
|
7
|
-
const alignments = [
|
|
7
|
+
const alignments = [LEFT, LEFT, RIGHT, RIGHT, RIGHT, LEFT];
|
|
8
8
|
const table = new TuiTable({ headings, alignments, compact: true });
|
|
9
9
|
for (const detail of llm_model_details) {
|
|
10
10
|
const { llm_model_name, llm_api_code, context_window, cents_input, cents_output, default_reasoning } = detail;
|
|
@@ -54,6 +54,19 @@ export const LLM_MODEL_DETAILS = [
|
|
|
54
54
|
recommended_temperature: undefined,
|
|
55
55
|
provider_options: provider_options_anthropic({ thinking: false }),
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
llm_model_name: "claude-haiku-4.5",
|
|
59
|
+
llm_model_code: "claude-haiku-4-5",
|
|
60
|
+
llm_api_code: "anthropic",
|
|
61
|
+
context_window: 200_000,
|
|
62
|
+
max_output_tokens: 64_000,
|
|
63
|
+
cents_input: 100,
|
|
64
|
+
cents_output: 500,
|
|
65
|
+
default_reasoning: false,
|
|
66
|
+
has_structured_json: true,
|
|
67
|
+
recommended_temperature: undefined,
|
|
68
|
+
provider_options: provider_options_anthropic({ thinking: false }),
|
|
69
|
+
},
|
|
57
70
|
{
|
|
58
71
|
llm_model_name: "claude-opus-4.5",
|
|
59
72
|
llm_model_code: "claude-opus-4-5",
|
|
@@ -80,6 +93,32 @@ export const LLM_MODEL_DETAILS = [
|
|
|
80
93
|
recommended_temperature: undefined,
|
|
81
94
|
provider_options: provider_options_anthropic({ thinking: true }),
|
|
82
95
|
},
|
|
96
|
+
{
|
|
97
|
+
llm_model_name: "claude-opus-4.6",
|
|
98
|
+
llm_model_code: "claude-opus-4-6",
|
|
99
|
+
llm_api_code: "anthropic",
|
|
100
|
+
context_window: 200_000,
|
|
101
|
+
max_output_tokens: 64_000,
|
|
102
|
+
cents_input: 300, // for input tokens <= 200K
|
|
103
|
+
cents_output: 1500, // for input tokens <= 200K
|
|
104
|
+
default_reasoning: false,
|
|
105
|
+
has_structured_json: true,
|
|
106
|
+
recommended_temperature: undefined,
|
|
107
|
+
provider_options: provider_options_anthropic({ thinking: false }),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
llm_model_name: "claude-opus-4.6-thinking",
|
|
111
|
+
llm_model_code: "claude-opus-4-6",
|
|
112
|
+
llm_api_code: "anthropic",
|
|
113
|
+
context_window: 200_000,
|
|
114
|
+
max_output_tokens: 64_000 - 1024,
|
|
115
|
+
cents_input: 300, // for input tokens <= 200K
|
|
116
|
+
cents_output: 1500, // for input tokens <= 200K
|
|
117
|
+
default_reasoning: false,
|
|
118
|
+
has_structured_json: true,
|
|
119
|
+
recommended_temperature: undefined,
|
|
120
|
+
provider_options: provider_options_anthropic({ thinking: true }),
|
|
121
|
+
},
|
|
83
122
|
{
|
|
84
123
|
llm_model_name: "claude-sonnet-4",
|
|
85
124
|
llm_model_code: "claude-sonnet-4-0",
|
|
@@ -184,19 +223,6 @@ export const LLM_MODEL_DETAILS = [
|
|
|
184
223
|
recommended_temperature: undefined,
|
|
185
224
|
provider_options: provider_options_openrouter({ only: "mistral" }),
|
|
186
225
|
},
|
|
187
|
-
{
|
|
188
|
-
llm_model_name: "gemini-2.0-flash",
|
|
189
|
-
llm_model_code: "gemini-2.0-flash",
|
|
190
|
-
llm_api_code: "google",
|
|
191
|
-
context_window: 1_048_576,
|
|
192
|
-
max_output_tokens: 8192,
|
|
193
|
-
cents_input: 10,
|
|
194
|
-
cents_output: 40,
|
|
195
|
-
default_reasoning: false,
|
|
196
|
-
has_structured_json: true,
|
|
197
|
-
recommended_temperature: undefined,
|
|
198
|
-
provider_options: undefined,
|
|
199
|
-
},
|
|
200
226
|
{
|
|
201
227
|
llm_model_name: "gemini-2.5-flash",
|
|
202
228
|
llm_model_code: "gemini-2.5-flash",
|
|
@@ -223,6 +249,32 @@ export const LLM_MODEL_DETAILS = [
|
|
|
223
249
|
recommended_temperature: undefined,
|
|
224
250
|
provider_options: undefined,
|
|
225
251
|
},
|
|
252
|
+
{
|
|
253
|
+
llm_model_name: "gemini-3-flash-preview-high",
|
|
254
|
+
llm_model_code: "gemini-3-flash-preview",
|
|
255
|
+
llm_api_code: "google",
|
|
256
|
+
context_window: 1_048_576,
|
|
257
|
+
max_output_tokens: 65_536,
|
|
258
|
+
cents_input: 50,
|
|
259
|
+
cents_output: 300,
|
|
260
|
+
default_reasoning: true,
|
|
261
|
+
has_structured_json: true,
|
|
262
|
+
recommended_temperature: undefined,
|
|
263
|
+
provider_options: provider_options_google({ thinking_level: "high" }),
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
llm_model_name: "gemini-3-flash-preview-low",
|
|
267
|
+
llm_model_code: "gemini-3-flash-preview",
|
|
268
|
+
llm_api_code: "google",
|
|
269
|
+
context_window: 1_048_576,
|
|
270
|
+
max_output_tokens: 65_536,
|
|
271
|
+
cents_input: 50,
|
|
272
|
+
cents_output: 300,
|
|
273
|
+
default_reasoning: true,
|
|
274
|
+
has_structured_json: true,
|
|
275
|
+
recommended_temperature: undefined,
|
|
276
|
+
provider_options: provider_options_google({ thinking_level: "low" }),
|
|
277
|
+
},
|
|
226
278
|
{
|
|
227
279
|
llm_model_name: "gemini-3-pro-preview-high",
|
|
228
280
|
llm_model_code: "gemini-3-pro-preview",
|
|
@@ -250,11 +302,11 @@ export const LLM_MODEL_DETAILS = [
|
|
|
250
302
|
provider_options: provider_options_google({ thinking_level: "low" }),
|
|
251
303
|
},
|
|
252
304
|
{
|
|
253
|
-
llm_model_name: "glm-4.
|
|
254
|
-
llm_model_code: "z-ai/glm-4.
|
|
305
|
+
llm_model_name: "glm-4.7@z-ai",
|
|
306
|
+
llm_model_code: "z-ai/glm-4.7",
|
|
255
307
|
llm_api_code: "openrouter",
|
|
256
|
-
context_window:
|
|
257
|
-
max_output_tokens:
|
|
308
|
+
context_window: 200_000,
|
|
309
|
+
max_output_tokens: 131_072,
|
|
258
310
|
cents_input: 60,
|
|
259
311
|
cents_output: 220,
|
|
260
312
|
default_reasoning: true,
|
|
@@ -263,26 +315,13 @@ export const LLM_MODEL_DETAILS = [
|
|
|
263
315
|
provider_options: provider_options_openrouter({ only: "z-ai" }),
|
|
264
316
|
},
|
|
265
317
|
{
|
|
266
|
-
llm_model_name: "glm-4.
|
|
267
|
-
llm_model_code: "z-ai/glm-4.
|
|
268
|
-
llm_api_code: "openrouter",
|
|
269
|
-
context_window: 128_000,
|
|
270
|
-
max_output_tokens: 96_000,
|
|
271
|
-
cents_input: 20,
|
|
272
|
-
cents_output: 110,
|
|
273
|
-
default_reasoning: true,
|
|
274
|
-
has_structured_json: false,
|
|
275
|
-
recommended_temperature: undefined,
|
|
276
|
-
provider_options: provider_options_openrouter({ only: "z-ai" }),
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
llm_model_name: "glm-4.6@z-ai",
|
|
280
|
-
llm_model_code: "z-ai/glm-4.6",
|
|
318
|
+
llm_model_name: "glm-4.7-flash@z-ai",
|
|
319
|
+
llm_model_code: "z-ai/glm-4.7-flash",
|
|
281
320
|
llm_api_code: "openrouter",
|
|
282
|
-
context_window:
|
|
283
|
-
max_output_tokens:
|
|
284
|
-
cents_input:
|
|
285
|
-
cents_output:
|
|
321
|
+
context_window: 200_000,
|
|
322
|
+
max_output_tokens: 131_072,
|
|
323
|
+
cents_input: 7,
|
|
324
|
+
cents_output: 40,
|
|
286
325
|
default_reasoning: true,
|
|
287
326
|
has_structured_json: false,
|
|
288
327
|
recommended_temperature: undefined,
|
|
@@ -561,6 +600,19 @@ export const LLM_MODEL_DETAILS = [
|
|
|
561
600
|
recommended_temperature: undefined,
|
|
562
601
|
provider_options: provider_options_openrouter({ only: "groq" }),
|
|
563
602
|
},
|
|
603
|
+
{
|
|
604
|
+
llm_model_name: "kimi-k2.5",
|
|
605
|
+
llm_model_code: "moonshotai/kimi-k2.5",
|
|
606
|
+
llm_api_code: "openrouter",
|
|
607
|
+
context_window: 131_072,
|
|
608
|
+
max_output_tokens: 131_072,
|
|
609
|
+
cents_input: 60,
|
|
610
|
+
cents_output: 300,
|
|
611
|
+
default_reasoning: false,
|
|
612
|
+
has_structured_json: true,
|
|
613
|
+
recommended_temperature: undefined,
|
|
614
|
+
provider_options: provider_options_openrouter({ only: "moonshotai" }),
|
|
615
|
+
},
|
|
564
616
|
{
|
|
565
617
|
llm_model_name: "llama-4-maverick@groq",
|
|
566
618
|
llm_model_code: "meta-llama/llama-4-maverick",
|
|
@@ -626,6 +678,32 @@ export const LLM_MODEL_DETAILS = [
|
|
|
626
678
|
recommended_temperature: undefined,
|
|
627
679
|
provider_options: undefined,
|
|
628
680
|
},
|
|
681
|
+
{
|
|
682
|
+
llm_model_name: "minimax-m2.1",
|
|
683
|
+
llm_model_code: "minimax/minimax-m2.1",
|
|
684
|
+
llm_api_code: "openrouter",
|
|
685
|
+
context_window: 204_800,
|
|
686
|
+
max_output_tokens: 131_072,
|
|
687
|
+
cents_input: 30,
|
|
688
|
+
cents_output: 120,
|
|
689
|
+
default_reasoning: false,
|
|
690
|
+
has_structured_json: false,
|
|
691
|
+
recommended_temperature: undefined,
|
|
692
|
+
provider_options: provider_options_openrouter({ only: "minimax" }),
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
llm_model_name: "minimax-m2.5",
|
|
696
|
+
llm_model_code: "minimax/minimax-m2.5",
|
|
697
|
+
llm_api_code: "openrouter",
|
|
698
|
+
context_window: 204_800,
|
|
699
|
+
max_output_tokens: 131_072,
|
|
700
|
+
cents_input: 30,
|
|
701
|
+
cents_output: 120,
|
|
702
|
+
default_reasoning: false,
|
|
703
|
+
has_structured_json: false,
|
|
704
|
+
recommended_temperature: undefined,
|
|
705
|
+
provider_options: provider_options_openrouter({ only: "minimax" }),
|
|
706
|
+
},
|
|
629
707
|
{
|
|
630
708
|
llm_model_name: "mistral-medium-3.1",
|
|
631
709
|
llm_model_code: "mistralai/mistral-medium-3.1",
|
|
@@ -26,19 +26,20 @@ export function llm_results_summary(all_results) {
|
|
|
26
26
|
const { default_reasoning } = llm_model_detail;
|
|
27
27
|
const { outputs } = result;
|
|
28
28
|
const { total_usage, provider_metadata } = outputs;
|
|
29
|
+
const { reasoningTokens: reasoning_tokens } = total_usage.outputTokenDetails;
|
|
29
30
|
const openrouter_provider = provider_metadata?.["openrouter"]?.["provider"];
|
|
30
31
|
const tui_model = tui_justify_left(max_length_model, llm_model_name);
|
|
31
32
|
const tui_seconds = tui_number_plain({ num: seconds, justify_left: 3 });
|
|
32
33
|
const tui_input = tui_number_plain({ num: total_usage.inputTokens, justify_left: 5 });
|
|
33
34
|
const tui_output = tui_number_plain({ num: total_usage.outputTokens, justify_left: 5 });
|
|
34
|
-
const tui_reasoning = tui_number_plain({ num:
|
|
35
|
+
const tui_reasoning = tui_number_plain({ num: reasoning_tokens, justify_left: 5, none: QUESTION });
|
|
35
36
|
const tui_provider = tui_none_blank(openrouter_provider);
|
|
36
37
|
const segments = [];
|
|
37
38
|
segments.push(tui_model);
|
|
38
39
|
segments.push(`seconds=${tui_seconds}`);
|
|
39
40
|
segments.push(`input=${tui_input}`);
|
|
40
41
|
segments.push(`output=${tui_output}`);
|
|
41
|
-
if (default_reasoning ||
|
|
42
|
+
if (default_reasoning || reasoning_tokens !== undefined) {
|
|
42
43
|
segments.push(`reasoning=${tui_reasoning}`);
|
|
43
44
|
}
|
|
44
45
|
if (openrouter_provider) {
|
|
@@ -3,7 +3,7 @@ import { ansi_yellow } from "./lib_ansi.js";
|
|
|
3
3
|
import { DASH } from "./lib_char_punctuation.js";
|
|
4
4
|
import { text_split_lines } from "./lib_text.js";
|
|
5
5
|
import { tui_confirm } from "./lib_tui_confirm.js";
|
|
6
|
-
import { tui_quote_smart_single } from "./lib_tui_quote.js";
|
|
6
|
+
import { tui_quote_smart_single as qss } from "./lib_tui_quote.js";
|
|
7
7
|
const regexp_word_global = createRegExp(oneOrMore(anyOf(wordChar, exactly(DASH))), [global]);
|
|
8
8
|
const regexp_segment_global = createRegExp(oneOrMore(anyOf(letter, digit)), [global]);
|
|
9
9
|
const regexp_identifier_exactly = createRegExp(anyOf(
|
|
@@ -66,7 +66,7 @@ async function is_secret_segment(segment, not_secret_segments, interactive) {
|
|
|
66
66
|
}
|
|
67
67
|
if (interactive) {
|
|
68
68
|
const confirmed_is_secret = await tui_confirm({
|
|
69
|
-
question: `Is ${
|
|
69
|
+
question: `Is ${qss(segment)} a secret?`,
|
|
70
70
|
default: false,
|
|
71
71
|
style_message: ansi_yellow,
|
|
72
72
|
});
|
|
@@ -82,7 +82,7 @@ export async function secret_check({ text, interactive }) {
|
|
|
82
82
|
const lines = text_split_lines(text);
|
|
83
83
|
for (const line of lines.toReversed()) {
|
|
84
84
|
if (is_secret_line(line)) {
|
|
85
|
-
throw new Error(`Secret detected: ${
|
|
85
|
+
throw new Error(`Secret detected: ${qss(line.trim())}`);
|
|
86
86
|
}
|
|
87
87
|
const words = line.match(regexp_word_global);
|
|
88
88
|
const segments = line.match(regexp_segment_global);
|
|
@@ -97,12 +97,12 @@ export async function secret_check({ text, interactive }) {
|
|
|
97
97
|
}
|
|
98
98
|
for (const word of words) {
|
|
99
99
|
if (is_secret_word(word)) {
|
|
100
|
-
throw new Error(`Secret detected: ${
|
|
100
|
+
throw new Error(`Secret detected: ${qss(word)}`);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
for (const segment of segments) {
|
|
104
104
|
if (await is_secret_segment(segment, not_secret_segments, interactive)) {
|
|
105
|
-
throw new Error(`Secret detected: ${
|
|
105
|
+
throw new Error(`Secret detected: ${qss(segment)}`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import cli_table3 from "cli-table3";
|
|
2
2
|
import { abort_with_error } from "./lib_abort.js";
|
|
3
3
|
import { ansi_bold } from "./lib_ansi.js";
|
|
4
|
+
export const LEFT = "left";
|
|
5
|
+
export const CENTER = "center";
|
|
6
|
+
export const RIGHT = "right";
|
|
4
7
|
export class TuiTable {
|
|
5
8
|
table;
|
|
6
9
|
columns_total;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A command-line tool to generate Git commit messages using AI",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"author": "John Owen Nixon",
|
|
@@ -18,48 +18,17 @@
|
|
|
18
18
|
"bin": {
|
|
19
19
|
"diffdash": "dist/src/diffdash.js"
|
|
20
20
|
},
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"@ai-sdk/anthropic": "2.0.53",
|
|
23
|
-
"@ai-sdk/deepseek": "1.0.31",
|
|
24
|
-
"@ai-sdk/google": "2.0.44",
|
|
25
|
-
"@ai-sdk/openai": "2.0.77",
|
|
26
|
-
"@inquirer/prompts": "8.0.2",
|
|
27
|
-
"@openrouter/ai-sdk-provider": "1.2.3",
|
|
28
|
-
"ai": "5.0.102",
|
|
29
|
-
"ansis": "4.2.0",
|
|
30
|
-
"argparse": "2.0.1",
|
|
31
|
-
"cli-table3": "0.6.5",
|
|
32
|
-
"json5": "2.2.3",
|
|
33
|
-
"magic-regexp": "0.10.0",
|
|
34
|
-
"simple-git": "3.30.0",
|
|
35
|
-
"zod": "4.1.13"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@biomejs/biome": "2.3.8",
|
|
39
|
-
"@candide/tsgolint": "1.4.0",
|
|
40
|
-
"@johnowennixon/add-shebangs": "1.1.0",
|
|
41
|
-
"@johnowennixon/chmodx": "2.1.0",
|
|
42
|
-
"@types/argparse": "2.0.17",
|
|
43
|
-
"@types/node": "24.10.1",
|
|
44
|
-
"@typescript/native-preview": "7.0.0-dev.20251022.1",
|
|
45
|
-
"knip": "5.71.0",
|
|
46
|
-
"markdownlint-cli2": "0.19.1",
|
|
47
|
-
"npm-run-all2": "8.0.4",
|
|
48
|
-
"oxlint": "1.31.0",
|
|
49
|
-
"rimraf": "6.1.2",
|
|
50
|
-
"typescript": "5.9.3"
|
|
51
|
-
},
|
|
52
21
|
"scripts": {
|
|
53
22
|
"build": "run-s -ls build:clean build:tsc build:shebang build:chmod",
|
|
54
23
|
"build:chmod": "echo 'Changing bin files to be executable' && chmodx --package",
|
|
55
24
|
"build:clean": "echo 'Removing dist' && rimraf dist",
|
|
56
25
|
"build:shebang": "echo 'Fixing the shebangs' && add-shebangs --node --exclude 'dist/**/lib_*.js' 'dist/**/*.js'",
|
|
57
|
-
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc
|
|
26
|
+
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc",
|
|
58
27
|
"build:tsgo": "echo 'Transpiling TypeScript to dist (using tsgo)' && tsgo || (rimraf dist && false)",
|
|
59
28
|
"fix": "run-s -ls fix:biome fix:markdownlint",
|
|
60
29
|
"fix:biome": "echo 'Fixing with Biome' && biome check --write",
|
|
61
30
|
"fix:docbot": "echo 'Fixing with DocBot' && docbot --prune --generate",
|
|
62
|
-
"fix:markdownlint": "echo 'Fixing with
|
|
31
|
+
"fix:markdownlint": "echo 'Fixing with Markdownlint' && markdownlint-cli2 '**/*.md' --fix",
|
|
63
32
|
"fix:oxlint": "echo 'Fixing with Oxlint' && oxlint --fix",
|
|
64
33
|
"lint": "run-s -ls lint:biome lint:oxlint lint:tsgolint lint:knip lint:markdownlint",
|
|
65
34
|
"lint:biome": "echo 'Linting with Biome' && biome check",
|
|
@@ -67,9 +36,40 @@
|
|
|
67
36
|
"lint:knip": "echo 'Linting with Knip' && knip",
|
|
68
37
|
"lint:markdownlint": "echo 'Linting with Markdownlint' && markdownlint-cli2 '**/*.md'",
|
|
69
38
|
"lint:oxlint": "echo 'Linting with Oxlint' && oxlint",
|
|
70
|
-
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit
|
|
39
|
+
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit",
|
|
71
40
|
"lint:tsgo": "echo 'Linting with tsgo' && tsgo --noEmit",
|
|
72
41
|
"lint:tsgolint": "echo 'Linting with tsgolint' && candide-tsgolint",
|
|
73
42
|
"test": "run-s -ls lint build"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@ai-sdk/anthropic": "3.0.44",
|
|
46
|
+
"@ai-sdk/deepseek": "2.0.20",
|
|
47
|
+
"@ai-sdk/google": "3.0.29",
|
|
48
|
+
"@ai-sdk/openai": "3.0.29",
|
|
49
|
+
"@inquirer/prompts": "8.2.1",
|
|
50
|
+
"@openrouter/ai-sdk-provider": "2.2.3",
|
|
51
|
+
"ai": "6.0.86",
|
|
52
|
+
"ansis": "4.2.0",
|
|
53
|
+
"argparse": "2.0.1",
|
|
54
|
+
"cli-table3": "0.6.5",
|
|
55
|
+
"json5": "2.2.3",
|
|
56
|
+
"magic-regexp": "0.10.0",
|
|
57
|
+
"simple-git": "3.31.1",
|
|
58
|
+
"zod": "4.3.6"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@biomejs/biome": "2.3.13",
|
|
62
|
+
"@candide/tsgolint": "1.5.0",
|
|
63
|
+
"@johnowennixon/add-shebangs": "1.1.0",
|
|
64
|
+
"@johnowennixon/chmodx": "2.1.0",
|
|
65
|
+
"@types/argparse": "2.0.17",
|
|
66
|
+
"@types/node": "25.2.3",
|
|
67
|
+
"@typescript/native-preview": "7.0.0-dev.20260103.1",
|
|
68
|
+
"knip": "5.83.1",
|
|
69
|
+
"markdownlint-cli2": "0.20.0",
|
|
70
|
+
"npm-run-all2": "8.0.4",
|
|
71
|
+
"oxlint": "1.47.0",
|
|
72
|
+
"rimraf": "6.1.2",
|
|
73
|
+
"typescript": "5.9.3"
|
|
74
74
|
}
|
|
75
|
-
}
|
|
75
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|