@johnowennixon/diffdash 1.1.0 → 1.3.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.
Files changed (51) hide show
  1. package/README.md +15 -9
  2. package/dist/package.json +80 -0
  3. package/dist/src/diffdash.js +26 -0
  4. package/dist/src/lib_abort.js +21 -0
  5. package/dist/src/lib_ansi.js +21 -0
  6. package/dist/src/lib_char_box.js +3 -0
  7. package/dist/src/lib_char_control.js +8 -0
  8. package/dist/src/lib_char_digit.js +10 -0
  9. package/dist/src/lib_char_empty.js +1 -0
  10. package/dist/src/lib_char_punctuation.js +37 -0
  11. package/dist/src/lib_cli.js +187 -0
  12. package/dist/src/lib_datetime.js +62 -0
  13. package/dist/src/lib_debug.js +68 -0
  14. package/dist/src/lib_diffdash_add.js +23 -0
  15. package/dist/src/lib_diffdash_cli.js +34 -0
  16. package/dist/src/lib_diffdash_config.js +52 -0
  17. package/dist/src/lib_diffdash_llm.js +24 -0
  18. package/dist/src/lib_diffdash_sequence.js +199 -0
  19. package/dist/src/lib_duration.js +29 -0
  20. package/dist/src/lib_enabled.js +30 -0
  21. package/dist/src/lib_env.js +18 -0
  22. package/dist/src/lib_error.js +14 -0
  23. package/dist/src/lib_file_path.js +22 -0
  24. package/dist/src/lib_git_message_display.js +4 -0
  25. package/dist/src/lib_git_message_generate.js +55 -0
  26. package/dist/src/lib_git_message_prompt.js +72 -0
  27. package/dist/src/lib_git_message_schema.js +16 -0
  28. package/dist/src/lib_git_message_validate.js +61 -0
  29. package/dist/src/lib_git_simple_open.js +24 -0
  30. package/dist/src/lib_git_simple_staging.js +41 -0
  31. package/dist/src/lib_inspect.js +4 -0
  32. package/dist/src/lib_llm_access.js +69 -0
  33. package/dist/src/lib_llm_chat.js +66 -0
  34. package/dist/src/lib_llm_config.js +23 -0
  35. package/dist/src/lib_llm_list.js +21 -0
  36. package/dist/src/lib_llm_model.js +336 -0
  37. package/dist/src/lib_llm_provider.js +63 -0
  38. package/dist/src/lib_llm_tokens.js +14 -0
  39. package/dist/src/lib_package.js +7 -0
  40. package/dist/src/lib_parse_number.js +13 -0
  41. package/dist/src/lib_stdio_write.js +14 -0
  42. package/dist/src/lib_string_types.js +1 -0
  43. package/dist/src/lib_tell.js +58 -0
  44. package/dist/src/lib_tui_block.js +10 -0
  45. package/dist/src/lib_tui_justify.js +29 -0
  46. package/dist/src/lib_tui_readline.js +16 -0
  47. package/dist/src/lib_tui_table.js +20 -0
  48. package/dist/src/lib_tui_truncate.js +13 -0
  49. package/dist/src/lib_type_infer.js +1 -0
  50. package/package.json +30 -24
  51. package/out/diffdash.cjs +0 -32581
package/README.md CHANGED
@@ -21,11 +21,26 @@ A command-line tool to generate Git commit messages using AI.
21
21
  npm install -g @johnowennixon/diffdash
22
22
  ```
23
23
 
24
+ ## LLM Models
25
+
26
+ Currently, for this application, the best LLM model by far is **gpt-4.1-mini** from OpenAI.
27
+ It is set as the default model.
28
+ I can only presume they have done a ton of training on diffs.
29
+
24
30
  ## API Keys
25
31
 
26
32
  DiffDash requires at least one API key for an LLM provider. These must be provided as environment variables.
27
33
 
28
34
  ```bash
35
+ # For OpenAI (recommended)
36
+ export OPENAI_API_KEY=your-api-key
37
+
38
+ # For Requesty
39
+ export REQUESTY_API_KEY=your-api-key
40
+
41
+ # For OpenRouter
42
+ export OPENROUTER_API_KEY=your-api-key
43
+
29
44
  # For Anthropic
30
45
  export ANTHROPIC_API_KEY=your-api-key
31
46
 
@@ -34,15 +49,6 @@ export DEEPSEEK_API_KEY=your-api-key
34
49
 
35
50
  # For Google Gemini
36
51
  export GEMINI_API_KEY=your-api-key
37
-
38
- # For OpenAI
39
- export OPENAI_API_KEY=your-api-key
40
-
41
- # For Requesty
42
- export REQUESTY_API_KEY=your-api-key
43
-
44
- # For OpenRouter
45
- export OPENROUTER_API_KEY=your-api-key
46
52
  ```
47
53
 
48
54
  ## Usage
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@johnowennixon/diffdash",
3
+ "version": "1.3.0",
4
+ "description": "A command-line tool to generate Git commit messages using AI",
5
+ "license": "0BSD",
6
+ "author": "John Owen Nixon",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/johnowennixon/diffdash.git"
10
+ },
11
+ "engines": {
12
+ "node": ">=20"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "bin": {
19
+ "diffdash": "dist/src/diffdash.js"
20
+ },
21
+ "scripts": {
22
+ "build": "run-s -ls build:clean build:tsc build:shebang build:chmod",
23
+ "build:chmod": "echo 'Changing bin files to be executable' && chmodx --package",
24
+ "build:clean": "echo 'Removing dist' && rimraf dist",
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",
27
+ "fix": "run-s -ls fix:biome fix:markdownlint",
28
+ "fix:biome": "echo 'Fixing with Biome' && biome check --write",
29
+ "fix:eslint": "echo 'Fixing with ESLint' && eslint --fix",
30
+ "fix:markdownlint": "echo 'Fixing with markdownlint' && markdownlint-cli2 '**/*.md' --fix",
31
+ "fix:oxlint": "echo 'Fixing with oxlint' && oxlint --fix",
32
+ "lint": "run-s -ls lint:biome lint:oxlint lint:knip lint:markdownlint",
33
+ "lint:biome": "echo 'Linting with Biome' && biome check",
34
+ "lint:eslint": "echo 'Linting with ESLint' && eslint",
35
+ "lint:knip": "echo 'Linting with Knip' && knip",
36
+ "lint:markdownlint": "echo 'Linting with markdownlint' && markdownlint-cli2 '**/*.md'",
37
+ "lint:oxlint": "echo 'Linting with oxlint' && oxlint",
38
+ "lint:tsc": "echo 'Linting with tsc' && tsc --noEmit --erasableSyntaxOnly --libReplacement false",
39
+ "test": "run-s -ls lint build"
40
+ },
41
+ "dependencies": {
42
+ "@ai-sdk/anthropic": "1.2.12",
43
+ "@ai-sdk/deepseek": "0.2.14",
44
+ "@ai-sdk/google": "1.2.21",
45
+ "@ai-sdk/openai": "1.3.22",
46
+ "@openrouter/ai-sdk-provider": "0.7.2",
47
+ "@requesty/ai-sdk": "0.0.9",
48
+ "ai": "4.3.16",
49
+ "ansis": "4.1.0",
50
+ "argparse": "2.0.1",
51
+ "cli-table3": "0.6.5",
52
+ "simple-git": "3.28.0",
53
+ "zod": "3.25.67"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "2.0.6",
57
+ "@eslint/eslintrc": "3.3.1",
58
+ "@eslint/js": "9.30.0",
59
+ "@johnowennixon/add-shebangs": "1.1.0",
60
+ "@johnowennixon/chmodx": "2.0.0",
61
+ "@stylistic/eslint-plugin": "5.1.0",
62
+ "@types/argparse": "2.0.17",
63
+ "@types/node": "24.0.8",
64
+ "@typescript-eslint/eslint-plugin": "8.35.1",
65
+ "@typescript-eslint/parser": "8.35.1",
66
+ "eslint": "9.30.0",
67
+ "eslint-import-resolver-typescript": "4.4.4",
68
+ "eslint-plugin-import-x": "4.16.1",
69
+ "eslint-plugin-sonarjs": "3.0.4",
70
+ "eslint-plugin-unicorn": "59.0.1",
71
+ "globals": "16.3.0",
72
+ "knip": "5.61.3",
73
+ "markdownlint-cli2": "0.18.1",
74
+ "npm-run-all2": "8.0.4",
75
+ "oxlint": "1.4.0",
76
+ "rimraf": "6.0.1",
77
+ "typescript": "5.8.3",
78
+ "typescript-eslint": "8.35.1"
79
+ }
80
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { ansi_blue, ansi_italic, ansi_red } from "./lib_ansi.js";
3
+ import { diffdash_config_get } from "./lib_diffdash_config.js";
4
+ import { diffdash_sequence_compare, diffdash_sequence_normal } from "./lib_diffdash_sequence.js";
5
+ import { error_abort } from "./lib_error.js";
6
+ import { tell_okay, tell_plain } from "./lib_tell.js";
7
+ async function main() {
8
+ const config = diffdash_config_get();
9
+ const { llm_compare, silent } = config;
10
+ if (!silent) {
11
+ const diffdash = ansi_italic(ansi_blue("Diff") + ansi_red("Dash"));
12
+ tell_plain(`This is ${diffdash} - the fast AI Git commit tool`);
13
+ }
14
+ // eslint-disable-next-line unicorn/prefer-ternary
15
+ if (llm_compare) {
16
+ await diffdash_sequence_compare(config);
17
+ }
18
+ else {
19
+ await diffdash_sequence_normal(config);
20
+ }
21
+ if (!silent) {
22
+ tell_okay();
23
+ }
24
+ }
25
+ // eslint-disable-next-line unicorn/prefer-top-level-await
26
+ main().catch(error_abort);
@@ -0,0 +1,21 @@
1
+ import { ansi_red, ansi_yellow } from "./lib_ansi.js";
2
+ import { stdio_write_stderr_linefeed } from "./lib_stdio_write.js";
3
+ export function abort_exit() {
4
+ process.exit(1);
5
+ }
6
+ export function abort_with_warning(message) {
7
+ if (process.stdout.isTTY) {
8
+ process.stdout.clearLine(0);
9
+ process.stdout.cursorTo(0);
10
+ }
11
+ stdio_write_stderr_linefeed(ansi_yellow(message));
12
+ abort_exit();
13
+ }
14
+ export function abort_with_error(message) {
15
+ if (process.stdout.isTTY) {
16
+ process.stdout.clearLine(0);
17
+ process.stdout.cursorTo(0);
18
+ }
19
+ stdio_write_stderr_linefeed(ansi_red(message));
20
+ abort_exit();
21
+ }
@@ -0,0 +1,21 @@
1
+ import ansis from "ansis";
2
+ import { EMPTY } from "./lib_char_empty.js";
3
+ export const ansi_normal = (message) => message;
4
+ export const ansi_dim = (message) => ansis.dim(message);
5
+ export const ansi_bold = (message) => ansis.bold(message);
6
+ export const ansi_italic = (message) => ansis.italic(message);
7
+ export const ansi_red = (message) => ansis.redBright(message);
8
+ export const ansi_yellow = (message) => ansis.yellowBright(message);
9
+ export const ansi_green = (message) => ansis.greenBright(message);
10
+ export const ansi_cyan = (message) => ansis.cyanBright(message);
11
+ export const ansi_blue = (message) => ansis.blueBright(message);
12
+ export const ansi_magenta = (message) => ansis.magentaBright(message);
13
+ export const ansi_grey = (message) => ansis.gray(message);
14
+ export function ansi_boolean(bool) {
15
+ return bool ? ansi_green : ansi_red;
16
+ }
17
+ export function ansi_strip(text) {
18
+ const pattern = String.raw `\u001B\[[^m]*m`;
19
+ // eslint-disable-next-line sonarjs/no-control-regex
20
+ return text.replace(new RegExp(pattern, "g"), EMPTY);
21
+ }
@@ -0,0 +1,3 @@
1
+ export const BOX_DRAWINGS_DOUBLE_HORIZONTAL = "\u2550";
2
+ export const BOX_DRAWINGS_HEAVY_HORIZONTAL = "\u2501";
3
+ export const BOX_DRAWINGS_LIGHT_HORIZONTAL = "\u2500";
@@ -0,0 +1,8 @@
1
+ export const BS = "\b";
2
+ export const CR = "\r";
3
+ export const ESC = "\u001B";
4
+ export const FF = "\f";
5
+ export const LF = "\n";
6
+ export const NUL = "\0";
7
+ export const TAB = "\t";
8
+ export const VT = "\v";
@@ -0,0 +1,10 @@
1
+ export const DIGIT_0 = "0";
2
+ export const DIGIT_1 = "1";
3
+ export const DIGIT_2 = "2";
4
+ export const DIGIT_3 = "3";
5
+ export const DIGIT_4 = "4";
6
+ export const DIGIT_5 = "5";
7
+ export const DIGIT_6 = "6";
8
+ export const DIGIT_7 = "7";
9
+ export const DIGIT_8 = "8";
10
+ export const DIGIT_9 = "9";
@@ -0,0 +1 @@
1
+ export const EMPTY = "";
@@ -0,0 +1,37 @@
1
+ export const AMPERSAND = "&";
2
+ export const APOSTROPHE = "'";
3
+ export const ASTERISK = "*";
4
+ export const AT_SIGN = "@";
5
+ export const BACKSLASH = "\\";
6
+ export const BACKTICK = "`";
7
+ export const CARET = "^";
8
+ export const COLON = ":";
9
+ export const COMMA = ",";
10
+ export const CURLY_LEFT = "{";
11
+ export const CURLY_RIGHT = "}";
12
+ export const DASH = "-";
13
+ export const DOLLAR = "$";
14
+ export const DOT = ".";
15
+ export const ELLIPSIS = "…";
16
+ export const EQUALS = "=";
17
+ export const HASH = "#";
18
+ export const HAT = "^";
19
+ export const LESS_THAN = "<";
20
+ export const MORE_THAN = ">";
21
+ export const MINUS = "-";
22
+ export const PERCENT = "%";
23
+ export const PIPE = "|";
24
+ export const PLUS = "+";
25
+ export const POUND_SIGN = "£";
26
+ export const QUESTION = "?";
27
+ export const QUOTE_DOUBLE = '"';
28
+ export const QUOTE_SINGLE = "'";
29
+ export const ROUND_LEFT = "(";
30
+ export const ROUND_RIGHT = ")";
31
+ export const SEMICOLON = ";";
32
+ export const SLASH = "/";
33
+ export const SPACE = " ";
34
+ export const SQUARE_LEFT = "[";
35
+ export const SQUARE_RIGHT = "]";
36
+ export const TILDE = "~";
37
+ export const UNDERSCORE = "_";
@@ -0,0 +1,187 @@
1
+ import { ArgumentParser } from "argparse";
2
+ import { EMPTY } from "./lib_char_empty.js";
3
+ import { DASH, PLUS, UNDERSCORE } from "./lib_char_punctuation.js";
4
+ import { debug_channels, debug_inspect_when } from "./lib_debug.js";
5
+ export function cli_string(options = {}) {
6
+ return { kind: "string", options, value: "" };
7
+ }
8
+ export function cli_string_always(options = {}) {
9
+ return { kind: "string", options, value: "" };
10
+ }
11
+ export function cli_integer(options = {}) {
12
+ return { kind: "integer", options, value: 0 };
13
+ }
14
+ export function cli_integer_always(options = {}) {
15
+ return { kind: "integer", options, value: 0 };
16
+ }
17
+ export function cli_boolean(options = {}) {
18
+ return { kind: "boolean", options, value: false };
19
+ }
20
+ export function cli_command_sync(command_sync, options = {}) {
21
+ return { kind: "boolean", options, value: false, command_sync };
22
+ }
23
+ export function cli_command_async(command_async, options = {}) {
24
+ return { kind: "boolean", options, value: false, command_async };
25
+ }
26
+ export function cli_choice_optional(options = {}) {
27
+ return { kind: "choice", options, value: undefined };
28
+ }
29
+ export function cli_choice_default(options = {}) {
30
+ return { kind: "choice", options, value: undefined };
31
+ }
32
+ export function cli_choice_required(options = {}) {
33
+ options.required = true;
34
+ return { kind: "choice", options, value: undefined };
35
+ }
36
+ export function cli_list(options = {}) {
37
+ return { kind: "list", options, value: [] };
38
+ }
39
+ export function cli_meg_optional(meg_schema) {
40
+ return { kind: "meg", options: { required: false }, value: meg_schema };
41
+ }
42
+ export function cli_meg_required(meg_schema) {
43
+ return { kind: "meg", options: { required: true }, value: meg_schema };
44
+ }
45
+ export function cli_meg_required_predicate(meg_schema, predicate) {
46
+ return { kind: "meg", options: { required: true, predicate }, value: meg_schema };
47
+ }
48
+ function cli_omit(obj, key_to_omit) {
49
+ const { [key_to_omit]: _, ...rest } = obj;
50
+ return rest;
51
+ }
52
+ function cli_add_keys({ cli_schema, parser_group, predicate, }) {
53
+ for (const key in cli_schema) {
54
+ if (!Object.hasOwn(cli_schema, key)) {
55
+ continue;
56
+ }
57
+ const cli = cli_schema[key];
58
+ if (!cli) {
59
+ continue;
60
+ }
61
+ if (predicate !== undefined) {
62
+ if (!predicate(key)) {
63
+ continue;
64
+ }
65
+ }
66
+ const key_replaced = key.replaceAll(UNDERSCORE, DASH);
67
+ const key_amended = `${cli.options.positional === true ? EMPTY : "--"}${key_replaced}`;
68
+ const options = cli_omit(cli.options, "positional");
69
+ switch (cli.kind) {
70
+ case "string":
71
+ case "choice":
72
+ parser_group.add_argument(key_amended, { ...options });
73
+ break;
74
+ case "integer":
75
+ parser_group.add_argument(key_amended, { ...options, type: "int" });
76
+ break;
77
+ case "boolean":
78
+ parser_group.add_argument(key_amended, { ...options, action: "store_true" });
79
+ break;
80
+ case "list":
81
+ parser_group.add_argument(key_amended, { ...options, nargs: PLUS });
82
+ break;
83
+ case "meg":
84
+ {
85
+ const mutually_exclusive_group = parser_group.add_mutually_exclusive_group({
86
+ required: cli.options.required === true,
87
+ });
88
+ cli_add_keys({
89
+ cli_schema: cli.value,
90
+ parser_group: mutually_exclusive_group,
91
+ predicate: cli.options.predicate,
92
+ });
93
+ }
94
+ break;
95
+ default:
96
+ throw new Error("Unknown argument kind");
97
+ }
98
+ }
99
+ }
100
+ function cli_recursive_parse({ schema, namespace, predicate, }) {
101
+ const result = {};
102
+ for (const key in schema) {
103
+ if (!Object.hasOwn(schema, key)) {
104
+ continue;
105
+ }
106
+ const cli = schema[key];
107
+ if (!cli) {
108
+ continue;
109
+ }
110
+ if (predicate !== undefined) {
111
+ if (!predicate(key)) {
112
+ continue;
113
+ }
114
+ }
115
+ if (cli.kind === "meg") {
116
+ const nested_schema = cli.value;
117
+ result[key] = cli_recursive_parse({
118
+ schema: nested_schema,
119
+ namespace,
120
+ predicate: cli.options.predicate,
121
+ });
122
+ }
123
+ else {
124
+ result[key] = namespace[key];
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+ function cli_recursive_despatch_sync({ schema, namespace, parsed_args, }) {
130
+ for (const key in schema) {
131
+ if (!Object.hasOwn(schema, key)) {
132
+ continue;
133
+ }
134
+ const cli = schema[key];
135
+ if (!cli) {
136
+ continue;
137
+ }
138
+ if (cli.kind === "meg") {
139
+ const nested_schema = cli.value;
140
+ cli_recursive_despatch_sync({ schema: nested_schema, namespace, parsed_args });
141
+ }
142
+ else if (cli.kind === "boolean") {
143
+ if (namespace[key]) {
144
+ if (cli.command_sync) {
145
+ cli.command_sync(parsed_args);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ async function cli_recursive_despatch_async({ schema, namespace, parsed_args, }) {
152
+ for (const key in schema) {
153
+ if (!Object.hasOwn(schema, key)) {
154
+ continue;
155
+ }
156
+ const cli = schema[key];
157
+ if (!cli) {
158
+ continue;
159
+ }
160
+ if (cli.kind === "meg") {
161
+ const nested_schema = cli.value;
162
+ await cli_recursive_despatch_async({ schema: nested_schema, namespace, parsed_args });
163
+ }
164
+ else if (cli.kind === "boolean") {
165
+ if (namespace[key]) {
166
+ if (cli.command_async) {
167
+ await cli.command_async(parsed_args);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ export function cli_make_parser({ cli_schema, description, }) {
174
+ const argument_parser_options = { description, allow_abbrev: false };
175
+ const parser = new ArgumentParser(argument_parser_options);
176
+ cli_add_keys({ cli_schema, parser_group: parser });
177
+ const namespace = parser.parse_args();
178
+ debug_inspect_when(debug_channels.cli, namespace, "namespace");
179
+ const parsed_args = cli_recursive_parse({ schema: cli_schema, namespace });
180
+ function despatch_sync() {
181
+ cli_recursive_despatch_sync({ schema: cli_schema, namespace, parsed_args });
182
+ }
183
+ async function despatch_async() {
184
+ await cli_recursive_despatch_async({ schema: cli_schema, namespace, parsed_args });
185
+ }
186
+ return { parsed_args, despatch_sync, despatch_async };
187
+ }
@@ -0,0 +1,62 @@
1
+ import { EMPTY } from "./lib_char_empty.js";
2
+ import { COLON, DASH, SPACE } from "./lib_char_punctuation.js";
3
+ export function datetime_now() {
4
+ return new Date();
5
+ }
6
+ export function datetime_now_minus_days(days) {
7
+ const date = datetime_now();
8
+ date.setDate(date.getDate() - days);
9
+ return date;
10
+ }
11
+ export function datetime_parse(s) {
12
+ return new Date(s);
13
+ }
14
+ export function datetime_parse_timestamp(timestamp) {
15
+ const year = Number.parseInt(timestamp.slice(0, 4), 10);
16
+ const month_index = Number.parseInt(timestamp.slice(4, 6), 10) - 1;
17
+ const day = Number.parseInt(timestamp.slice(6, 8), 10);
18
+ const hours = Number.parseInt(timestamp.slice(8, 10), 10);
19
+ const minutes = Number.parseInt(timestamp.slice(10, 12), 10);
20
+ const seconds = Number.parseInt(timestamp.slice(12, 14), 10);
21
+ const utc = Date.UTC(year, month_index, day, hours, minutes, seconds);
22
+ const date = new Date(utc);
23
+ return date;
24
+ }
25
+ export function datetime_format_utc_iso_ymdthms(date) {
26
+ return date.toISOString().slice(0, 19);
27
+ }
28
+ export function datetime_format_utc_iso_ymd(date) {
29
+ return date.toISOString().slice(0, 10);
30
+ }
31
+ export function datetime_format_utc_iso_hms(date) {
32
+ return date.toISOString().slice(11, 19);
33
+ }
34
+ export function datetime_format_utc_timestamp_alpha(date) {
35
+ const s1 = date.toISOString().slice(0, 19);
36
+ const s2 = s1.replaceAll(DASH, EMPTY);
37
+ const s3 = s2.replaceAll(COLON, EMPTY);
38
+ const s4 = s3 + "Z";
39
+ return s4;
40
+ }
41
+ export function datetime_format_utc_timestamp_numeric(date) {
42
+ const s1 = date.toISOString().slice(0, 19);
43
+ const s2 = s1.replaceAll(DASH, EMPTY);
44
+ const s3 = s2.replaceAll(COLON, EMPTY);
45
+ const s4 = s3.replace("T", EMPTY);
46
+ return s4;
47
+ }
48
+ function datetime_localize(date) {
49
+ return new Date(date.valueOf() - date.getTimezoneOffset() * 60_000);
50
+ }
51
+ export function datetime_format_local_iso_ymdthms(date) {
52
+ return datetime_format_utc_iso_ymdthms(datetime_localize(date));
53
+ }
54
+ export function datetime_format_local_iso_ymd_hms(date) {
55
+ return datetime_format_utc_iso_ymdthms(datetime_localize(date)).replace("T", SPACE);
56
+ }
57
+ export function datetime_format_local_iso_ymd(date) {
58
+ return datetime_format_utc_iso_ymd(datetime_localize(date));
59
+ }
60
+ export function datetime_format_local_iso_hms(date) {
61
+ return datetime_format_utc_iso_hms(datetime_localize(date));
62
+ }
@@ -0,0 +1,68 @@
1
+ import { ansi_grey } from "./lib_ansi.js";
2
+ import { EMPTY } from "./lib_char_empty.js";
3
+ import { enabled_from_env } from "./lib_enabled.js";
4
+ import { inspect_obj_to_string } from "./lib_inspect.js";
5
+ import { stdio_write_stderr_linefeed } from "./lib_stdio_write.js";
6
+ import { tell_debug } from "./lib_tell.js";
7
+ export const debug_channels = {
8
+ api: false,
9
+ backups: false,
10
+ child_input: false,
11
+ child_output: false,
12
+ cleanup: false,
13
+ cli: false,
14
+ cloudflare: false,
15
+ config: false,
16
+ data: false,
17
+ detail: false,
18
+ forwarding: false,
19
+ gcp: false,
20
+ git: false,
21
+ helm: false,
22
+ lines: false,
23
+ llm_inputs: false,
24
+ llm_outputs: false,
25
+ llm_tokens: false,
26
+ llm_tools: false,
27
+ node: false,
28
+ path: false,
29
+ kubectl: false,
30
+ postgresql: false,
31
+ rejects: false,
32
+ retries: false,
33
+ sql: false,
34
+ };
35
+ export function debug_enable_if(channel, enabled) {
36
+ if (enabled && !debug_channels[channel]) {
37
+ debug_channels[channel] = true;
38
+ tell_debug(`Debugging enabled for ‘${channel}’`);
39
+ }
40
+ }
41
+ function debug_init() {
42
+ for (const channel in debug_channels) {
43
+ if (enabled_from_env(`lib_debug_${channel}`)) {
44
+ debug_enable_if(channel, true);
45
+ }
46
+ }
47
+ }
48
+ debug_init();
49
+ export function debug_tell_when(when, message) {
50
+ if (when !== undefined && when) {
51
+ tell_debug(message);
52
+ }
53
+ }
54
+ export function debug_inspect(obj, name = null) {
55
+ const prefix = name ? `${name}: ` : EMPTY;
56
+ const message = prefix + inspect_obj_to_string(obj);
57
+ stdio_write_stderr_linefeed(ansi_grey(message));
58
+ }
59
+ export function debug_inspect_if(obj, name = null) {
60
+ if (obj !== undefined) {
61
+ debug_inspect(obj, name);
62
+ }
63
+ }
64
+ export function debug_inspect_when(when, obj, name = null) {
65
+ if (when !== undefined && when) {
66
+ debug_inspect(obj, name);
67
+ }
68
+ }
@@ -0,0 +1,23 @@
1
+ import { LF } from "./lib_char_control.js";
2
+ import { EMPTY } from "./lib_char_empty.js";
3
+ import { datetime_format_local_iso_ymd_hms } from "./lib_datetime.js";
4
+ import { PACKAGE_NAME, PACKAGE_VERSION } from "./lib_package.js";
5
+ export function diffdash_add_prefix_or_suffix({ git_message, add_prefix, add_suffix, }) {
6
+ if (!add_prefix && !add_suffix) {
7
+ return git_message;
8
+ }
9
+ const lines = git_message.split(LF);
10
+ if (lines.length === 0) {
11
+ return git_message;
12
+ }
13
+ const formatted_prefix = add_prefix ? `${add_prefix} ` : EMPTY;
14
+ const formatted_suffix = add_suffix ? ` ${add_suffix}` : EMPTY;
15
+ lines[0] = `${formatted_prefix}${lines[0]}${formatted_suffix}`;
16
+ return lines.join(LF);
17
+ }
18
+ export function diffdash_add_footer({ git_message, llm_config }) {
19
+ const { llm_model_name } = llm_config;
20
+ const timestamp = datetime_format_local_iso_ymd_hms(new Date());
21
+ const footer = `Generated by: ${PACKAGE_NAME} v${PACKAGE_VERSION} at ${timestamp} using ${llm_model_name}`;
22
+ return git_message.trim() + LF + LF + footer;
23
+ }
@@ -0,0 +1,34 @@
1
+ import { cli_boolean, cli_choice_default, cli_make_parser, cli_string } from "./lib_cli.js";
2
+ import { diffdash_llm_model_choices, diffdash_llm_model_default, diffdash_llm_model_fallback, } from "./lib_diffdash_llm.js";
3
+ const diffdash_cli_schema = {
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
+ auto_add: cli_boolean({ help: "automatically stage all changes without confirmation" }),
8
+ auto_commit: cli_boolean({ help: "automatically commit changes without confirmation" }),
9
+ auto_push: cli_boolean({ help: "automatically push changes after commit without confirmation" }),
10
+ disable_add: cli_boolean({ help: "disable adding unstaged changes - exit if no changes staged" }),
11
+ disable_status: cli_boolean({ help: "disable listing the staged files before generating a message" }),
12
+ disable_preview: cli_boolean({ help: "disable previewing the generated message" }),
13
+ disable_commit: cli_boolean({ help: "disable committing changes - exit after generating the message" }),
14
+ disable_push: cli_boolean({ help: "disable pushing changes - exit after making the commit" }),
15
+ push_no_verify: cli_boolean({ help: "bypass git hooks when pushing to Git" }),
16
+ push_force: cli_boolean({ help: "apply force when pushing to Git" }),
17
+ llm_list: cli_boolean({ help: "display a list of available Large Language Models and exit" }),
18
+ llm_compare: cli_boolean({ help: "compare the generated messages from all models - but do not commit" }),
19
+ llm_router: cli_boolean({ help: "prefer to access the LLM via a router rather than direct" }),
20
+ llm_fallback: cli_boolean({ help: `use the fallback model (${diffdash_llm_model_fallback})` }),
21
+ llm_model: cli_choice_default({
22
+ help: `choose the Large Language Model by name (defaults to ${diffdash_llm_model_default})`,
23
+ choices: diffdash_llm_model_choices,
24
+ default: diffdash_llm_model_default,
25
+ }),
26
+ llm_excludes: cli_string({ help: "models to exclude from comparison (comma separated)", metavar: "MODELS" }),
27
+ silent: cli_boolean({ help: "suppress all normal output - errors and aborts still display" }),
28
+ debug_llm_inputs: cli_boolean({ help: "debug inputs (including all prompts) sent to the LLM" }),
29
+ debug_llm_outputs: cli_boolean({ help: "debug outputs received from the LLM" }),
30
+ };
31
+ export const diffdash_cli_parser = cli_make_parser({
32
+ cli_schema: diffdash_cli_schema,
33
+ description: "DiffDash - generate Git commit messages using AI",
34
+ });