@johnowennixon/diffdash 1.7.0 → 1.9.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 +18 -18
- package/dist/package.json +24 -22
- package/dist/src/lib_datetime.js +4 -1
- package/dist/src/lib_debug.js +1 -0
- package/dist/src/lib_diffdash_cli.js +3 -5
- package/dist/src/lib_diffdash_config.js +6 -15
- package/dist/src/lib_diffdash_llm.js +8 -13
- package/dist/src/lib_diffdash_sequence.js +33 -17
- package/dist/src/lib_git_message_generate.js +19 -19
- package/dist/src/lib_git_message_prompt.js +3 -3
- package/dist/src/lib_git_message_schema.js +3 -3
- package/dist/src/lib_git_simple_staging.js +11 -6
- package/dist/src/lib_llm_access.js +14 -53
- package/dist/src/lib_llm_api.js +2 -36
- package/dist/src/lib_llm_chat.js +26 -19
- package/dist/src/lib_llm_config.js +12 -15
- package/dist/src/lib_llm_list.js +10 -8
- package/dist/src/lib_llm_model.js +369 -241
- package/dist/src/lib_llm_results.js +50 -0
- package/dist/src/lib_llm_tokens.js +6 -3
- package/dist/src/lib_parse_number.js +2 -2
- package/dist/src/lib_tell.js +6 -5
- package/dist/src/lib_tui_confirm.js +25 -0
- package/dist/src/lib_tui_none.js +12 -0
- package/dist/src/lib_tui_number.js +22 -0
- package/dist/src/lib_tui_table.js +14 -6
- package/package.json +24 -22
- package/dist/src/lib_assert_type.js +0 -30
- package/dist/src/lib_tui_readline.js +0 -16
- package/dist/src/lib_type_guard.js +0 -15
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { QUESTION, SPACE } from "./lib_char_punctuation.js";
|
|
2
|
+
import { tell_action, tell_info, tell_warning } from "./lib_tell.js";
|
|
3
|
+
import { tui_justify_left } from "./lib_tui_justify.js";
|
|
4
|
+
import { tui_none_blank } from "./lib_tui_none.js";
|
|
5
|
+
import { tui_number_plain } from "./lib_tui_number.js";
|
|
6
|
+
export function llm_results_summary(all_results) {
|
|
7
|
+
tell_action("Showing summary of responses ...");
|
|
8
|
+
all_results = all_results.toSorted((a, b) => a.seconds - b.seconds);
|
|
9
|
+
const max_length_model = Math.max(...all_results.map((result) => result.llm_config.llm_model_name.length));
|
|
10
|
+
for (const result of all_results) {
|
|
11
|
+
const { llm_config, seconds, error_text } = result;
|
|
12
|
+
if (error_text === null) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const { llm_model_name } = llm_config;
|
|
16
|
+
const tui_model = tui_justify_left(max_length_model, llm_model_name);
|
|
17
|
+
const tui_seconds = tui_number_plain({ num: seconds, justify_left: 3 });
|
|
18
|
+
tell_warning(`${tui_model} seconds=${tui_seconds} ${error_text}`);
|
|
19
|
+
}
|
|
20
|
+
for (const result of all_results) {
|
|
21
|
+
const { llm_config, seconds, error_text } = result;
|
|
22
|
+
if (error_text !== null) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const { llm_model_name, llm_model_detail } = llm_config;
|
|
26
|
+
const { default_reasoning } = llm_model_detail;
|
|
27
|
+
const { outputs } = result;
|
|
28
|
+
const { total_usage, provider_metadata } = outputs;
|
|
29
|
+
const openrouter_provider = provider_metadata?.["openrouter"]?.["provider"];
|
|
30
|
+
const tui_model = tui_justify_left(max_length_model, llm_model_name);
|
|
31
|
+
const tui_seconds = tui_number_plain({ num: seconds, justify_left: 3 });
|
|
32
|
+
const tui_input = tui_number_plain({ num: total_usage.inputTokens, justify_left: 5 });
|
|
33
|
+
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_provider = tui_none_blank(openrouter_provider);
|
|
36
|
+
const segments = [];
|
|
37
|
+
segments.push(tui_model);
|
|
38
|
+
segments.push(`seconds=${tui_seconds}`);
|
|
39
|
+
segments.push(`input=${tui_input}`);
|
|
40
|
+
segments.push(`output=${tui_output}`);
|
|
41
|
+
if (default_reasoning || total_usage.reasoningTokens !== undefined) {
|
|
42
|
+
segments.push(`reasoning=${tui_reasoning}`);
|
|
43
|
+
}
|
|
44
|
+
if (openrouter_provider) {
|
|
45
|
+
segments.push(`provider=${tui_provider}`);
|
|
46
|
+
}
|
|
47
|
+
const message = segments.join(SPACE + SPACE);
|
|
48
|
+
tell_info(message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { debug_channels } from "./lib_debug.js";
|
|
2
2
|
import { tell_debug } from "./lib_tell.js";
|
|
3
|
-
export function
|
|
4
|
-
return Math.round(
|
|
3
|
+
export function llm_tokens_estimate_tokens_from_length({ length }) {
|
|
4
|
+
return Math.round(length / 1.4);
|
|
5
|
+
}
|
|
6
|
+
export function llm_tokens_estimate_length_from_tokens({ tokens }) {
|
|
7
|
+
return Math.round(tokens * 1.4);
|
|
5
8
|
}
|
|
6
9
|
export function llm_tokens_debug_usage({ name, llm_config, text, }) {
|
|
7
10
|
if (debug_channels.llm_tokens) {
|
|
8
11
|
const { llm_model_name } = llm_config;
|
|
9
12
|
const length = text.length;
|
|
10
|
-
const tokens =
|
|
13
|
+
const tokens = llm_tokens_estimate_tokens_from_length({ llm_config, length });
|
|
11
14
|
const ratio = Math.round((length / tokens) * 100) / 100;
|
|
12
15
|
tell_debug(`${name}: length=${length}, tokens=${tokens}, ratio=${ratio}, model=${llm_model_name}`);
|
|
13
16
|
}
|
|
@@ -6,8 +6,8 @@ export function parse_float(input) {
|
|
|
6
6
|
return Number.parseFloat(input);
|
|
7
7
|
}
|
|
8
8
|
export function parse_int_or_undefined(input) {
|
|
9
|
-
return input === undefined || input === EMPTY ? undefined : parse_int(input);
|
|
9
|
+
return input === undefined || input === null || input === EMPTY ? undefined : parse_int(input);
|
|
10
10
|
}
|
|
11
11
|
export function parse_float_or_undefined(input) {
|
|
12
|
-
return input === undefined || input === EMPTY ? undefined : parse_float(input);
|
|
12
|
+
return input === undefined || input === null || input === EMPTY ? undefined : parse_float(input);
|
|
13
13
|
}
|
package/dist/src/lib_tell.js
CHANGED
|
@@ -2,9 +2,9 @@ import { ansi_cyan, ansi_green, ansi_grey, ansi_magenta, ansi_normal, ansi_red,
|
|
|
2
2
|
import { LF } from "./lib_char_control.js";
|
|
3
3
|
import { EMPTY } from "./lib_char_empty.js";
|
|
4
4
|
import { SPACE } from "./lib_char_punctuation.js";
|
|
5
|
-
import {
|
|
5
|
+
import { datetime_format_local_iso_ymd_hms, datetime_now } from "./lib_datetime.js";
|
|
6
6
|
import { enabled_from_env } from "./lib_enabled.js";
|
|
7
|
-
import { stdio_write_stderr_linefeed } from "./lib_stdio_write.js";
|
|
7
|
+
import { stdio_write_stderr_linefeed, stdio_write_stdout_linefeed } from "./lib_stdio_write.js";
|
|
8
8
|
export const tell_enables = {
|
|
9
9
|
ansi: enabled_from_env("TELL_ANSI", { default: true }),
|
|
10
10
|
okay: enabled_from_env("TELL_OKAY", { default: true }),
|
|
@@ -17,12 +17,13 @@ function tell_generic({ message, colourizer }) {
|
|
|
17
17
|
}
|
|
18
18
|
let text = EMPTY;
|
|
19
19
|
if (tell_enables.timestamp) {
|
|
20
|
-
const now_local_ymdthms =
|
|
20
|
+
const now_local_ymdthms = datetime_format_local_iso_ymd_hms(datetime_now());
|
|
21
21
|
text += ansi_grey(now_local_ymdthms);
|
|
22
|
-
text += SPACE;
|
|
22
|
+
text += SPACE + SPACE;
|
|
23
23
|
}
|
|
24
24
|
text += tell_enables.ansi && colourizer ? colourizer(message) : message;
|
|
25
|
-
stdio_write_stderr_linefeed
|
|
25
|
+
const teller = tell_enables.stdout ? stdio_write_stdout_linefeed : stdio_write_stderr_linefeed;
|
|
26
|
+
teller(text);
|
|
26
27
|
}
|
|
27
28
|
export function tell_nowhere(_message) {
|
|
28
29
|
// intentionally empty
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { input as inquirer_input } from "@inquirer/prompts";
|
|
2
|
+
export async function tui_confirm({ question, default: default_value, style_message, }) {
|
|
3
|
+
const result = await inquirer_input({
|
|
4
|
+
message: question,
|
|
5
|
+
default: default_value === undefined ? undefined : default_value ? "Yes" : "No",
|
|
6
|
+
validate: (text) => {
|
|
7
|
+
const cleaned = text.trim().toLowerCase();
|
|
8
|
+
if (cleaned === "y" || cleaned === "yes" || cleaned === "n" || cleaned === "no") {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return "Please enter Yes or No";
|
|
12
|
+
},
|
|
13
|
+
transformer: (text, { isFinal: is_final }) => {
|
|
14
|
+
const cleaned = text.trim().toLowerCase();
|
|
15
|
+
return is_final ? (cleaned === "y" || cleaned === "yes" ? "Yes" : "No") : text;
|
|
16
|
+
},
|
|
17
|
+
theme: {
|
|
18
|
+
prefix: { idle: undefined, done: undefined },
|
|
19
|
+
style: {
|
|
20
|
+
message: style_message,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return result.trim().toLowerCase() === "y" || result.trim().toLowerCase() === "yes";
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EMPTY } from "./lib_char_empty.js";
|
|
2
|
+
import { DASH } from "./lib_char_punctuation.js";
|
|
3
|
+
// eslint-disable-next-line sonarjs/use-type-alias
|
|
4
|
+
function tui_none_generic({ str, none }) {
|
|
5
|
+
return str === undefined || str === null || str === EMPTY ? none : str.toString();
|
|
6
|
+
}
|
|
7
|
+
export function tui_none_blank(str) {
|
|
8
|
+
return tui_none_generic({ str, none: EMPTY });
|
|
9
|
+
}
|
|
10
|
+
export function tui_none_dash(str) {
|
|
11
|
+
return tui_none_generic({ str, none: DASH });
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EMPTY } from "./lib_char_empty.js";
|
|
2
|
+
import { DOT } from "./lib_char_punctuation.js";
|
|
3
|
+
import { tui_justify_left, tui_justify_zero } from "./lib_tui_justify.js";
|
|
4
|
+
export function tui_number_plain({ num, none = EMPTY, justify_left, justify_right, }) {
|
|
5
|
+
let str = num === null || num === undefined ? none : num.toString();
|
|
6
|
+
if (justify_left !== undefined) {
|
|
7
|
+
str = tui_justify_left(justify_left, str);
|
|
8
|
+
}
|
|
9
|
+
if (justify_right !== undefined) {
|
|
10
|
+
str = tui_justify_left(justify_right, str);
|
|
11
|
+
}
|
|
12
|
+
return str;
|
|
13
|
+
}
|
|
14
|
+
export function tui_number_integer_commas(n) {
|
|
15
|
+
return Math.round(n).toLocaleString();
|
|
16
|
+
}
|
|
17
|
+
export function tui_number_money_format(value) {
|
|
18
|
+
const rounded = Math.round(value * 100);
|
|
19
|
+
const units = Math.floor(rounded / 100);
|
|
20
|
+
const cents = rounded % 100;
|
|
21
|
+
return tui_number_integer_commas(units) + DOT + tui_justify_zero(2, cents.toString());
|
|
22
|
+
}
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import cli_table3 from "cli-table3";
|
|
2
|
+
import { abort_with_error } from "./lib_abort.js";
|
|
2
3
|
import { ansi_bold } from "./lib_ansi.js";
|
|
3
4
|
export class TuiTable {
|
|
4
5
|
table;
|
|
5
|
-
|
|
6
|
-
constructor({ headings }) {
|
|
7
|
-
const constructor_options = { style: { head: [] } };
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
columns_total;
|
|
7
|
+
constructor({ headings, alignments, compact, }) {
|
|
8
|
+
const constructor_options = { style: { head: [], compact } };
|
|
9
|
+
constructor_options.head = headings.map((heading) => ansi_bold(heading));
|
|
10
|
+
this.columns_total = headings.length;
|
|
11
|
+
if (alignments) {
|
|
12
|
+
if (alignments.length !== this.columns_total) {
|
|
13
|
+
abort_with_error(`length of alignments (${alignments.length}) must match length of headings (${this.columns_total})`);
|
|
14
|
+
}
|
|
15
|
+
constructor_options.colAligns = alignments;
|
|
10
16
|
}
|
|
11
17
|
this.table = new cli_table3(constructor_options);
|
|
12
18
|
}
|
|
13
19
|
push(row) {
|
|
20
|
+
if (row.length !== this.columns_total) {
|
|
21
|
+
abort_with_error(`length of row (${row.length}) must match length of headings (${this.columns_total})`);
|
|
22
|
+
}
|
|
14
23
|
this.table.push(row);
|
|
15
|
-
this.count++;
|
|
16
24
|
}
|
|
17
25
|
toString() {
|
|
18
26
|
return this.table.toString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnowennixon/diffdash",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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",
|
|
@@ -19,34 +19,34 @@
|
|
|
19
19
|
"diffdash": "dist/src/diffdash.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ai-sdk/anthropic": "
|
|
23
|
-
"@ai-sdk/deepseek": "0.
|
|
24
|
-
"@ai-sdk/google": "
|
|
25
|
-
"@ai-sdk/openai": "
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
28
|
-
"ai": "
|
|
22
|
+
"@ai-sdk/anthropic": "2.0.9",
|
|
23
|
+
"@ai-sdk/deepseek": "1.0.13",
|
|
24
|
+
"@ai-sdk/google": "2.0.11",
|
|
25
|
+
"@ai-sdk/openai": "2.0.23",
|
|
26
|
+
"@inquirer/prompts": "7.8.4",
|
|
27
|
+
"@openrouter/ai-sdk-provider": "1.1.2",
|
|
28
|
+
"ai": "5.0.29",
|
|
29
29
|
"ansis": "4.1.0",
|
|
30
30
|
"argparse": "2.0.1",
|
|
31
31
|
"cli-table3": "0.6.5",
|
|
32
32
|
"json5": "2.2.3",
|
|
33
33
|
"simple-git": "3.28.0",
|
|
34
|
-
"zod": "
|
|
34
|
+
"zod": "4.1.5"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@biomejs/biome": "2.
|
|
37
|
+
"@biomejs/biome": "2.2.2",
|
|
38
|
+
"@candide/tsgolint": "1.3.0",
|
|
38
39
|
"@johnowennixon/add-shebangs": "1.1.0",
|
|
39
|
-
"@johnowennixon/chmodx": "2.
|
|
40
|
-
"@johnowennixon/pipe-exit": "1.0.1",
|
|
40
|
+
"@johnowennixon/chmodx": "2.1.0",
|
|
41
41
|
"@types/argparse": "2.0.17",
|
|
42
|
-
"@types/node": "24.
|
|
43
|
-
"
|
|
42
|
+
"@types/node": "24.3.0",
|
|
43
|
+
"@typescript/native-preview": "7.0.0-dev.20250902.1",
|
|
44
|
+
"knip": "5.63.0",
|
|
44
45
|
"markdownlint-cli2": "0.18.1",
|
|
45
46
|
"npm-run-all2": "8.0.4",
|
|
46
|
-
"oxlint": "1.
|
|
47
|
-
"oxlint-tsgolint": "0.0.0-8",
|
|
47
|
+
"oxlint": "1.14.0",
|
|
48
48
|
"rimraf": "6.0.1",
|
|
49
|
-
"typescript": "5.
|
|
49
|
+
"typescript": "5.9.2"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "run-s -ls build:clean build:tsc build:shebang build:chmod",
|
|
@@ -54,19 +54,21 @@
|
|
|
54
54
|
"build:clean": "echo 'Removing dist' && rimraf dist",
|
|
55
55
|
"build:shebang": "echo 'Fixing the shebangs' && add-shebangs --node --exclude 'dist/**/lib_*.js' 'dist/**/*.js'",
|
|
56
56
|
"build:tsc": "echo 'Transpiling TypeScript to dist (using tsc)' && tsc --erasableSyntaxOnly --libReplacement false",
|
|
57
|
+
"build:tsgo": "echo 'Transpiling TypeScript to dist (using tsgo)' && tsgo || (rimraf dist && false)",
|
|
57
58
|
"fix": "run-s -ls fix:biome fix:markdownlint",
|
|
58
59
|
"fix:biome": "echo 'Fixing with Biome' && biome check --write",
|
|
59
|
-
"fix:docbot": "echo 'Fixing with DocBot' && docbot --
|
|
60
|
+
"fix:docbot": "echo 'Fixing with DocBot' && docbot --prune --generate",
|
|
60
61
|
"fix:markdownlint": "echo 'Fixing with markdownlint' && markdownlint-cli2 '**/*.md' --fix",
|
|
61
|
-
"fix:oxlint": "echo 'Fixing with
|
|
62
|
+
"fix:oxlint": "echo 'Fixing with Oxlint' && oxlint --fix",
|
|
62
63
|
"lint": "run-s -ls lint:biome lint:oxlint lint:tsgolint lint:knip lint:markdownlint",
|
|
63
64
|
"lint:biome": "echo 'Linting with Biome' && biome check",
|
|
64
65
|
"lint:docbot": "echo 'Linting with DocBot' && docbot",
|
|
65
66
|
"lint:knip": "echo 'Linting with Knip' && knip",
|
|
66
|
-
"lint:markdownlint": "echo 'Linting with
|
|
67
|
-
"lint:oxlint": "echo 'Linting with
|
|
67
|
+
"lint:markdownlint": "echo 'Linting with Markdownlint' && markdownlint-cli2 '**/*.md'",
|
|
68
|
+
"lint:oxlint": "echo 'Linting with Oxlint' && oxlint",
|
|
68
69
|
"lint:tsc": "echo 'Linting with tsc' && tsc --noEmit --erasableSyntaxOnly --libReplacement false",
|
|
69
|
-
"lint:
|
|
70
|
+
"lint:tsgo": "echo 'Linting with tsgo' && tsgo --noEmit",
|
|
71
|
+
"lint:tsgolint": "echo 'Linting with tsgolint' && candide-tsgolint",
|
|
70
72
|
"test": "run-s -ls lint build"
|
|
71
73
|
}
|
|
72
74
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { abort_with_error } from "./lib_abort.js";
|
|
2
|
-
import { type_guard_is_boolean, type_guard_is_number, type_guard_is_object, type_guard_is_string, } from "./lib_type_guard.js";
|
|
3
|
-
export function assert_type_boolean(value) {
|
|
4
|
-
if (type_guard_is_boolean(value)) {
|
|
5
|
-
return value;
|
|
6
|
-
}
|
|
7
|
-
console.error(value);
|
|
8
|
-
return abort_with_error("Assertion failed: value is not a boolean");
|
|
9
|
-
}
|
|
10
|
-
export function assert_type_number(value) {
|
|
11
|
-
if (type_guard_is_number(value)) {
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
console.error(value);
|
|
15
|
-
return abort_with_error("Assertion failed: value is not a number");
|
|
16
|
-
}
|
|
17
|
-
export function assert_type_string(value) {
|
|
18
|
-
if (type_guard_is_string(value)) {
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
console.error(value);
|
|
22
|
-
return abort_with_error("Assertion failed: value is not a string");
|
|
23
|
-
}
|
|
24
|
-
export function assert_type_object(value) {
|
|
25
|
-
if (type_guard_is_object(value)) {
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
console.error(value);
|
|
29
|
-
return abort_with_error("Assertion failed: value is not an object");
|
|
30
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline";
|
|
2
|
-
import { ansi_blue, ansi_bold } from "./lib_ansi.js";
|
|
3
|
-
export async function tui_readline_confirm(message) {
|
|
4
|
-
const query = ansi_bold(ansi_blue(`${message} [Y/n] `));
|
|
5
|
-
const rl = createInterface({
|
|
6
|
-
input: process.stdin,
|
|
7
|
-
output: process.stdout,
|
|
8
|
-
});
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
rl.question(query, (answer) => {
|
|
11
|
-
rl.close();
|
|
12
|
-
const normalized_answer = answer.trim().toLowerCase();
|
|
13
|
-
resolve(normalized_answer === "" || normalized_answer === "y" || normalized_answer === "yes");
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function type_guard_is_boolean(value) {
|
|
2
|
-
return typeof value === "boolean";
|
|
3
|
-
}
|
|
4
|
-
export function type_guard_is_number(value) {
|
|
5
|
-
return typeof value === "number";
|
|
6
|
-
}
|
|
7
|
-
export function type_guard_is_string(value) {
|
|
8
|
-
return typeof value === "string";
|
|
9
|
-
}
|
|
10
|
-
export function type_guard_is_object(value) {
|
|
11
|
-
return typeof value === "object" && !Array.isArray(value) && value !== null;
|
|
12
|
-
}
|
|
13
|
-
export function type_guard_is_array(value) {
|
|
14
|
-
return Array.isArray(value);
|
|
15
|
-
}
|