@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 CHANGED
@@ -1,3 +1,5 @@
1
+ ![Demonstration](asciinema/diffdash-demo.gif)
2
+
1
3
  # DiffDash
2
4
 
3
5
  ![npm version](https://img.shields.io/npm/v/@johnowennixon/diffdash.svg)
@@ -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
- ![Demonstration](asciinema/diffdash-demo.gif)
13
-
14
12
  ## Features
15
13
 
16
- * Generate Git commit messages in **natural English**
17
- * Add a footer to the generated commit messages
18
- * Add a prefix or suffix to the summary line
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 5)
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-4.1-mini** from OpenAI.
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 #123)"
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
- pnpm install
171
+ bun install
170
172
 
171
173
  # Build the project
172
- pnpm run build
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
- pnpm run lint
184
+ bun run lint
183
185
 
184
186
  # Fix formatting issues (if required)
185
- pnpm run fix
187
+ bun run fix
186
188
 
187
189
  # Build the project
188
- pnpm run build
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.11.0",
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 --erasableSyntaxOnly --libReplacement false",
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 markdownlint' && markdownlint-cli2 '**/*.md' --fix",
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 --erasableSyntaxOnly --libReplacement false",
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": "2.0.53",
46
- "@ai-sdk/deepseek": "1.0.31",
47
- "@ai-sdk/google": "2.0.44",
48
- "@ai-sdk/openai": "2.0.77",
49
- "@inquirer/prompts": "8.0.2",
50
- "@openrouter/ai-sdk-provider": "1.2.3",
51
- "ai": "5.0.102",
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.30.0",
58
- "zod": "4.1.13"
57
+ "simple-git": "3.31.1",
58
+ "zod": "4.3.6"
59
59
  },
60
60
  "devDependencies": {
61
- "@biomejs/biome": "2.3.8",
62
- "@candide/tsgolint": "1.4.0",
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": "24.10.1",
67
- "@typescript/native-preview": "7.0.0-dev.20251022.1",
68
- "knip": "5.71.0",
69
- "markdownlint-cli2": "0.19.1",
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.31.0",
71
+ "oxlint": "1.47.0",
72
72
  "rimraf": "6.1.2",
73
73
  "typescript": "5.9.3"
74
74
  }
@@ -16,6 +16,5 @@ export function ansi_boolean(bool) {
16
16
  }
17
17
  export function ansi_strip(text) {
18
18
  const pattern = String.raw `\u001B\[[^m]*m`;
19
- // eslint-disable-next-line sonarjs/no-control-regex
20
19
  return text.replace(new RegExp(pattern, "g"), EMPTY);
21
20
  }
@@ -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 cli_string(options = {}) {
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 cli_string_always(options = {}) {
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 cli_integer(options = {}) {
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 cli_integer_always(options = {}) {
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 cli_boolean(options = {}) {
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 cli_choice_optional(options = {}) {
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 cli_choice_default(options = {}) {
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 cli_choice_required(options = {}) {
27
- options.required = true;
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 cli_list(options = {}) {
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, predicate, }) {
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, predicate, }) {
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 {
@@ -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
  }
@@ -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 ‘${channel}’`);
41
+ tell_debug(`Debugging enabled for ${qss(channel)}`);
41
42
  }
42
43
  }
43
44
  function debug_init() {
@@ -1,33 +1,40 @@
1
- import { cli_boolean, cli_choice_default, cli_make_parser, cli_string } from "./lib_cli.js";
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: cli_boolean({ help: "show program version information and exit" }),
5
- auto_add: cli_boolean({ help: "automatically stage all changes without confirmation" }),
6
- auto_commit: cli_boolean({ help: "automatically commit changes without confirmation" }),
7
- auto_push: cli_boolean({ help: "automatically push changes after commit without confirmation" }),
8
- disable_add: cli_boolean({ help: "disable adding unstaged changes - exit if no changes staged" }),
9
- disable_status: cli_boolean({ help: "disable listing the staged files before generating a message" }),
10
- disable_preview: cli_boolean({ help: "disable previewing the generated message" }),
11
- disable_commit: cli_boolean({ help: "disable committing changes - exit after generating the message" }),
12
- disable_push: cli_boolean({ help: "disable pushing changes - exit after making the commit" }),
13
- add_prefix: cli_string({ help: "add a prefix to the commit message summary line", metavar: "PREFIX" }),
14
- add_suffix: cli_string({ help: "add a suffix to the commit message summary line", metavar: "SUFFIX" }),
15
- llm_list: cli_boolean({ help: "display a list of available Large Language Models and exit" }),
16
- llm_compare: cli_boolean({ help: "compare the generated messages from all models - but do not commit" }),
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: cli_string({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
23
- no_secret_check: cli_boolean({ help: "bypass checking for secrets in diffs" }),
24
- no_verify: cli_boolean({ help: "bypass git hooks when committing or pushing to Git" }),
25
- force: cli_boolean({ help: "apply force when pushing to Git" }),
26
- just_output: cli_boolean({ help: "just output the commit message for use in scripts" }),
27
- silent: cli_boolean({ help: "suppress all normal output - errors and aborts still display" }),
28
- debug_llm_prompts: cli_boolean({ help: "debug prompts sent to the LLM" }),
29
- debug_llm_inputs: cli_boolean({ help: "debug inputs object sent to the LLM" }),
30
- debug_llm_outputs: cli_boolean({ help: "debug outputs object received from the LLM" }),
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: ${tui_quote_smart_single(config_file_name)}`);
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-4.1-mini";
3
+ const model_name_default = "gpt-5-mini-minimal";
4
4
  const model_name_options = [
5
- "claude-3.5-haiku", // fallback
5
+ "claude-haiku-4.5", // fallback
6
6
  "deepseek-chat",
7
- "gemini-2.0-flash",
8
7
  "gemini-2.5-flash",
9
- "gpt-4.1-mini", // the best
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", // fallback
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
- const portion_role = `
6
- Your role is to generate a Git commit message in conversational English.
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
+ }
@@ -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":
@@ -1,4 +1,4 @@
1
- import { generateObject, generateText, stepCountIs } from "ai";
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: "object",
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 generateObject(llm_inputs);
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 { object: generated_object, usage: total_usage, providerMetadata: provider_metadata } = llm_outputs;
106
+ const { output: generated_object, usage: total_usage, providerMetadata: provider_metadata } = llm_outputs;
108
107
  return { generated_object, total_usage, provider_metadata };
109
108
  }
@@ -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 = ["left", "left", "right", "right", "right", "left"];
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.5@z-ai",
254
- llm_model_code: "z-ai/glm-4.5",
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: 128_000,
257
- max_output_tokens: 96_000,
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.5-air@z-ai",
267
- llm_model_code: "z-ai/glm-4.5-air",
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: 128_000,
283
- max_output_tokens: 96_000,
284
- cents_input: 60,
285
- cents_output: 220,
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: total_usage.reasoningTokens, justify_left: 5, none: QUESTION });
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 || total_usage.reasoningTokens !== undefined) {
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 ${tui_quote_smart_single(segment)} a secret?`,
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: ${tui_quote_smart_single(line.trim())}`);
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: ${tui_quote_smart_single(word)}`);
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: ${tui_quote_smart_single(segment)}`);
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.11.0",
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 --erasableSyntaxOnly --libReplacement false",
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 markdownlint' && markdownlint-cli2 '**/*.md' --fix",
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 --erasableSyntaxOnly --libReplacement false",
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 {};