@kud/ai-conventional-commit-cli 1.1.0 → 2.0.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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadConfig
4
- } from "./chunk-F3BOAVBY.js";
4
+ } from "./chunk-U7UVALKR.js";
5
5
  import {
6
6
  OpenCodeProvider,
7
7
  abortMessage,
@@ -16,7 +16,7 @@ import {
16
16
  formatCommitTitle,
17
17
  renderCommitBlock,
18
18
  sectionTitle
19
- } from "./chunk-WW3N76NL.js";
19
+ } from "./chunk-ECRGQKAX.js";
20
20
 
21
21
  // src/index.ts
22
22
  import { Cli, Command, Option } from "clipanion";
@@ -873,7 +873,7 @@ var ModelsCommand = class extends Command {
873
873
  });
874
874
  async execute() {
875
875
  if (this.current) {
876
- const { loadConfigDetailed } = await import("./config-RHGCFLHQ.js");
876
+ const { loadConfigDetailed } = await import("./config-AZDENPAB.js");
877
877
  const { config } = await loadConfigDetailed();
878
878
  this.context.stdout.write(`${config.model} (source: ${config._sources.model})
879
879
  `);
@@ -917,7 +917,7 @@ var ModelsCommand = class extends Command {
917
917
  this.context.stdout.write(model + "\n");
918
918
  if (this.save) {
919
919
  try {
920
- const { saveGlobalConfig } = await import("./config-RHGCFLHQ.js");
920
+ const { saveGlobalConfig } = await import("./config-AZDENPAB.js");
921
921
  const path = saveGlobalConfig({ model });
922
922
  this.context.stdout.write(`Saved as default model in ${path}
923
923
  `);
@@ -952,7 +952,7 @@ var ConfigShowCommand = class extends Command {
952
952
  });
953
953
  json = Option.Boolean("--json", false, { description: "Output JSON including _sources" });
954
954
  async execute() {
955
- const { loadConfigDetailed } = await import("./config-RHGCFLHQ.js");
955
+ const { loadConfigDetailed } = await import("./config-AZDENPAB.js");
956
956
  const { config, raw } = await loadConfigDetailed();
957
957
  if (this.json) {
958
958
  this.context.stdout.write(JSON.stringify({ config, raw }, null, 2) + "\n");
@@ -977,7 +977,7 @@ var ConfigGetCommand = class extends Command {
977
977
  key = Option.String();
978
978
  withSource = Option.Boolean("--with-source", false, { description: "Append source label" });
979
979
  async execute() {
980
- const { loadConfigDetailed } = await import("./config-RHGCFLHQ.js");
980
+ const { loadConfigDetailed } = await import("./config-AZDENPAB.js");
981
981
  const { config } = await loadConfigDetailed();
982
982
  const key = this.key;
983
983
  if (!(key in config)) {
@@ -1026,7 +1026,7 @@ var ConfigSetCommand = class extends Command {
1026
1026
  } catch {
1027
1027
  }
1028
1028
  }
1029
- const { saveGlobalConfig } = await import("./config-RHGCFLHQ.js");
1029
+ const { saveGlobalConfig } = await import("./config-AZDENPAB.js");
1030
1030
  const path = saveGlobalConfig({ [this.key]: parsed });
1031
1031
  this.context.stdout.write(`Saved ${this.key} to ${path}
1032
1032
  `);
@@ -1050,7 +1050,7 @@ var RewordCommand = class extends Command {
1050
1050
  description: "Auto-confirm commit without prompting"
1051
1051
  });
1052
1052
  async execute() {
1053
- const { runReword } = await import("./reword-7GG233AE.js");
1053
+ const { runReword } = await import("./reword-4VB7EOET.js");
1054
1054
  const config = await loadConfig();
1055
1055
  if (this.style) config.style = this.style;
1056
1056
  if (this.model) config.model = this.model;
@@ -10,7 +10,7 @@ import {
10
10
  formatCommitTitle,
11
11
  renderCommitBlock,
12
12
  sectionTitle
13
- } from "./chunk-H4W6AMGZ.js";
13
+ } from "./chunk-ECRGQKAX.js";
14
14
 
15
15
  // src/workflow/reword.ts
16
16
  import chalk from "chalk";
@@ -115,7 +115,7 @@ async function runReword(config, hash) {
115
115
  const resolvedHash = (await git.revparse([hash])).trim();
116
116
  const headHash = (await git.revparse(["HEAD"])).trim();
117
117
  const isHead = headHash === resolvedHash || headHash.startsWith(resolvedHash);
118
- const { ok } = await inquirer.prompt([
118
+ const ok = config.yes || (await inquirer.prompt([
119
119
  {
120
120
  type: "list",
121
121
  name: "ok",
@@ -126,7 +126,7 @@ async function runReword(config, hash) {
126
126
  ],
127
127
  default: 0
128
128
  }
129
- ]);
129
+ ])).ok;
130
130
  if (!ok) {
131
131
  borderLine();
132
132
  abortMessage();
@@ -10,7 +10,7 @@ import {
10
10
  formatCommitTitle,
11
11
  renderCommitBlock,
12
12
  sectionTitle
13
- } from "./chunk-WW3N76NL.js";
13
+ } from "./chunk-EHJXGWTJ.js";
14
14
 
15
15
  // src/workflow/reword.ts
16
16
  import chalk from "chalk";
@@ -115,7 +115,7 @@ async function runReword(config, hash) {
115
115
  const resolvedHash = (await git.revparse([hash])).trim();
116
116
  const headHash = (await git.revparse(["HEAD"])).trim();
117
117
  const isHead = headHash === resolvedHash || headHash.startsWith(resolvedHash);
118
- const { ok } = await inquirer.prompt([
118
+ const ok = config.yes || (await inquirer.prompt([
119
119
  {
120
120
  type: "list",
121
121
  name: "ok",
@@ -126,7 +126,7 @@ async function runReword(config, hash) {
126
126
  ],
127
127
  default: 0
128
128
  }
129
- ]);
129
+ ])).ok;
130
130
  if (!ok) {
131
131
  borderLine();
132
132
  abortMessage();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/ai-conventional-commit-cli",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "Opinionated, style-aware AI assistant for crafting and splitting git commits (opencode-based, provider-agnostic).",
6
6
  "bin": {
@@ -24,6 +24,7 @@
24
24
  "dependencies": {
25
25
  "@commitlint/config-conventional": "^20.0.0",
26
26
  "@inquirer/prompts": "8.0.1",
27
+ "@opencode-ai/sdk": "1.3.3",
27
28
  "chalk": "^5.6.2",
28
29
  "clipanion": "^3.2.1",
29
30
  "cosmiconfig": "^9.0.0",
@@ -1,120 +0,0 @@
1
- // src/config.ts
2
- import { cosmiconfig } from "cosmiconfig";
3
- import { resolve, dirname, join } from "path";
4
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
- import { homedir } from "os";
6
- var DEFAULTS = {
7
- model: process.env.AICC_MODEL || "github-copilot/gpt-4.1",
8
- privacy: process.env.AICC_PRIVACY || "low",
9
- style: process.env.AICC_STYLE || "standard",
10
- styleSamples: parseInt(process.env.AICC_STYLE_SAMPLES || "120", 10),
11
- maxTokens: parseInt(process.env.AICC_MAX_TOKENS || "512", 10),
12
- maxFileLines: parseInt(process.env.AICC_MAX_FILE_LINES || "1000", 10),
13
- skipFilePatterns: [
14
- "**/package-lock.json",
15
- "**/yarn.lock",
16
- "**/pnpm-lock.yaml",
17
- "**/bun.lockb",
18
- "**/composer.lock",
19
- "**/Gemfile.lock",
20
- "**/Cargo.lock",
21
- "**/poetry.lock",
22
- "**/*.d.ts",
23
- "**/dist/**",
24
- "**/build/**",
25
- "**/.next/**",
26
- "**/out/**",
27
- "**/coverage/**",
28
- "**/*.min.js",
29
- "**/*.min.css",
30
- "**/*.map"
31
- ],
32
- cacheDir: ".git/.aicc-cache",
33
- plugins: [],
34
- verbose: process.env.AICC_VERBOSE === "true"
35
- };
36
- function getGlobalConfigPath() {
37
- const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
38
- return resolve(base, "ai-conventional-commit-cli", "aicc.json");
39
- }
40
- function saveGlobalConfig(partial) {
41
- const filePath = getGlobalConfigPath();
42
- const dir = dirname(filePath);
43
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
44
- let existing = {};
45
- if (existsSync(filePath)) {
46
- try {
47
- existing = JSON.parse(readFileSync(filePath, "utf8")) || {};
48
- } catch (e) {
49
- if (process.env.AICC_VERBOSE === "true") {
50
- console.error("[ai-cc] Failed to parse existing global config, overwriting.");
51
- }
52
- }
53
- }
54
- const merged = { ...existing, ...partial };
55
- writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
56
- return filePath;
57
- }
58
- async function loadConfig(cwd = process.cwd()) {
59
- return (await loadConfigDetailed(cwd)).config;
60
- }
61
- async function loadConfigDetailed(cwd = process.cwd()) {
62
- let globalCfg = {};
63
- const globalPath = getGlobalConfigPath();
64
- if (existsSync(globalPath)) {
65
- try {
66
- globalCfg = JSON.parse(readFileSync(globalPath, "utf8")) || {};
67
- } catch (e) {
68
- if (process.env.AICC_VERBOSE === "true") {
69
- console.error("[ai-cc] Failed to parse global config, ignoring.");
70
- }
71
- }
72
- }
73
- const explorer = cosmiconfig("aicc");
74
- const result = await explorer.search(cwd);
75
- const projectCfg = result?.config || {};
76
- const envCfg = {};
77
- if (process.env.AICC_MODEL) envCfg.model = process.env.AICC_MODEL;
78
- if (process.env.AICC_PRIVACY) envCfg.privacy = process.env.AICC_PRIVACY;
79
- if (process.env.AICC_STYLE) envCfg.style = process.env.AICC_STYLE;
80
- if (process.env.AICC_STYLE_SAMPLES)
81
- envCfg.styleSamples = parseInt(process.env.AICC_STYLE_SAMPLES, 10);
82
- if (process.env.AICC_MAX_TOKENS) envCfg.maxTokens = parseInt(process.env.AICC_MAX_TOKENS, 10);
83
- if (process.env.AICC_MAX_FILE_LINES)
84
- envCfg.maxFileLines = parseInt(process.env.AICC_MAX_FILE_LINES, 10);
85
- if (process.env.AICC_VERBOSE) envCfg.verbose = process.env.AICC_VERBOSE === "true";
86
- const merged = {
87
- ...DEFAULTS,
88
- ...globalCfg,
89
- ...projectCfg,
90
- ...envCfg
91
- };
92
- merged.plugins = (merged.plugins || []).filter((p) => {
93
- const abs = resolve(cwd, p);
94
- return existsSync(abs);
95
- });
96
- if (!merged.skipFilePatterns) {
97
- merged.skipFilePatterns = DEFAULTS.skipFilePatterns;
98
- }
99
- const sources = Object.keys(merged).reduce((acc, key) => {
100
- const k = key;
101
- let src = "default";
102
- if (k in globalCfg) src = "global";
103
- if (k in projectCfg) src = "project";
104
- if (k in envCfg) src = "env";
105
- acc[k] = src;
106
- return acc;
107
- }, {});
108
- const withMeta = Object.assign(merged, { _sources: sources });
109
- return {
110
- config: withMeta,
111
- raw: { defaults: DEFAULTS, global: globalCfg, project: projectCfg, env: envCfg }
112
- };
113
- }
114
-
115
- export {
116
- getGlobalConfigPath,
117
- saveGlobalConfig,
118
- loadConfig,
119
- loadConfigDetailed
120
- };
@@ -1,12 +0,0 @@
1
- import {
2
- getGlobalConfigPath,
3
- loadConfig,
4
- loadConfigDetailed,
5
- saveGlobalConfig
6
- } from "./chunk-F3BOAVBY.js";
7
- export {
8
- getGlobalConfigPath,
9
- loadConfig,
10
- loadConfigDetailed,
11
- saveGlobalConfig
12
- };
@@ -1,150 +0,0 @@
1
- import {
2
- OpenCodeProvider,
3
- animateHeaderBase,
4
- borderLine,
5
- buildRefineMessages,
6
- extractJSON,
7
- formatCommitTitle,
8
- renderCommitBlock,
9
- sectionTitle
10
- } from "./chunk-YIXP5EWA.js";
11
-
12
- // src/workflow/reword.ts
13
- import chalk from "chalk";
14
- import ora from "ora";
15
- import inquirer from "inquirer";
16
- import { simpleGit } from "simple-git";
17
- var git = simpleGit();
18
- async function getCommitMessage(hash) {
19
- try {
20
- const raw = await git.show([`${hash}`, "--quiet", "--format=%P%n%B"]);
21
- const lines = raw.split("\n");
22
- const parentsLine = lines.shift() || "";
23
- const parents = parentsLine.trim().length ? parentsLine.trim().split(/\s+/) : [];
24
- const message = lines.join("\n").trim();
25
- if (!message) return null;
26
- const [first, ...rest] = message.split("\n");
27
- const body = rest.join("\n").trim() || void 0;
28
- return { title: first, body, parents };
29
- } catch {
30
- return null;
31
- }
32
- }
33
- async function runReword(config, hash) {
34
- const startedAt = Date.now();
35
- const commit = await getCommitMessage(hash);
36
- if (!commit) {
37
- console.log(`Commit not found: ${hash}`);
38
- return;
39
- }
40
- if (commit.parents.length > 1) {
41
- console.log("Refusing to reword a merge commit (multiple parents).");
42
- return;
43
- }
44
- if (process.stdout.isTTY) {
45
- await animateHeaderBase("ai-conventional-commit", config.model);
46
- borderLine();
47
- }
48
- sectionTitle("Original commit");
49
- borderLine(chalk.yellow(commit.title));
50
- if (commit.body) {
51
- commit.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
52
- }
53
- borderLine();
54
- const instructions = [
55
- "Improve clarity & conformity to Conventional Commits while preserving meaning."
56
- ];
57
- const syntheticPlan = {
58
- commits: [
59
- {
60
- title: commit.title,
61
- body: commit.body,
62
- score: 0,
63
- reasons: []
64
- }
65
- ]
66
- };
67
- const provider = new OpenCodeProvider(config.model);
68
- const spinner = ora({ text: "Calling model", spinner: "dots" }).start();
69
- let refined = null;
70
- try {
71
- const messages = buildRefineMessages({
72
- originalPlan: syntheticPlan,
73
- index: 0,
74
- instructions,
75
- config
76
- });
77
- const raw = await provider.chat(messages, { maxTokens: config.maxTokens });
78
- refined = await extractJSON(raw);
79
- } catch (e) {
80
- spinner.fail("Model call failed: " + (e?.message || e));
81
- return;
82
- }
83
- spinner.stop();
84
- if (!refined || !refined.commits.length) {
85
- console.log("No refined commit produced.");
86
- return;
87
- }
88
- const candidate = refined.commits[0];
89
- candidate.title = formatCommitTitle(candidate.title, {
90
- allowGitmoji: config.style === "gitmoji" || config.style === "gitmoji-pure",
91
- mode: config.style
92
- });
93
- sectionTitle("Proposed commit");
94
- renderCommitBlock({
95
- title: chalk.yellow(candidate.title),
96
- body: candidate.body,
97
- hideMessageLabel: true
98
- });
99
- borderLine();
100
- const isHead = (await git.revparse(["HEAD"])).startsWith(hash) || await git.revparse([hash]) === await git.revparse(["HEAD"]);
101
- const { ok } = await inquirer.prompt([
102
- {
103
- type: "list",
104
- name: "ok",
105
- message: isHead ? "Amend HEAD with this message?" : "Use this new message (show rebase instructions)?",
106
- choices: [
107
- { name: "Yes", value: true },
108
- { name: "No", value: false }
109
- ],
110
- default: 0
111
- }
112
- ]);
113
- if (!ok) {
114
- borderLine("Aborted.");
115
- return;
116
- }
117
- if (isHead) {
118
- const full = candidate.body ? `${candidate.title}
119
-
120
- ${candidate.body}` : candidate.title;
121
- try {
122
- await git.commit(full, { "--amend": null });
123
- borderLine("Amended HEAD.");
124
- } catch (e) {
125
- borderLine("Failed to amend: " + (e?.message || e));
126
- }
127
- } else {
128
- const full = candidate.body ? `${candidate.title}
129
-
130
- ${candidate.body}` : candidate.title;
131
- sectionTitle("Apply manually");
132
- borderLine("Interactive rebase steps:");
133
- borderLine(`1. git rebase -i ${hash}~1 --reword`);
134
- borderLine(
135
- "2. In the editor, ensure the line for the commit is kept as reword (or change pick \u2192 reword)."
136
- );
137
- borderLine("3. When prompted, replace the message with below:");
138
- borderLine();
139
- borderLine(candidate.title);
140
- if (candidate.body) {
141
- candidate.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
142
- }
143
- borderLine();
144
- }
145
- const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(1) + "s";
146
- borderLine(`Done in ${elapsed}.`);
147
- }
148
- export {
149
- runReword
150
- };
@@ -1,150 +0,0 @@
1
- import {
2
- OpenCodeProvider,
3
- animateHeaderBase,
4
- borderLine,
5
- buildRefineMessages,
6
- extractJSON,
7
- formatCommitTitle,
8
- renderCommitBlock,
9
- sectionTitle
10
- } from "./chunk-H4W6AMGZ.js";
11
-
12
- // src/workflow/reword.ts
13
- import chalk from "chalk";
14
- import ora from "ora";
15
- import inquirer from "inquirer";
16
- import { simpleGit } from "simple-git";
17
- var git = simpleGit();
18
- async function getCommitMessage(hash) {
19
- try {
20
- const raw = await git.show([`${hash}`, "--quiet", "--format=%P%n%B"]);
21
- const lines = raw.split("\n");
22
- const parentsLine = lines.shift() || "";
23
- const parents = parentsLine.trim().length ? parentsLine.trim().split(/\s+/) : [];
24
- const message = lines.join("\n").trim();
25
- if (!message) return null;
26
- const [first, ...rest] = message.split("\n");
27
- const body = rest.join("\n").trim() || void 0;
28
- return { title: first, body, parents };
29
- } catch {
30
- return null;
31
- }
32
- }
33
- async function runReword(config, hash) {
34
- const startedAt = Date.now();
35
- const commit = await getCommitMessage(hash);
36
- if (!commit) {
37
- console.log(`Commit not found: ${hash}`);
38
- return;
39
- }
40
- if (commit.parents.length > 1) {
41
- console.log("Refusing to reword a merge commit (multiple parents).");
42
- return;
43
- }
44
- if (process.stdout.isTTY) {
45
- await animateHeaderBase("ai-conventional-commit", config.model);
46
- borderLine();
47
- }
48
- sectionTitle("Original commit");
49
- borderLine(chalk.yellow(commit.title));
50
- if (commit.body) {
51
- commit.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
52
- }
53
- borderLine();
54
- const instructions = [
55
- "Improve clarity & conformity to Conventional Commits while preserving meaning."
56
- ];
57
- const syntheticPlan = {
58
- commits: [
59
- {
60
- title: commit.title,
61
- body: commit.body,
62
- score: 0,
63
- reasons: []
64
- }
65
- ]
66
- };
67
- const provider = new OpenCodeProvider(config.model);
68
- const spinner = ora({ text: "Calling model", spinner: "dots" }).start();
69
- let refined = null;
70
- try {
71
- const messages = buildRefineMessages({
72
- originalPlan: syntheticPlan,
73
- index: 0,
74
- instructions,
75
- config
76
- });
77
- const raw = await provider.chat(messages, { maxTokens: config.maxTokens });
78
- refined = await extractJSON(raw);
79
- } catch (e) {
80
- spinner.fail("Model call failed: " + (e?.message || e));
81
- return;
82
- }
83
- spinner.stop();
84
- if (!refined || !refined.commits.length) {
85
- console.log("No refined commit produced.");
86
- return;
87
- }
88
- const candidate = refined.commits[0];
89
- candidate.title = formatCommitTitle(candidate.title, {
90
- allowGitmoji: config.style === "gitmoji" || config.style === "gitmoji-pure",
91
- mode: config.style
92
- });
93
- sectionTitle("Proposed commit");
94
- renderCommitBlock({
95
- title: chalk.yellow(candidate.title),
96
- body: candidate.body,
97
- hideMessageLabel: true
98
- });
99
- borderLine();
100
- const isHead = (await git.revparse(["HEAD"])).startsWith(hash) || await git.revparse([hash]) === await git.revparse(["HEAD"]);
101
- const { ok } = await inquirer.prompt([
102
- {
103
- type: "list",
104
- name: "ok",
105
- message: isHead ? "Amend HEAD with this message?" : "Use this new message (show rebase instructions)?",
106
- choices: [
107
- { name: "Yes", value: true },
108
- { name: "No", value: false }
109
- ],
110
- default: 0
111
- }
112
- ]);
113
- if (!ok) {
114
- borderLine("Aborted.");
115
- return;
116
- }
117
- if (isHead) {
118
- const full = candidate.body ? `${candidate.title}
119
-
120
- ${candidate.body}` : candidate.title;
121
- try {
122
- await git.commit(full, { "--amend": null });
123
- borderLine("Amended HEAD.");
124
- } catch (e) {
125
- borderLine("Failed to amend: " + (e?.message || e));
126
- }
127
- } else {
128
- const full = candidate.body ? `${candidate.title}
129
-
130
- ${candidate.body}` : candidate.title;
131
- sectionTitle("Apply manually");
132
- borderLine("Interactive rebase steps:");
133
- borderLine(`1. git rebase -i ${hash}~1 --reword`);
134
- borderLine(
135
- "2. In the editor, ensure the line for the commit is kept as reword (or change pick \u2192 reword)."
136
- );
137
- borderLine("3. When prompted, replace the message with below:");
138
- borderLine();
139
- borderLine(candidate.title);
140
- if (candidate.body) {
141
- candidate.body.split("\n").forEach((l) => l.trim().length ? borderLine(l) : borderLine());
142
- }
143
- borderLine();
144
- }
145
- const elapsed = ((Date.now() - startedAt) / 1e3).toFixed(1) + "s";
146
- borderLine(`Done in ${elapsed}.`);
147
- }
148
- export {
149
- runReword
150
- };