@chof64/aicommit 0.1.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/aicommit.js +186 -0
- package/dist/aicommit.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chad Fernandez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @chof64/aicommit
|
|
2
|
+
|
|
3
|
+
AI-powered commit message generator. Reads your staged `git diff`, sends it
|
|
4
|
+
to the [opencode.ai zen](https://opencode.ai) chat completions API, and
|
|
5
|
+
writes a conventional-commit message after a quick confirmation prompt.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js **20+** (uses native `fetch`)
|
|
10
|
+
- `git` on `PATH`
|
|
11
|
+
- An [opencode.ai](https://opencode.ai) API key exposed as `OPENCODE_API_KEY`
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm i -g @chof64/aicommit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configure
|
|
20
|
+
|
|
21
|
+
Export your opencode.ai API key in your shell rc:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
export OPENCODE_API_KEY=<your-key>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Stage your changes as usual, then run `aicommit`:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
git add .
|
|
33
|
+
aicommit
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Add a hint to steer the message — useful for non-obvious diffs:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
git add src/auth.ts
|
|
40
|
+
aicommit fix race in token refresh
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Flags:
|
|
44
|
+
|
|
45
|
+
| Flag | Description |
|
|
46
|
+
| ----------------- | -------------------------------------------------------- |
|
|
47
|
+
| `--dry-run` | Print the generated message, do not commit. |
|
|
48
|
+
| `-v` / `--verbose`| Echo verbose progress to stderr (network, retries). |
|
|
49
|
+
| `-V` / `--version`| Print the version and exit. |
|
|
50
|
+
| `-h` / `--help` | Print the help text and exit. |
|
|
51
|
+
|
|
52
|
+
You will always be asked to confirm before `git commit` runs. Press `n` (or
|
|
53
|
+
`N`) to abort; anything else (including just hitting Enter) confirms.
|
|
54
|
+
|
|
55
|
+
## How it works
|
|
56
|
+
|
|
57
|
+
1. Runs `git diff --cached` and aborts if nothing is staged.
|
|
58
|
+
2. Sends the diff (plus any hint) to
|
|
59
|
+
`https://opencode.ai/zen/v1/chat/completions` with model `big-pickle`.
|
|
60
|
+
3. Asks the LLM for a single conventional-commit message
|
|
61
|
+
(`<type>: <description>`).
|
|
62
|
+
4. Shows you the result, waits for `Y/n`, then runs `git commit -m`.
|
|
63
|
+
|
|
64
|
+
The full prompt sent to the model is in
|
|
65
|
+
[`src/config.ts`](./src/config.ts) — see `SYSTEM_PROMPT` and `USER_PROMPT_TAIL`.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
[MIT](./LICENSE)
|
package/dist/aicommit.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
var ENDPOINT = "https://opencode.ai/zen/v1/chat/completions";
|
|
9
|
+
var MODEL = "big-pickle";
|
|
10
|
+
var TIMEOUT_MS = 6e4;
|
|
11
|
+
var RETRY_DELAY_MS = 3e3;
|
|
12
|
+
var MAX_RETRIES = 1;
|
|
13
|
+
var SYSTEM_PROMPT = "You are a helpful assistant that generates commit messages in conventional commit format (<type>: <description>). Output ONLY the commit message. No explanation, no markdown, no code blocks.";
|
|
14
|
+
var USER_PROMPT_TAIL = "Given these staged changes, output ONLY the commit message in conventional commit format (<type>: <description>). No explanation, no markdown, no code blocks.";
|
|
15
|
+
var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
16
|
+
|
|
17
|
+
// src/logger.ts
|
|
18
|
+
var verbose = false;
|
|
19
|
+
function setVerbose(value) {
|
|
20
|
+
verbose = value;
|
|
21
|
+
}
|
|
22
|
+
function log(msg) {
|
|
23
|
+
process.stdout.write(`\u2192 ${msg}
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
function logVerbose(msg) {
|
|
27
|
+
if (verbose) {
|
|
28
|
+
process.stderr.write(`\u2192 [VERBOSE] ${msg}
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function logError(msg) {
|
|
33
|
+
process.stderr.write(`${msg}
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/api.ts
|
|
38
|
+
function buildMessages(hintPrompt, diff) {
|
|
39
|
+
const userContent = hintPrompt ? `${hintPrompt}${USER_PROMPT_TAIL}
|
|
40
|
+
|
|
41
|
+
${diff}` : `${USER_PROMPT_TAIL}
|
|
42
|
+
|
|
43
|
+
${diff}`;
|
|
44
|
+
return [
|
|
45
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
46
|
+
{ role: "user", content: userContent }
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
async function callOnce(messages, apiKey, signal) {
|
|
50
|
+
const response = await fetch(ENDPOINT, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
signal,
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${apiKey}`,
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"User-Agent": USER_AGENT
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ model: MODEL, messages, stream: false })
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
62
|
+
}
|
|
63
|
+
return await response.json();
|
|
64
|
+
}
|
|
65
|
+
async function callWithRetry(messages, apiKey) {
|
|
66
|
+
let lastError;
|
|
67
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
70
|
+
try {
|
|
71
|
+
logVerbose("Sending request to API...");
|
|
72
|
+
const data = await callOnce(messages, apiKey, controller.signal);
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
logVerbose("Response received, parsing...");
|
|
75
|
+
return data;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
lastError = err;
|
|
79
|
+
logVerbose(`Retry attempt ${attempt + 1}/${MAX_RETRIES + 1} failed: ${err}`);
|
|
80
|
+
if (attempt < MAX_RETRIES) {
|
|
81
|
+
logError(
|
|
82
|
+
`API request failed (attempt ${attempt + 1}/${MAX_RETRIES + 1}), retrying in ${RETRY_DELAY_MS / 1e3}s...`
|
|
83
|
+
);
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
logError(`Error: failed to generate commit message: ${lastError}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
function parseCommitMessage(data) {
|
|
92
|
+
const content = data.choices?.[0]?.message?.content?.trim();
|
|
93
|
+
if (!content || content === "null") {
|
|
94
|
+
logError("Error: invalid API response format: empty content");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
logVerbose(`Parsed commit message: ${content}`);
|
|
98
|
+
return content;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/git.ts
|
|
102
|
+
import { execFileSync } from "child_process";
|
|
103
|
+
function getStagedDiff() {
|
|
104
|
+
logVerbose("Checking for staged changes...");
|
|
105
|
+
const diff = execFileSync("git", ["diff", "--cached"], { encoding: "utf-8" }).trim();
|
|
106
|
+
logVerbose(`Staged diff: ${diff.length} bytes`);
|
|
107
|
+
if (!diff) {
|
|
108
|
+
logError("No staged files. Run 'git add <files>' first.");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
return diff;
|
|
112
|
+
}
|
|
113
|
+
function executeCommit(message) {
|
|
114
|
+
process.stdout.write("\u2192 Committing...\n");
|
|
115
|
+
try {
|
|
116
|
+
execFileSync("git", ["commit", "-m", message], { stdio: "inherit" });
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const stderr = err.stderr;
|
|
119
|
+
const detail = stderr ? stderr.toString().trim() : String(err);
|
|
120
|
+
logError(`Error: git commit failed: ${detail}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write("Done.\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/cli.ts
|
|
127
|
+
var require2 = createRequire(import.meta.url);
|
|
128
|
+
var pkg = require2("../package.json");
|
|
129
|
+
function confirm(message) {
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
process.stdout.write(`
|
|
132
|
+
${message}
|
|
133
|
+
|
|
134
|
+
`);
|
|
135
|
+
process.stdout.write("Proceed with commit? [Y/n] ");
|
|
136
|
+
const onData = (chunk) => {
|
|
137
|
+
const reply = chunk.toString().trim().toLowerCase();
|
|
138
|
+
process.stdin.removeListener("data", onData);
|
|
139
|
+
process.stdin.pause();
|
|
140
|
+
if (reply === "n") {
|
|
141
|
+
process.stdout.write("Aborted.\n");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
resolve();
|
|
145
|
+
};
|
|
146
|
+
process.stdin.resume();
|
|
147
|
+
process.stdin.on("data", onData);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function run() {
|
|
151
|
+
const program = new Command();
|
|
152
|
+
program.name("aicommit").description("AI-powered commit message generator").version(pkg.version).option("--dry-run", "generate commit message without committing").option("-v, --verbose", "enable verbose output to stderr").argument("[hint...]", "optional hint/context for the commit message").action(async (hintArgs, opts) => {
|
|
153
|
+
const options = { dryRun: opts.dryRun, verbose: opts.verbose };
|
|
154
|
+
setVerbose(Boolean(options.verbose));
|
|
155
|
+
const hint = hintArgs.join(" ");
|
|
156
|
+
const hintPrompt = hint ? `Context/hint: ${hint} ` : "";
|
|
157
|
+
logVerbose("Starting aicommit");
|
|
158
|
+
if (options.dryRun) logVerbose("Dry-run mode enabled");
|
|
159
|
+
log("Checking for staged changes...");
|
|
160
|
+
const diff = getStagedDiff();
|
|
161
|
+
log("Generating commit message...");
|
|
162
|
+
const apiKey = process.env.OPENCODE_API_KEY;
|
|
163
|
+
if (!apiKey) {
|
|
164
|
+
logError("Error: OPENCODE_API_KEY environment variable not set");
|
|
165
|
+
logError("Set it with: export OPENCODE_API_KEY=<your-key>");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const messages = buildMessages(hintPrompt, diff);
|
|
169
|
+
const response = await callWithRetry(messages, apiKey);
|
|
170
|
+
const message = parseCommitMessage(response);
|
|
171
|
+
await confirm(message);
|
|
172
|
+
if (options.dryRun) {
|
|
173
|
+
process.stdout.write("Dry run complete.\n");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
executeCommit(message);
|
|
177
|
+
});
|
|
178
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
179
|
+
logError(`Error: ${err}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/index.ts
|
|
185
|
+
run();
|
|
186
|
+
//# sourceMappingURL=aicommit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/logger.ts","../src/api.ts","../src/git.ts","../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { buildMessages, callWithRetry, parseCommitMessage } from \"./api.js\";\nimport { executeCommit, getStagedDiff } from \"./git.js\";\nimport { log, logError, logVerbose, setVerbose } from \"./logger.js\";\nimport type { CliOptions } from \"./types.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version: string };\n\n/**\n * Ask the user to confirm the proposed commit message. Default is yes;\n * only the literal `n` (case-insensitive) aborts.\n */\nfunction confirm(message: string): Promise<void> {\n return new Promise((resolve) => {\n process.stdout.write(`\\n ${message}\\n\\n`);\n process.stdout.write(\"Proceed with commit? [Y/n] \");\n\n const onData = (chunk: Buffer) => {\n const reply = chunk.toString().trim().toLowerCase();\n process.stdin.removeListener(\"data\", onData);\n process.stdin.pause();\n if (reply === \"n\") {\n process.stdout.write(\"Aborted.\\n\");\n process.exit(0);\n }\n resolve();\n };\n\n process.stdin.resume();\n process.stdin.on(\"data\", onData);\n });\n}\n\n/** Build and run the commander program. */\nexport function run(): void {\n const program = new Command();\n\n program\n .name(\"aicommit\")\n .description(\"AI-powered commit message generator\")\n .version(pkg.version)\n .option(\"--dry-run\", \"generate commit message without committing\")\n .option(\"-v, --verbose\", \"enable verbose output to stderr\")\n .argument(\"[hint...]\", \"optional hint/context for the commit message\")\n .action(async (hintArgs: string[], opts: CliOptions) => {\n const options: CliOptions = { dryRun: opts.dryRun, verbose: opts.verbose };\n setVerbose(Boolean(options.verbose));\n\n const hint = hintArgs.join(\" \");\n const hintPrompt = hint ? `Context/hint: ${hint} ` : \"\";\n\n logVerbose(\"Starting aicommit\");\n if (options.dryRun) logVerbose(\"Dry-run mode enabled\");\n\n log(\"Checking for staged changes...\");\n const diff = getStagedDiff();\n\n log(\"Generating commit message...\");\n const apiKey = process.env.OPENCODE_API_KEY;\n if (!apiKey) {\n logError(\"Error: OPENCODE_API_KEY environment variable not set\");\n logError(\"Set it with: export OPENCODE_API_KEY=<your-key>\");\n process.exit(1);\n }\n\n const messages = buildMessages(hintPrompt, diff);\n const response = await callWithRetry(messages, apiKey);\n const message = parseCommitMessage(response);\n\n await confirm(message);\n\n if (options.dryRun) {\n process.stdout.write(\"Dry run complete.\\n\");\n return;\n }\n executeCommit(message);\n });\n\n program.parseAsync(process.argv).catch((err) => {\n logError(`Error: ${err}`);\n process.exit(1);\n });\n}\n","/** Endpoint and timing constants for the opencode.ai zen API. */\nexport const ENDPOINT = \"https://opencode.ai/zen/v1/chat/completions\";\nexport const MODEL = \"big-pickle\";\nexport const TIMEOUT_MS = 60_000;\nexport const RETRY_DELAY_MS = 3_000;\nexport const MAX_RETRIES = 1;\n\n/** System prompt that frames the model as a commit-message generator. */\nexport const SYSTEM_PROMPT =\n \"You are a helpful assistant that generates commit messages in conventional commit format (<type>: <description>). Output ONLY the commit message. No explanation, no markdown, no code blocks.\";\n\n/** User-prompt tail appended after any hint prefix. */\nexport const USER_PROMPT_TAIL =\n \"Given these staged changes, output ONLY the commit message in conventional commit format (<type>: <description>). No explanation, no markdown, no code blocks.\";\n\nexport const USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\";\n","/**\n * Tiny logging helpers. `log` is user-facing progress on stdout; `logVerbose`\n * is debug-level info on stderr; `logError` is for failures on stderr.\n */\nlet verbose = false;\n\nexport function setVerbose(value: boolean): void {\n verbose = value;\n}\n\nexport function isVerbose(): boolean {\n return verbose;\n}\n\nexport function log(msg: string): void {\n process.stdout.write(`→ ${msg}\\n`);\n}\n\nexport function logVerbose(msg: string): void {\n if (verbose) {\n process.stderr.write(`→ [VERBOSE] ${msg}\\n`);\n }\n}\n\nexport function logError(msg: string): void {\n process.stderr.write(`${msg}\\n`);\n}\n","import {\n ENDPOINT,\n MAX_RETRIES,\n MODEL,\n RETRY_DELAY_MS,\n SYSTEM_PROMPT,\n TIMEOUT_MS,\n USER_AGENT,\n USER_PROMPT_TAIL,\n} from \"./config.js\";\nimport { logError, logVerbose } from \"./logger.js\";\nimport type { ApiResponse, ChatMessage } from \"./types.js\";\n\n/** Build the system+user message pair for the chat-completions API. */\nexport function buildMessages(hintPrompt: string, diff: string): ChatMessage[] {\n const userContent = hintPrompt\n ? `${hintPrompt}${USER_PROMPT_TAIL}\\n\\n${diff}`\n : `${USER_PROMPT_TAIL}\\n\\n${diff}`;\n\n return [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: userContent },\n ];\n}\n\n/** One raw API call. Throws on non-2xx, network failure, or abort. */\nasync function callOnce(\n messages: ChatMessage[],\n apiKey: string,\n signal: AbortSignal,\n): Promise<ApiResponse> {\n const response = await fetch(ENDPOINT, {\n method: \"POST\",\n signal,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": USER_AGENT,\n },\n body: JSON.stringify({ model: MODEL, messages, stream: false }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} ${response.statusText}`);\n }\n return (await response.json()) as ApiResponse;\n}\n\n/** Call the API with one automatic retry on failure. Exits on terminal failure. */\nexport async function callWithRetry(messages: ChatMessage[], apiKey: string): Promise<ApiResponse> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);\n\n try {\n logVerbose(\"Sending request to API...\");\n const data = await callOnce(messages, apiKey, controller.signal);\n clearTimeout(timer);\n logVerbose(\"Response received, parsing...\");\n return data;\n } catch (err) {\n clearTimeout(timer);\n lastError = err;\n logVerbose(`Retry attempt ${attempt + 1}/${MAX_RETRIES + 1} failed: ${err}`);\n\n if (attempt < MAX_RETRIES) {\n logError(\n `API request failed (attempt ${attempt + 1}/${MAX_RETRIES + 1}), retrying in ${RETRY_DELAY_MS / 1000}s...`,\n );\n await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));\n }\n }\n }\n\n logError(`Error: failed to generate commit message: ${lastError}`);\n process.exit(1);\n}\n\n/** Extract the commit message text from the API response. Exits on bad shape. */\nexport function parseCommitMessage(data: ApiResponse): string {\n const content = data.choices?.[0]?.message?.content?.trim();\n if (!content || content === \"null\") {\n logError(\"Error: invalid API response format: empty content\");\n process.exit(1);\n }\n logVerbose(`Parsed commit message: ${content}`);\n return content;\n}\n","import { execFileSync } from \"node:child_process\";\nimport { logError, logVerbose } from \"./logger.js\";\n\n/** Read the staged diff. Exits with a helpful message if nothing is staged. */\nexport function getStagedDiff(): string {\n logVerbose(\"Checking for staged changes...\");\n\n const diff = execFileSync(\"git\", [\"diff\", \"--cached\"], { encoding: \"utf-8\" }).trim();\n logVerbose(`Staged diff: ${diff.length} bytes`);\n\n if (!diff) {\n logError(\"No staged files. Run 'git add <files>' first.\");\n process.exit(1);\n }\n\n return diff;\n}\n\n/** Run `git commit -m <message>`. Exits with git's stderr on failure. */\nexport function executeCommit(message: string): void {\n process.stdout.write(\"→ Committing...\\n\");\n\n try {\n execFileSync(\"git\", [\"commit\", \"-m\", message], { stdio: \"inherit\" });\n } catch (err) {\n const stderr = (err as { stderr?: Buffer | string }).stderr;\n const detail = stderr ? stderr.toString().trim() : String(err);\n logError(`Error: git commit failed: ${detail}`);\n process.exit(1);\n }\n\n process.stdout.write(\"Done.\\n\");\n}\n","import { run } from \"./cli.js\";\n\nrun();\n"],"mappings":"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","names":["require"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chof64/aicommit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered commit message generator using opencode.ai zen",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"aicommit": "./dist/aicommit.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsx src/index.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"lint": "biome check",
|
|
24
|
+
"format": "biome format --write",
|
|
25
|
+
"check": "biome check --write",
|
|
26
|
+
"prepublishOnly": "npm run build && npm test"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"commander": "^15.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@biomejs/biome": "^2.5.0",
|
|
33
|
+
"@types/node": "^25.9.3",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"tsx": "^4.22.4",
|
|
36
|
+
"typescript": "^6.0.3",
|
|
37
|
+
"vitest": "^4.1.8"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|