commit-whisper 1.0.6 → 1.0.8

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 (2) hide show
  1. package/dist/index.js +50 -23
  2. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -163,6 +163,7 @@ function readProcessEnv() {
163
163
  }
164
164
 
165
165
  // src/config/config-store.ts
166
+ import { randomBytes } from "crypto";
166
167
  import { join } from "path";
167
168
  import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
168
169
  var PROVIDERS2 = /* @__PURE__ */ new Set(["ollama", "openai", "gemini", "anthropic", "openai-compatible"]);
@@ -289,7 +290,7 @@ async function writeSettings(env, data, io = defaultConfigStoreIo) {
289
290
  return finalPath;
290
291
  }
291
292
  function randomSuffix() {
292
- return Math.random().toString(36).slice(2, 10);
293
+ return randomBytes(6).toString("hex");
293
294
  }
294
295
 
295
296
  // src/config/sources.ts
@@ -493,6 +494,15 @@ function resolveRunConfig(input) {
493
494
  });
494
495
  }
495
496
 
497
+ // src/shared/url.ts
498
+ function stripTrailingSlashes(value) {
499
+ let end = value.length;
500
+ while (end > 0 && value.codePointAt(end - 1) === 47) {
501
+ end -= 1;
502
+ }
503
+ return value.slice(0, end);
504
+ }
505
+
496
506
  // src/narrate/preflight.ts
497
507
  var OLLAMA_DEFAULT_BASE_URL = "http://localhost:11434";
498
508
  var GEMINI_MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
@@ -528,7 +538,7 @@ function cleanBaseUrl(baseUrl) {
528
538
  if (baseUrl === void 0) {
529
539
  return void 0;
530
540
  }
531
- const trimmed = baseUrl.trim().replace(/\/+$/, "");
541
+ const trimmed = stripTrailingSlashes(baseUrl.trim());
532
542
  return trimmed === "" ? void 0 : trimmed;
533
543
  }
534
544
  function classifyModelsResponse(res, providerLabel) {
@@ -765,7 +775,7 @@ function mapValidate(json) {
765
775
  function resolveClient(deps) {
766
776
  return {
767
777
  doFetch: deps.fetchImpl ?? fetch,
768
- apiBase: (deps.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, ""),
778
+ apiBase: stripTrailingSlashes(deps.apiBase ?? DEFAULT_API_BASE),
769
779
  timeoutMs: deps.timeoutMs ?? DEFAULT_TIMEOUT_MS2
770
780
  };
771
781
  }
@@ -815,6 +825,7 @@ function createLemonSqueezyDeactivator(deps = {}) {
815
825
  }
816
826
 
817
827
  // src/license/store.ts
828
+ import { randomBytes as randomBytes2 } from "crypto";
818
829
  import { join as join2 } from "path";
819
830
  import { mkdir as mkdir2, readFile as readFile2, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
820
831
  var LICENSE_FILE_NAME = "license.json";
@@ -902,7 +913,7 @@ async function clearLicenseCache(env, io = defaultLicenseStoreIo) {
902
913
  await io.unlink(licenseFilePath(env));
903
914
  }
904
915
  function randomSuffix2() {
905
- return Math.random().toString(36).slice(2, 10);
916
+ return randomBytes2(6).toString("hex");
906
917
  }
907
918
 
908
919
  // src/retrieve/git.ts
@@ -1185,7 +1196,8 @@ function quoteArg(value) {
1185
1196
  if (value !== "" && /^[A-Za-z0-9_./:@%+=-]+$/.test(value)) {
1186
1197
  return value;
1187
1198
  }
1188
- return `'${value.replace(/'/g, "'\\''")}'`;
1199
+ const escaped = value.replaceAll("'", String.raw`'\''`);
1200
+ return `'${escaped}'`;
1189
1201
  }
1190
1202
  function interpretLimit(raw) {
1191
1203
  const trimmed = raw.trim();
@@ -1821,10 +1833,10 @@ function pad(n, width) {
1821
1833
  // src/analyze/groups/a-cadence.ts
1822
1834
  var DORMANT_GAP_SECONDS = 14 * 24 * 3600;
1823
1835
  function minOf(values) {
1824
- return values.reduce((m, v) => v < m ? v : m, values[0]);
1836
+ return values.reduce((m, v) => Math.min(v, m), values[0]);
1825
1837
  }
1826
1838
  function maxOf(values) {
1827
- return values.reduce((m, v) => v > m ? v : m, values[0]);
1839
+ return values.reduce((m, v) => Math.max(v, m), values[0]);
1828
1840
  }
1829
1841
  var VOLUME = { id: "a-commit-volume", group: "A", title: "Commit volume over time" };
1830
1842
  var CADENCE = { id: "a-commit-cadence", group: "A", title: "Commit frequency / cadence" };
@@ -2097,7 +2109,7 @@ var ownershipByArea = (model) => {
2097
2109
  topFiles: topAreas(fileAreas, HOTSPOT_TOP_FILES)
2098
2110
  });
2099
2111
  };
2100
- var CO_AUTHOR_TRAILER = /^[ \t]*co-authored-by:[ \t]*(.+?)[ \t]*$/gim;
2112
+ var CO_AUTHOR_TRAILER = /^[ \t]*co-authored-by:[ \t]*(.+)$/gim;
2101
2113
  function coAuthorKey(trailer) {
2102
2114
  const angle = /<([^<>]+)>/.exec(trailer);
2103
2115
  return (angle ? angle[1] : trailer).trim().toLowerCase();
@@ -2199,10 +2211,10 @@ function wordCount(text) {
2199
2211
  return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
2200
2212
  }
2201
2213
  function minOf2(values) {
2202
- return values.length === 0 ? 0 : values.reduce((m, v) => v < m ? v : m, values[0]);
2214
+ return values.length === 0 ? 0 : values.reduce((m, v) => Math.min(v, m), values[0]);
2203
2215
  }
2204
2216
  function maxOf3(values) {
2205
- return values.length === 0 ? 0 : values.reduce((m, v) => v > m ? v : m, values[0]);
2217
+ return values.length === 0 ? 0 : values.reduce((m, v) => Math.max(v, m), values[0]);
2206
2218
  }
2207
2219
  function sharePct(part, total) {
2208
2220
  return total === 0 ? 0 : round(part / total * 100);
@@ -3528,7 +3540,11 @@ function resolveModel(config) {
3528
3540
  return createOpenAICompatible({ name: "openai-compatible", baseURL, apiKey: config.aiKey?.reveal() })(model);
3529
3541
  }
3530
3542
  case "ollama": {
3531
- const baseURL = `${cleanBaseUrl2(config.llmBaseUrl) ?? OLLAMA_DEFAULT_BASE_URL2}/v1`.replace(/(\/v1)+$/, "/v1");
3543
+ let base = cleanBaseUrl2(config.llmBaseUrl) ?? OLLAMA_DEFAULT_BASE_URL2;
3544
+ while (base.endsWith("/v1")) {
3545
+ base = base.slice(0, -3);
3546
+ }
3547
+ const baseURL = `${base}/v1`;
3532
3548
  return createOpenAICompatible({ name: "ollama", baseURL })(model);
3533
3549
  }
3534
3550
  case void 0:
@@ -3564,7 +3580,7 @@ function cleanBaseUrl2(baseUrl) {
3564
3580
  if (baseUrl === void 0) {
3565
3581
  return void 0;
3566
3582
  }
3567
- const trimmed = baseUrl.trim().replace(/\/+$/, "");
3583
+ const trimmed = stripTrailingSlashes(baseUrl.trim());
3568
3584
  return trimmed === "" ? void 0 : trimmed;
3569
3585
  }
3570
3586
  function assertNeverProvider(provider) {
@@ -4809,10 +4825,17 @@ function resolveNumstatPath(raw) {
4809
4825
  if (!raw.includes(" => ")) {
4810
4826
  return raw;
4811
4827
  }
4812
- const brace = /^(.*)\{(.*) => (.*)\}(.*)$/.exec(raw);
4813
- if (brace) {
4814
- const [, prefix, , next, suffix] = brace;
4815
- return `${prefix}${next}${suffix}`.replace(/\/{2,}/g, "/");
4828
+ const open2 = raw.indexOf("{");
4829
+ const close = open2 === -1 ? -1 : raw.indexOf("}", open2 + 1);
4830
+ if (open2 !== -1 && close !== -1) {
4831
+ const inside = raw.slice(open2 + 1, close);
4832
+ const arrow = inside.indexOf(" => ");
4833
+ if (arrow !== -1) {
4834
+ const prefix = raw.slice(0, open2);
4835
+ const next = inside.slice(arrow + " => ".length);
4836
+ const suffix = raw.slice(close + 1);
4837
+ return `${prefix}${next}${suffix}`.replace(/\/{2,}/g, "/");
4838
+ }
4816
4839
  }
4817
4840
  return raw.slice(raw.indexOf(" => ") + " => ".length);
4818
4841
  }
@@ -5111,23 +5134,27 @@ async function runPipeline(config, deps = {}) {
5111
5134
  const outcome = await narrateOutcome(config, narrateConfig, analysis, narrate, preflightReason);
5112
5135
  const report = reportFromOutcome(analysis, outcome);
5113
5136
  const targets = planOutputs(config.outputFormats, config.outputPath);
5137
+ const htmlFilePath = await emitOutputs(report, targets, { writeStdout: writeStdout2, writeFile: writeFile3, ui: ui2 });
5138
+ if (autoOpen && htmlFilePath !== void 0) {
5139
+ await tryOpen(openBrowser, htmlFilePath, ui2);
5140
+ }
5141
+ return report.degraded ? ExitCode.Degraded : ExitCode.Success;
5142
+ }
5143
+ async function emitOutputs(report, targets, io) {
5114
5144
  let htmlFilePath;
5115
5145
  for (const target of targets) {
5116
5146
  const text = renderOne(report, target);
5117
5147
  if (target.destination.kind === "stdout") {
5118
- writeStdout2(text.endsWith("\n") ? text : `${text}
5148
+ io.writeStdout(text.endsWith("\n") ? text : `${text}
5119
5149
  `);
5120
5150
  } else {
5121
- await writeOne(writeFile3, target.destination.path, text, target.format, ui2);
5151
+ await writeOne(io.writeFile, target.destination.path, text, target.format, io.ui);
5122
5152
  if (target.format === "html") {
5123
5153
  htmlFilePath = target.destination.path;
5124
5154
  }
5125
5155
  }
5126
5156
  }
5127
- if (autoOpen && htmlFilePath !== void 0) {
5128
- await tryOpen(openBrowser, htmlFilePath, ui2);
5129
- }
5130
- return report.degraded ? ExitCode.Degraded : ExitCode.Success;
5157
+ return htmlFilePath;
5131
5158
  }
5132
5159
  function renderOne(report, target) {
5133
5160
  try {
@@ -5237,7 +5264,7 @@ function formatShowConfig(config, secrets) {
5237
5264
  }
5238
5265
 
5239
5266
  // src/cli/version.ts
5240
- var VERSION = "1.0.6";
5267
+ var VERSION = "1.0.8";
5241
5268
 
5242
5269
  // src/cli/cli.ts
5243
5270
  var PROVIDERS3 = ["ollama", "openai", "gemini", "anthropic", "openai-compatible"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commit-whisper",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Deterministic git history analysis with a grounded, BYOK AI narrative — terminal-native CLI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,24 +45,24 @@
45
45
  },
46
46
  "homepage": "https://github.com/georgiosnikitas/commit-whisper#readme",
47
47
  "dependencies": {
48
- "@ai-sdk/anthropic": "3.0.84",
49
- "@ai-sdk/google": "3.0.82",
50
- "@ai-sdk/openai": "3.0.71",
51
- "@ai-sdk/openai-compatible": "2.0.50",
48
+ "@ai-sdk/anthropic": "3.0.85",
49
+ "@ai-sdk/google": "3.0.83",
50
+ "@ai-sdk/openai": "3.0.72",
51
+ "@ai-sdk/openai-compatible": "2.0.51",
52
52
  "@clack/prompts": "1.5.1",
53
- "ai": "6.0.203",
53
+ "ai": "6.0.207",
54
54
  "commander": "15.0.0",
55
55
  "picocolors": "1.1.1",
56
56
  "zod": "4.4.3"
57
57
  },
58
58
  "devDependencies": {
59
- "@types/node": "22.19.21",
60
- "@vitest/coverage-v8": "4.1.8",
59
+ "@types/node": "25.9.3",
60
+ "@vitest/coverage-v8": "4.1.9",
61
61
  "eslint": "10.5.0",
62
62
  "tsup": "8.5.1",
63
63
  "typescript": "6.0.3",
64
- "typescript-eslint": "8.61.0",
65
- "vitest": "4.1.8"
64
+ "typescript-eslint": "8.61.1",
65
+ "vitest": "4.1.9"
66
66
  },
67
67
  "overrides": {
68
68
  "esbuild": "^0.28.1"