@johnowennixon/diffdash 1.4.0 → 1.5.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 CHANGED
@@ -12,6 +12,7 @@ A command-line tool to generate Git commit messages using AI.
12
12
  * Able to add a prefix or suffix to the summary line
13
13
  * Optionally select from a choice of LLM models
14
14
  * Compare messages generated from all configured models
15
+ * Can just output the commit message for use in scripts
15
16
  * Configuration using standard API provider environment variables
16
17
  * Uses the Vercel AI SDK
17
18
  * Uses structured JSON with compatible models
@@ -59,12 +60,6 @@ export GEMINI_API_KEY=your-api-key
59
60
  # Basic usage (uses defaults)
60
61
  diffdash
61
62
 
62
- # Add a prefix to the commit message summary line
63
- diffdash --add-prefix "[FIX]"
64
-
65
- # Add a suffix to the commit message summary line
66
- diffdash --add-suffix "(closes #123)"
67
-
68
63
  # Automatically stage all changes, but still prompt for commit and push
69
64
  diffdash --auto-add
70
65
 
@@ -92,6 +87,12 @@ diffdash --disable-push
92
87
  # Skip git hooks when pushing
93
88
  diffdash --no-verify
94
89
 
90
+ # Add a prefix to the commit message summary line
91
+ diffdash --add-prefix "[FIX]"
92
+
93
+ # Add a suffix to the commit message summary line
94
+ diffdash --add-suffix "(closes #123)"
95
+
95
96
  # Display commit messages generated by all models
96
97
  diffdash --llm-compare
97
98
 
@@ -101,6 +102,9 @@ diffdash --llm-fallback
101
102
  # Specify the LLM model by name
102
103
  diffdash --llm-model claude-3.5-haiku
103
104
 
105
+ # Just output the commit message for use in scripts
106
+ diffdash --just-output
107
+
104
108
  # Debug options
105
109
  diffdash --debug-llm-inputs --debug-llm-outputs
106
110
  ```
@@ -113,8 +117,6 @@ All command-line arguments are optional.
113
117
  |--------|-------------|
114
118
  | `--help` | show a help message and exit |
115
119
  | `--version` | show program version information and exit |
116
- | `--add-prefix PREFIX` | add a prefix to the commit message summary line |
117
- | `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
118
120
  | `--auto-add` | automatically stage all changes without confirmation |
119
121
  | `--auto-commit` | automatically commit changes without confirmation |
120
122
  | `--auto-push` | automatically push changes after commit without confirmation |
@@ -125,12 +127,15 @@ All command-line arguments are optional.
125
127
  | `--disable-push` | disable pushing changes - exit after making the commit |
126
128
  | `--push-no-verify` | bypass git hooks when pushing to Git |
127
129
  | `--push-force` | apply force when pushing to Git |
130
+ | `--add-prefix PREFIX` | add a prefix to the commit message summary line |
131
+ | `--add-suffix SUFFIX` | add a suffix to the commit message summary line |
128
132
  | `--llm-list` | display a list of available Large Language Models and exit |
129
133
  | `--llm-compare` | compare the generated messages from all models - but do not commit |
130
134
  | `--llm-router` | prefer to access the LLM via a router rather than direct |
131
135
  | `--llm-fallback` | use the fallback LLM model instead of the default |
132
136
  | `--llm-model MODEL` | choose the LLM model by name (the default is normally best) |
133
137
  | `--llm-excludes MODELS` | models to exclude from comparison (comma separated) |
138
+ | `--just-output` | just output the commit message for use in scripts |
134
139
  | `--silent` | suppress all normal output - errors and aborts still display |
135
140
  | `--debug-llm-inputs` | show inputs (including all prompts) sent to the LLM |
136
141
  | `--debug-llm-outputs` | show outputs received from the LLM |
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnowennixon/diffdash",
3
- "version": "1.3.0",
3
+ "version": "1.5.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",
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@ai-sdk/anthropic": "1.2.12",
43
43
  "@ai-sdk/deepseek": "0.2.14",
44
- "@ai-sdk/google": "1.2.21",
44
+ "@ai-sdk/google": "1.2.22",
45
45
  "@ai-sdk/openai": "1.3.22",
46
46
  "@openrouter/ai-sdk-provider": "0.7.2",
47
47
  "@requesty/ai-sdk": "0.0.9",
@@ -55,15 +55,15 @@
55
55
  "devDependencies": {
56
56
  "@biomejs/biome": "2.0.6",
57
57
  "@eslint/eslintrc": "3.3.1",
58
- "@eslint/js": "9.30.0",
58
+ "@eslint/js": "9.30.1",
59
59
  "@johnowennixon/add-shebangs": "1.1.0",
60
60
  "@johnowennixon/chmodx": "2.0.0",
61
61
  "@stylistic/eslint-plugin": "5.1.0",
62
62
  "@types/argparse": "2.0.17",
63
- "@types/node": "24.0.8",
63
+ "@types/node": "24.0.10",
64
64
  "@typescript-eslint/eslint-plugin": "8.35.1",
65
65
  "@typescript-eslint/parser": "8.35.1",
66
- "eslint": "9.30.0",
66
+ "eslint": "9.30.1",
67
67
  "eslint-import-resolver-typescript": "4.4.4",
68
68
  "eslint-plugin-import-x": "4.16.1",
69
69
  "eslint-plugin-sonarjs": "3.0.4",
@@ -72,7 +72,7 @@
72
72
  "knip": "5.61.3",
73
73
  "markdownlint-cli2": "0.18.1",
74
74
  "npm-run-all2": "8.0.4",
75
- "oxlint": "1.4.0",
75
+ "oxlint": "1.5.0",
76
76
  "rimraf": "6.0.1",
77
77
  "typescript": "5.8.3",
78
78
  "typescript-eslint": "8.35.1"
@@ -1,24 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import { ansi_blue, ansi_italic, ansi_red } from "./lib_ansi.js";
3
3
  import { diffdash_config_get } from "./lib_diffdash_config.js";
4
- import { diffdash_sequence_compare, diffdash_sequence_normal } from "./lib_diffdash_sequence.js";
4
+ import { diffdash_sequence_compare, diffdash_sequence_normal, diffdash_sequence_output } from "./lib_diffdash_sequence.js";
5
5
  import { error_abort } from "./lib_error.js";
6
6
  import { tell_okay, tell_plain } from "./lib_tell.js";
7
7
  async function main() {
8
8
  const config = diffdash_config_get();
9
- const { llm_compare, silent } = config;
10
- if (!silent) {
9
+ const { silent, just_output, llm_compare } = config;
10
+ if (!silent && !just_output) {
11
11
  const diffdash = ansi_italic(ansi_blue("Diff") + ansi_red("Dash"));
12
12
  tell_plain(`This is ${diffdash} - the fast AI Git commit tool`);
13
13
  }
14
- // eslint-disable-next-line unicorn/prefer-ternary
15
- if (llm_compare) {
14
+ if (just_output) {
15
+ await diffdash_sequence_output(config);
16
+ }
17
+ else if (llm_compare) {
16
18
  await diffdash_sequence_compare(config);
17
19
  }
18
20
  else {
19
21
  await diffdash_sequence_normal(config);
20
22
  }
21
- if (!silent) {
23
+ if (!silent && !just_output) {
22
24
  tell_okay();
23
25
  }
24
26
  }
@@ -2,8 +2,6 @@ import { cli_boolean, cli_choice_default, cli_make_parser, cli_string } from "./
2
2
  import { diffdash_llm_model_choices, diffdash_llm_model_default, diffdash_llm_model_fallback, } from "./lib_diffdash_llm.js";
3
3
  const diffdash_cli_schema = {
4
4
  version: cli_boolean({ help: "show program version information and exit" }),
5
- add_prefix: cli_string({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
6
- add_suffix: cli_string({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
7
5
  auto_add: cli_boolean({ help: "automatically stage all changes without confirmation" }),
8
6
  auto_commit: cli_boolean({ help: "automatically commit changes without confirmation" }),
9
7
  auto_push: cli_boolean({ help: "automatically push changes after commit without confirmation" }),
@@ -14,6 +12,8 @@ const diffdash_cli_schema = {
14
12
  disable_push: cli_boolean({ help: "disable pushing changes - exit after making the commit" }),
15
13
  push_no_verify: cli_boolean({ help: "bypass git hooks when pushing to Git" }),
16
14
  push_force: cli_boolean({ help: "apply force when pushing to Git" }),
15
+ add_prefix: cli_string({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
16
+ add_suffix: cli_string({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
17
17
  llm_list: cli_boolean({ help: "display a list of available Large Language Models and exit" }),
18
18
  llm_compare: cli_boolean({ help: "compare the generated messages from all models - but do not commit" }),
19
19
  llm_router: cli_boolean({ help: "prefer to access the LLM via a router rather than direct" }),
@@ -24,6 +24,7 @@ const diffdash_cli_schema = {
24
24
  default: diffdash_llm_model_default,
25
25
  }),
26
26
  llm_excludes: cli_string({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
27
+ just_output: cli_boolean({ help: "just output the commit message for use in scripts" }),
27
28
  silent: cli_boolean({ help: "suppress all normal output - errors and aborts still display" }),
28
29
  debug_llm_inputs: cli_boolean({ help: "debug inputs (including all prompts) sent to the LLM" }),
29
30
  debug_llm_outputs: cli_boolean({ help: "debug outputs received from the LLM" }),
@@ -7,7 +7,7 @@ import { PACKAGE_NAME, PACKAGE_VERSION } from "./lib_package.js";
7
7
  import { tell_plain } from "./lib_tell.js";
8
8
  export function diffdash_config_get() {
9
9
  const pa = diffdash_cli_parser.parsed_args;
10
- const { version, add_prefix, add_suffix, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, push_no_verify, push_force, llm_list, llm_compare, llm_router, llm_fallback, llm_model, llm_excludes, silent, debug_llm_inputs, debug_llm_outputs, } = pa;
10
+ const { version, auto_add, auto_commit, auto_push, disable_add, disable_commit, disable_preview, disable_status, disable_push, push_no_verify, push_force, add_prefix, add_suffix, llm_list, llm_compare, llm_router, llm_fallback, llm_model, llm_excludes, just_output, silent, debug_llm_inputs, debug_llm_outputs, } = pa;
11
11
  if (version) {
12
12
  tell_plain(`${PACKAGE_NAME} v${PACKAGE_VERSION}`);
13
13
  process.exit(0);
@@ -30,8 +30,6 @@ export function diffdash_config_get() {
30
30
  debug_channels.llm_inputs = debug_llm_inputs;
31
31
  debug_channels.llm_outputs = debug_llm_outputs;
32
32
  const config = {
33
- add_prefix,
34
- add_suffix,
35
33
  auto_add,
36
34
  auto_commit,
37
35
  auto_push,
@@ -42,9 +40,12 @@ export function diffdash_config_get() {
42
40
  disable_push,
43
41
  push_no_verify,
44
42
  push_force,
43
+ add_prefix,
44
+ add_suffix,
45
45
  llm_compare,
46
46
  llm_config,
47
47
  all_llm_configs,
48
+ just_output,
48
49
  silent,
49
50
  };
50
51
  debug_inspect_when(debug_channels.config, config, "config");
@@ -8,7 +8,7 @@ import { git_message_validate_check, git_message_validate_get_result } from "./l
8
8
  import { git_simple_open_check_not_bare, git_simple_open_git_repo } from "./lib_git_simple_open.js";
9
9
  import { git_simple_staging_create_commit, git_simple_staging_get_staged_diff, git_simple_staging_get_staged_diffstat, git_simple_staging_has_staged_changes, git_simple_staging_has_unstaged_changes, git_simple_staging_push_to_remote, git_simple_staging_stage_all_changes, } from "./lib_git_simple_staging.js";
10
10
  import { llm_config_get_model_via } from "./lib_llm_config.js";
11
- import { stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
11
+ import { stdio_write_stdout, stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
12
12
  import { tell_action, tell_info, tell_plain, tell_success, tell_warning } from "./lib_tell.js";
13
13
  import { tui_justify_left } from "./lib_tui_justify.js";
14
14
  import { tui_readline_confirm } from "./lib_tui_readline.js";
@@ -115,10 +115,10 @@ async function phase_compare({ config, git }) {
115
115
  git_message_display({ git_message, teller });
116
116
  }
117
117
  }
118
- async function phase_commit({ config, git }) {
119
- const { add_prefix, add_suffix, auto_commit, disable_commit, disable_preview, silent, llm_config } = config;
118
+ async function phase_generate({ config, git }) {
119
+ const { disable_preview, add_prefix, add_suffix, llm_config, just_output, silent } = config;
120
120
  const model_via = llm_config_get_model_via(llm_config);
121
- if (!silent) {
121
+ if (!silent && !just_output) {
122
122
  tell_action(`Generating the Git commit message using ${model_via}`);
123
123
  }
124
124
  const diffstat = await git_simple_staging_get_staged_diffstat(git);
@@ -127,18 +127,22 @@ async function phase_commit({ config, git }) {
127
127
  const result = await git_message_generate_result({ llm_config, inputs });
128
128
  const { error_text } = result;
129
129
  let { git_message } = result;
130
- if (error_text) {
130
+ if (error_text || git_message === null) {
131
131
  abort_with_error(`Failed to generate a commit message using ${model_via}: ${error_text}`);
132
132
  }
133
- if (!git_message) {
134
- return;
135
- }
136
133
  git_message_validate_check(git_message);
137
134
  git_message = diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix });
138
135
  git_message = diffdash_add_footer({ git_message, llm_config });
139
- if (!disable_preview && !silent) {
136
+ if (just_output) {
137
+ stdio_write_stdout(git_message);
138
+ }
139
+ else if (!disable_preview && !silent) {
140
140
  git_message_display({ git_message, teller: tell_plain });
141
141
  }
142
+ return git_message;
143
+ }
144
+ async function phase_commit({ config, git, git_message, }) {
145
+ const { auto_commit, disable_commit, silent } = config;
142
146
  if (disable_commit) {
143
147
  return;
144
148
  }
@@ -188,9 +192,14 @@ export async function diffdash_sequence_normal(config) {
188
192
  const git = await phase_open();
189
193
  await phase_add({ config, git });
190
194
  await phase_status({ config, git });
191
- await phase_commit({ config, git });
195
+ const git_message = await phase_generate({ config, git });
196
+ await phase_commit({ config, git, git_message });
192
197
  await phase_push({ config, git });
193
198
  }
199
+ export async function diffdash_sequence_output(config) {
200
+ const git = await phase_open();
201
+ await phase_generate({ config, git });
202
+ }
194
203
  export async function diffdash_sequence_compare(config) {
195
204
  const git = await phase_open();
196
205
  await phase_add({ config, git });
@@ -8,6 +8,13 @@ export function error_console(error) {
8
8
  export function error_get_text(error) {
9
9
  return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
10
10
  }
11
+ export function error_get_message(error) {
12
+ return error instanceof Error
13
+ ? error.name === "Error"
14
+ ? error.message
15
+ : `${error.name}: ${error.message}`
16
+ : String(error);
17
+ }
11
18
  export function error_abort(error) {
12
19
  const message = `Unhandled error: ${error_get_text(error)}`;
13
20
  abort_with_error(message);
@@ -192,7 +192,7 @@ const LLM_MODEL_DETAILS = [
192
192
  llm_provider: null,
193
193
  llm_model_code_direct: "grok-3",
194
194
  llm_model_code_requesty: "xai/grok-3-beta",
195
- llm_model_code_openrouter: "x-ai/grok-3-beta",
195
+ llm_model_code_openrouter: "x-ai/grok-3",
196
196
  context_window: 131_072,
197
197
  cents_input: 300,
198
198
  cents_output: 1500,
@@ -203,12 +203,23 @@ const LLM_MODEL_DETAILS = [
203
203
  llm_provider: null,
204
204
  llm_model_code_direct: "grok-3-mini",
205
205
  llm_model_code_requesty: "xai/grok-3-mini-beta",
206
- llm_model_code_openrouter: "x-ai/grok-3-mini-beta",
206
+ llm_model_code_openrouter: "x-ai/grok-3-mini",
207
207
  context_window: 131_072,
208
208
  cents_input: 30,
209
209
  cents_output: 50,
210
210
  has_structured_json: true,
211
211
  },
212
+ {
213
+ llm_model_name: "grok-4",
214
+ llm_provider: null,
215
+ llm_model_code_direct: "grok-4",
216
+ llm_model_code_requesty: "xai/grok-4",
217
+ llm_model_code_openrouter: "x-ai/grok-4",
218
+ context_window: 256_000,
219
+ cents_input: 300,
220
+ cents_output: 1500,
221
+ has_structured_json: true,
222
+ },
212
223
  {
213
224
  llm_model_name: "llama-4-maverick",
214
225
  llm_provider: null,
@@ -232,7 +243,18 @@ const LLM_MODEL_DETAILS = [
232
243
  has_structured_json: true,
233
244
  },
234
245
  {
235
- llm_model_name: "mercury-coder-small",
246
+ llm_model_name: "mercury",
247
+ llm_provider: null,
248
+ llm_model_code_direct: null,
249
+ llm_model_code_requesty: null,
250
+ llm_model_code_openrouter: "inception/mercury",
251
+ context_window: 32_000,
252
+ cents_input: 25,
253
+ cents_output: 100,
254
+ has_structured_json: false,
255
+ },
256
+ {
257
+ llm_model_name: "mercury-coder",
236
258
  llm_provider: null,
237
259
  llm_model_code_direct: null,
238
260
  llm_model_code_requesty: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnowennixon/diffdash",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@ai-sdk/anthropic": "1.2.12",
23
23
  "@ai-sdk/deepseek": "0.2.14",
24
- "@ai-sdk/google": "1.2.21",
24
+ "@ai-sdk/google": "1.2.22",
25
25
  "@ai-sdk/openai": "1.3.22",
26
26
  "@openrouter/ai-sdk-provider": "0.7.2",
27
27
  "@requesty/ai-sdk": "0.0.9",
@@ -35,15 +35,15 @@
35
35
  "devDependencies": {
36
36
  "@biomejs/biome": "2.0.6",
37
37
  "@eslint/eslintrc": "3.3.1",
38
- "@eslint/js": "9.30.0",
38
+ "@eslint/js": "9.30.1",
39
39
  "@johnowennixon/add-shebangs": "1.1.0",
40
40
  "@johnowennixon/chmodx": "2.0.0",
41
41
  "@stylistic/eslint-plugin": "5.1.0",
42
42
  "@types/argparse": "2.0.17",
43
- "@types/node": "24.0.8",
43
+ "@types/node": "24.0.10",
44
44
  "@typescript-eslint/eslint-plugin": "8.35.1",
45
45
  "@typescript-eslint/parser": "8.35.1",
46
- "eslint": "9.30.0",
46
+ "eslint": "9.30.1",
47
47
  "eslint-import-resolver-typescript": "4.4.4",
48
48
  "eslint-plugin-import-x": "4.16.1",
49
49
  "eslint-plugin-sonarjs": "3.0.4",
@@ -52,7 +52,7 @@
52
52
  "knip": "5.61.3",
53
53
  "markdownlint-cli2": "0.18.1",
54
54
  "npm-run-all2": "8.0.4",
55
- "oxlint": "1.4.0",
55
+ "oxlint": "1.5.0",
56
56
  "rimraf": "6.0.1",
57
57
  "typescript": "5.8.3",
58
58
  "typescript-eslint": "8.35.1"