@thankarezos/git-ai-commit 0.1.2 → 0.1.4
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 -3
- package/bin/git-aic.js +81 -11
- package/bin/presets.js +8 -4
- package/bin/run-provider.js +41 -13
- package/bin/spawn.js +97 -0
- package/bin/wizard.js +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# git-aic
|
|
2
2
|
|
|
3
|
-
Generate a git commit message from your staged diff using any AI CLI you already have on your machine (Claude Code,
|
|
3
|
+
Generate a git commit message from your staged diff using any AI CLI you already have on your machine (Claude Code, Antigravity CLI, Codex, `llm`, Ollama, or a custom command). The message opens in your editor — save to commit, quit without saving to abort.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
$ git aic -ap "mention the migration"
|
|
@@ -44,13 +44,14 @@ First run launches an **interactive wizard** that asks you to pick a provider, e
|
|
|
44
44
|
| `-a`, `--add` | Run `git add -A` before generating |
|
|
45
45
|
| `-p "..."`, `--prompt "..."` | Append extra instructions to the AI prompt |
|
|
46
46
|
| `-ap "..."`, `-pa "..."` | Combine `-a` and `-p` |
|
|
47
|
-
| `--provider <name>`, `-P <name>` | One-off provider override. `<name>` matches a preset command (`claude`, `
|
|
47
|
+
| `--provider <name>`, `-P <name>` | One-off provider override. `<name>` matches a preset command (`claude`, `agy`, `codex`, `opencode`, `llm`, `ollama`). |
|
|
48
48
|
| `--no-commit` | Generate and edit, but skip the `git commit` |
|
|
49
49
|
| `--commit` | Force commit (overrides `commit: false` in config) |
|
|
50
50
|
| `--require-edit` | Refuse to commit if the saved message is byte-identical to what the AI produced |
|
|
51
51
|
| `--no-require-edit` | Allow committing the AI message unchanged |
|
|
52
52
|
| `--init` | Run the interactive setup wizard |
|
|
53
53
|
| `--config` | Print the active config path and contents |
|
|
54
|
+
| `-v`, `--version` | Print the package version |
|
|
54
55
|
| `-h` | Show usage. (`--help` is intercepted by git, so use `-h`.) |
|
|
55
56
|
|
|
56
57
|
## How it decides to commit
|
|
@@ -99,7 +100,7 @@ Each field in your user config **replaces** the default if present. If you delet
|
|
|
99
100
|
The wizard ships with presets for:
|
|
100
101
|
|
|
101
102
|
- **Claude Code** — `claude -p "{prompt}"` (reads diff from stdin)
|
|
102
|
-
- **
|
|
103
|
+
- **Antigravity CLI** — `agy -p "{prompt}"`
|
|
103
104
|
- **Codex CLI** — `codex exec --skip-git-repo-check "{prompt}"`
|
|
104
105
|
- **OpenCode** — `opencode run "{prompt}"`
|
|
105
106
|
- **llm** (Simon Willison) — `llm "{prompt}"`
|
|
@@ -108,6 +109,20 @@ The wizard ships with presets for:
|
|
|
108
109
|
|
|
109
110
|
You can edit `provider` in the config file at any time, or rerun `git aic --init`.
|
|
110
111
|
|
|
112
|
+
### Windows note
|
|
113
|
+
|
|
114
|
+
Most CLIs work the same on Windows. The exception is a CLI that renders its output to the console instead of stdout (like `agy`): to capture it, `git-aic` must spawn it with no console, which briefly flashes a console window. A provider can opt into this with a `windows` field carrying extra spawn options:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
"provider": {
|
|
118
|
+
"command": "agy",
|
|
119
|
+
"args": ["-p", "{prompt}"],
|
|
120
|
+
"windows": { "detached": true }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
CLIs that write to stdout (e.g. `claude`) don't need this and run without any window.
|
|
125
|
+
|
|
111
126
|
## Customizing the prompt
|
|
112
127
|
|
|
113
128
|
The base prompt template, the `<commit_message>` tag, and the cleanup patterns live in `bin/prompts.json`. Edit that file to:
|
package/bin/git-aic.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
4
4
|
import {
|
|
5
5
|
existsSync,
|
|
6
6
|
mkdtempSync,
|
|
@@ -16,6 +16,7 @@ import { runInitWizard } from "./wizard.js";
|
|
|
16
16
|
import { cleanCommitMessage } from "./clean.js";
|
|
17
17
|
import { extractCommitMessage } from "./extract.js";
|
|
18
18
|
import { runProvider } from "./run-provider.js";
|
|
19
|
+
import { spawnCross } from "./spawn.js";
|
|
19
20
|
import { applyUserOverrides, getConfiguration } from "./prompts.js";
|
|
20
21
|
import { getConfigPath, loadConfig } from "./get-config-path.js";
|
|
21
22
|
import { PROVIDER_PRESETS } from "./presets.js";
|
|
@@ -28,6 +29,9 @@ function fail(message, code = 1) {
|
|
|
28
29
|
function run(command, args, options = {}) {
|
|
29
30
|
return spawnSync(command, args, {
|
|
30
31
|
encoding: "utf8",
|
|
32
|
+
// Suppress the transient console window Windows flashes for piped child
|
|
33
|
+
// processes (no effect on other platforms).
|
|
34
|
+
windowsHide: true,
|
|
31
35
|
...options,
|
|
32
36
|
});
|
|
33
37
|
}
|
|
@@ -37,9 +41,11 @@ function parseArgs(argv) {
|
|
|
37
41
|
let shouldCommit = null;
|
|
38
42
|
let requireEdit = null;
|
|
39
43
|
let printConfig = false;
|
|
44
|
+
let printVersion = false;
|
|
40
45
|
let runInit = false;
|
|
41
46
|
let shouldAdd = false;
|
|
42
47
|
let providerName = null;
|
|
48
|
+
let verbose = false;
|
|
43
49
|
|
|
44
50
|
for (let i = 0; i < argv.length; i++) {
|
|
45
51
|
const arg = argv[i];
|
|
@@ -99,6 +105,16 @@ function parseArgs(argv) {
|
|
|
99
105
|
continue;
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
if (arg === "-v" || arg === "--version") {
|
|
109
|
+
printVersion = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (arg === "-V" || arg === "--verbose") {
|
|
114
|
+
verbose = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
if (arg === "--init") {
|
|
103
119
|
runInit = true;
|
|
104
120
|
continue;
|
|
@@ -111,10 +127,12 @@ Usage:
|
|
|
111
127
|
git aic -a # stage all changes first (git add -A)
|
|
112
128
|
git aic -p "extra prompt"
|
|
113
129
|
git aic -ap "extra prompt" # combine -a and -p
|
|
114
|
-
git aic --provider
|
|
130
|
+
git aic --provider agy # one-off provider override (also -P)
|
|
115
131
|
git aic --provider # list available providers
|
|
116
132
|
git aic --no-commit
|
|
117
133
|
git aic --config
|
|
134
|
+
git aic -v, --version
|
|
135
|
+
git aic -V, --verbose # print the prompt and raw provider output
|
|
118
136
|
git aic --init
|
|
119
137
|
|
|
120
138
|
Examples:
|
|
@@ -143,9 +161,11 @@ Environment:
|
|
|
143
161
|
shouldCommit,
|
|
144
162
|
requireEdit,
|
|
145
163
|
printConfig,
|
|
164
|
+
printVersion,
|
|
146
165
|
runInit,
|
|
147
166
|
shouldAdd,
|
|
148
167
|
providerName,
|
|
168
|
+
verbose,
|
|
149
169
|
};
|
|
150
170
|
}
|
|
151
171
|
|
|
@@ -159,8 +179,37 @@ function buildPrompt(extraPrompt) {
|
|
|
159
179
|
return cfg.prompt.join("\n").replaceAll(token, extraPrompt || extraPromptFallback);
|
|
160
180
|
}
|
|
161
181
|
|
|
182
|
+
function getVersion() {
|
|
183
|
+
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
184
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
185
|
+
return packageJson.version;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readStagedDiff() {
|
|
189
|
+
try {
|
|
190
|
+
return execFileSync(
|
|
191
|
+
"git",
|
|
192
|
+
["diff", "--cached", "--no-ext-diff"],
|
|
193
|
+
{
|
|
194
|
+
encoding: "utf8",
|
|
195
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
196
|
+
windowsHide: true,
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const detail = error?.message ? `\n${error.message}` : "";
|
|
201
|
+
throw new Error(`Failed to read staged diff.${detail}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
162
205
|
|
|
163
206
|
const args = parseArgs(process.argv.slice(2));
|
|
207
|
+
|
|
208
|
+
if (args.printVersion) {
|
|
209
|
+
console.log(getVersion());
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
|
|
164
213
|
const configPath = getConfigPath();
|
|
165
214
|
|
|
166
215
|
if (args.runInit) {
|
|
@@ -192,7 +241,11 @@ if (args.providerName) {
|
|
|
192
241
|
const names = PROVIDER_PRESETS.map((p) => p.command).join(", ");
|
|
193
242
|
fail(`Unknown provider: ${args.providerName}. Available: ${names}`);
|
|
194
243
|
}
|
|
195
|
-
config.provider = {
|
|
244
|
+
config.provider = {
|
|
245
|
+
command: preset.command,
|
|
246
|
+
args: preset.args,
|
|
247
|
+
...(preset.windows ? { windows: preset.windows } : {}),
|
|
248
|
+
};
|
|
196
249
|
}
|
|
197
250
|
|
|
198
251
|
if (args.printConfig) {
|
|
@@ -223,23 +276,36 @@ if (hasStagedChanges.status === 0) {
|
|
|
223
276
|
fail("No staged changes found. Use: git add <files>");
|
|
224
277
|
}
|
|
225
278
|
|
|
226
|
-
const diff =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
279
|
+
const diff = readStagedDiff();
|
|
280
|
+
const MAX_DIFF_CHARS = 120_000;
|
|
281
|
+
const safeDiff =
|
|
282
|
+
diff.length > MAX_DIFF_CHARS
|
|
283
|
+
? `${diff.slice(0, MAX_DIFF_CHARS)}\n\n[Diff truncated because it was too large.]`
|
|
284
|
+
: diff;
|
|
231
285
|
|
|
232
286
|
const prompt = buildPrompt(args.extraPrompt);
|
|
233
287
|
|
|
234
288
|
console.log(`Using provider: ${config.provider.command}`);
|
|
235
289
|
|
|
290
|
+
if (args.verbose) {
|
|
291
|
+
console.error("[verbose] ----- prompt -----");
|
|
292
|
+
console.error(prompt);
|
|
293
|
+
console.error("[verbose] ----- end prompt -----");
|
|
294
|
+
}
|
|
295
|
+
|
|
236
296
|
let raw;
|
|
237
297
|
try {
|
|
238
|
-
raw = runProvider(config.provider, prompt,
|
|
298
|
+
raw = runProvider(config.provider, prompt, safeDiff, { verbose: args.verbose });
|
|
239
299
|
} catch (error) {
|
|
240
300
|
fail(error.message);
|
|
241
301
|
}
|
|
242
302
|
|
|
303
|
+
if (args.verbose) {
|
|
304
|
+
console.error("[verbose] ----- raw provider output -----");
|
|
305
|
+
console.error(raw);
|
|
306
|
+
console.error("[verbose] ----- end raw provider output -----");
|
|
307
|
+
}
|
|
308
|
+
|
|
243
309
|
const message = extractCommitMessage(raw);
|
|
244
310
|
|
|
245
311
|
if (!message) {
|
|
@@ -254,8 +320,12 @@ writeFileSync(file, `${message.trim()}\n`);
|
|
|
254
320
|
const before = sha256(readFileSync(file, "utf8"));
|
|
255
321
|
const mtimeBefore = statSync(file).mtimeMs;
|
|
256
322
|
|
|
257
|
-
const
|
|
258
|
-
const
|
|
323
|
+
const defaultEditor = process.platform === "win32" ? "notepad" : "nano";
|
|
324
|
+
const editor = process.env.EDITOR || config.editor || defaultEditor;
|
|
325
|
+
// Editor configs can carry flags (e.g. "code --wait"), so split the command
|
|
326
|
+
// from its arguments before spawning.
|
|
327
|
+
const [editorCommand, ...editorArgs] = editor.trim().split(/\s+/);
|
|
328
|
+
const editorResult = spawnCross(editorCommand, [...editorArgs, file], {
|
|
259
329
|
stdio: "inherit",
|
|
260
330
|
});
|
|
261
331
|
|
package/bin/presets.js
CHANGED
|
@@ -5,9 +5,13 @@ export const PROVIDER_PRESETS = [
|
|
|
5
5
|
args: ["-p", "{prompt}"]
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
|
-
name: "
|
|
9
|
-
command: "
|
|
10
|
-
args: ["-p", "{prompt}"]
|
|
8
|
+
name: "Antigravity CLI",
|
|
9
|
+
command: "agy",
|
|
10
|
+
args: ["-p", "{prompt}"],
|
|
11
|
+
// agy renders to the Windows console device instead of stdout, so it can
|
|
12
|
+
// only be captured when spawned with no console (detached). This pops a
|
|
13
|
+
// brief console window on Windows; CLIs that write to stdout don't need it.
|
|
14
|
+
windows: { detached: true }
|
|
11
15
|
},
|
|
12
16
|
{
|
|
13
17
|
name: "Codex CLI",
|
|
@@ -31,4 +35,4 @@ export const PROVIDER_PRESETS = [
|
|
|
31
35
|
}
|
|
32
36
|
];
|
|
33
37
|
|
|
34
|
-
export const EDITOR_PRESETS = ["nano", "vim", "nvim", "code --wait", "micro", "hx"];
|
|
38
|
+
export const EDITOR_PRESETS = ["nano", "vim", "nvim", "code --wait", "micro", "hx", "notepad"];
|
package/bin/run-provider.js
CHANGED
|
@@ -1,30 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnCross, isWindowsShim } from "./spawn.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Keep verbose output readable: show short args verbatim, summarise long ones
|
|
4
|
+
// (like the full prompt) by byte size instead of dumping them.
|
|
5
|
+
function previewArg(arg) {
|
|
6
|
+
return arg.length > 60 ? `<${Buffer.byteLength(arg)} bytes>` : arg;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function runProvider(provider, prompt, diff, { verbose = false } = {}) {
|
|
4
10
|
if (!provider?.command) {
|
|
5
11
|
throw new Error("Config error: provider.command is required.");
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
const args = provider.args ?? [];
|
|
9
|
-
|
|
10
|
-
const finalArgs = args.map((arg) =>
|
|
11
|
-
arg
|
|
12
|
-
.replaceAll("{prompt}", prompt)
|
|
13
|
-
.replaceAll("{diff}", diff)
|
|
14
|
-
);
|
|
15
|
-
|
|
16
15
|
const usesPromptPlaceholder = args.some((arg) => arg.includes("{prompt}"));
|
|
17
16
|
const usesDiffPlaceholder = args.some((arg) => arg.includes("{diff}"));
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
let finalArgs;
|
|
19
|
+
let input;
|
|
20
|
+
|
|
21
|
+
if (isWindowsShim(provider.command) && !usesDiffPlaceholder) {
|
|
22
|
+
// Batch shims (e.g. claude.cmd) run through cmd.exe, which truncates a
|
|
23
|
+
// command-line argument at the first newline — so the multi-line prompt
|
|
24
|
+
// can't be passed inline. Send the prompt and diff via stdin and drop the
|
|
25
|
+
// inline {prompt}; CLIs like claude read the prompt from stdin when no
|
|
26
|
+
// inline prompt is given.
|
|
27
|
+
finalArgs = args.filter((arg) => arg.trim() !== "{prompt}");
|
|
28
|
+
input = `${prompt}\n\n${diff}`;
|
|
29
|
+
} else {
|
|
30
|
+
finalArgs = args.map((arg) =>
|
|
31
|
+
arg.replaceAll("{prompt}", prompt).replaceAll("{diff}", diff)
|
|
32
|
+
);
|
|
33
|
+
input = usesDiffPlaceholder ? undefined : diff;
|
|
34
|
+
if (!usesPromptPlaceholder) {
|
|
35
|
+
finalArgs.push(prompt);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
20
38
|
|
|
21
|
-
if (
|
|
22
|
-
finalArgs.
|
|
39
|
+
if (verbose) {
|
|
40
|
+
const preview = finalArgs.map(previewArg).join(" ");
|
|
41
|
+
console.error(`[verbose] command: ${provider.command} ${preview}`);
|
|
42
|
+
console.error(
|
|
43
|
+
`[verbose] stdin: ${input ? `${Buffer.byteLength(input)} bytes` : "(none)"}`
|
|
44
|
+
);
|
|
23
45
|
}
|
|
24
46
|
|
|
25
|
-
|
|
47
|
+
// Per-provider Windows spawn options (e.g. { detached: true } for agy).
|
|
48
|
+
// Applied only on Windows; ignored elsewhere.
|
|
49
|
+
const windowsOptions =
|
|
50
|
+
process.platform === "win32" ? provider.windows ?? {} : {};
|
|
51
|
+
|
|
52
|
+
const result = spawnCross(provider.command, finalArgs, {
|
|
26
53
|
input,
|
|
27
54
|
encoding: "utf8",
|
|
55
|
+
...windowsOptions,
|
|
28
56
|
});
|
|
29
57
|
|
|
30
58
|
if (result.status !== 0) {
|
package/bin/spawn.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { delimiter, extname, isAbsolute, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const isWindows = process.platform === "win32";
|
|
6
|
+
|
|
7
|
+
// On Windows, a CLI on PATH may be a native executable (`agy.exe`) or a batch
|
|
8
|
+
// shim that npm generates for global installs (`claude.cmd`). Native exes can
|
|
9
|
+
// be spawned directly and accept multi-line arguments; batch shims must be run
|
|
10
|
+
// through cmd.exe, which mangles multi-line arguments. We need to tell them
|
|
11
|
+
// apart, so resolve the command to a real file and inspect its extension.
|
|
12
|
+
function resolveWindowsCommand(command) {
|
|
13
|
+
if (isAbsolute(command) || command.includes("\\") || command.includes("/")) {
|
|
14
|
+
return { file: command, ext: extname(command).toLowerCase() };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pathext = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
18
|
+
.split(";")
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
const hasExt = extname(command) !== "";
|
|
21
|
+
const candidates = hasExt ? [command] : pathext.map((ext) => command + ext);
|
|
22
|
+
const dirs = (process.env.PATH || "").split(delimiter).filter(Boolean);
|
|
23
|
+
|
|
24
|
+
for (const dir of dirs) {
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
const full = join(dir, candidate);
|
|
27
|
+
if (existsSync(full)) {
|
|
28
|
+
return { file: full, ext: extname(full).toLowerCase() };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Not found on PATH — hand it back unchanged and let spawn surface the error.
|
|
34
|
+
return { file: command, ext: extname(command).toLowerCase() };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Whether a command resolves to a batch/PowerShell shim on Windows (e.g. the
|
|
38
|
+
// `claude.cmd` npm installs). Such shims run through cmd.exe, which truncates
|
|
39
|
+
// arguments at the first newline — so callers must avoid passing multi-line
|
|
40
|
+
// arguments to them. Always false off Windows.
|
|
41
|
+
export function isWindowsShim(command) {
|
|
42
|
+
if (!isWindows) return false;
|
|
43
|
+
const { ext } = resolveWindowsCommand(command);
|
|
44
|
+
return ext === ".cmd" || ext === ".bat" || ext === ".ps1";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Quoting for the cmd.exe shell path. With `shell: true` Node concatenates argv
|
|
48
|
+
// verbatim instead of quoting it, so we wrap anything containing whitespace or
|
|
49
|
+
// shell metacharacters ourselves.
|
|
50
|
+
function quoteWinArg(arg) {
|
|
51
|
+
if (arg === "") return '""';
|
|
52
|
+
if (!/[\s"&|<>^()]/.test(arg)) return arg;
|
|
53
|
+
const escaped = arg
|
|
54
|
+
.replace(/(\\*)"/g, '$1$1\\"') // double backslashes before a quote, then escape the quote
|
|
55
|
+
.replace(/(\\*)$/, "$1$1"); // double trailing backslashes so they don't escape the closing quote
|
|
56
|
+
return `"${escaped}"`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Cross-platform spawnSync. Non-Windows spawns the command directly. On Windows
|
|
60
|
+
// native executables are also spawned directly (preserving multi-line args),
|
|
61
|
+
// while batch/PowerShell shims are routed through the shell so PATHEXT resolves
|
|
62
|
+
// them.
|
|
63
|
+
export function spawnCross(command, args = [], options = {}) {
|
|
64
|
+
if (!isWindows) {
|
|
65
|
+
return spawnSync(command, args, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { file, ext } = resolveWindowsCommand(command);
|
|
69
|
+
const needsShell = ext === ".cmd" || ext === ".bat" || ext === ".ps1";
|
|
70
|
+
// Interactive spawns (the editor) inherit stdio and need a real, visible
|
|
71
|
+
// console; only capturing spawns get the window-suppression flags below.
|
|
72
|
+
// detached and windowsHide map to mutually exclusive console-creation flags
|
|
73
|
+
// (DETACHED_PROCESS vs CREATE_NO_WINDOW), so each path picks exactly one.
|
|
74
|
+
const capturing = options.stdio !== "inherit";
|
|
75
|
+
|
|
76
|
+
if (!needsShell) {
|
|
77
|
+
// Native exe. A caller can pass `detached: true` for a CLI that renders to
|
|
78
|
+
// the console device instead of stdout (e.g. agy): detached gives it no
|
|
79
|
+
// console so it falls back to stdout — at the cost of a brief console
|
|
80
|
+
// window. Everything else just gets a hidden console (no window).
|
|
81
|
+
const wantsDetached = capturing && options.detached === true;
|
|
82
|
+
const extra = capturing && !wantsDetached ? { windowsHide: true } : {};
|
|
83
|
+
return spawnSync(file, args, { ...extra, ...options });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Batch/PowerShell shim — route through cmd.exe so PATHEXT resolves it.
|
|
87
|
+
// windowsHide (CREATE_NO_WINDOW) gives cmd.exe a hidden console: no popup,
|
|
88
|
+
// and shims like claude write to stdout normally so capture still works.
|
|
89
|
+
// detached would pop a cmd.exe window and shims never need it, so drop it.
|
|
90
|
+
const { detached, ...shimOptions } = options;
|
|
91
|
+
const extra = capturing ? { windowsHide: true } : {};
|
|
92
|
+
return spawnSync(file, args.map(quoteWinArg), {
|
|
93
|
+
...extra,
|
|
94
|
+
...shimOptions,
|
|
95
|
+
shell: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
package/bin/wizard.js
CHANGED
|
@@ -75,7 +75,11 @@ async function pickProvider(rl) {
|
|
|
75
75
|
|
|
76
76
|
if (index >= 0 && index < PROVIDER_PRESETS.length) {
|
|
77
77
|
const preset = PROVIDER_PRESETS[index];
|
|
78
|
-
return {
|
|
78
|
+
return {
|
|
79
|
+
command: preset.command,
|
|
80
|
+
args: preset.args,
|
|
81
|
+
...(preset.windows ? { windows: preset.windows } : {}),
|
|
82
|
+
};
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
if (index === PROVIDER_PRESETS.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thankarezos/git-ai-commit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Generate editable git commit messages using a configurable AI CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"commit",
|
|
33
33
|
"cli",
|
|
34
34
|
"claude",
|
|
35
|
-
"
|
|
35
|
+
"antigravity",
|
|
36
36
|
"conventional-commits"
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT"
|