@jakejarvis/acai 0.2.1 → 0.3.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 +12 -8
- package/dist/cli.mjs +159 -93
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# acai
|
|
2
2
|
|
|
3
|
-
AI-generated commit messages that match your repo's existing style. Powered by [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Codex](https://github.com/openai/codex).
|
|
3
|
+
AI-generated commit messages that match your repo's existing style. Powered by [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Codex](https://github.com/openai/codex), with real-time streaming as the message is generated.
|
|
4
4
|
|
|
5
5
|
`acai` reads your repo's recent commit history and adapts to whatever conventions your team already uses — conventional commits, gitmoji, ticket prefixes, pig latin, etc.
|
|
6
6
|
|
|
@@ -29,8 +29,11 @@ AI-generated commit messages that match your repo's existing style. Powered by [
|
|
|
29
29
|
| Flag | Env var | Default | Description |
|
|
30
30
|
|------|---------|---------|-------------|
|
|
31
31
|
| `-p, --provider` | `ACAI_PROVIDER` | `claude` | AI provider (`claude`, `codex`) |
|
|
32
|
-
|
|
|
32
|
+
| `--claude, --codex` | `ACAI_PROVIDER` | `claude` | Shorthand for `--provider <name>` |
|
|
33
|
+
| `-m, --model` | `ACAI_MODEL` | `sonnet` or `gpt-5.4-mini` | Model override |
|
|
33
34
|
| `-y, --yolo` | — | `false` | Stage all changes and commit without confirmation |
|
|
35
|
+
| `-V, --verbose` | — | `false` | Print prompts sent to the provider and raw responses |
|
|
36
|
+
| `-v, --version` | — | — | Show version number |
|
|
34
37
|
|
|
35
38
|
## Usage
|
|
36
39
|
|
|
@@ -45,11 +48,12 @@ npm install -g @jakejarvis/acai
|
|
|
45
48
|
acai
|
|
46
49
|
|
|
47
50
|
# Use a different provider
|
|
51
|
+
acai --codex
|
|
48
52
|
acai -p codex
|
|
49
53
|
|
|
50
54
|
# Override the model
|
|
51
55
|
acai -m haiku
|
|
52
|
-
acai
|
|
56
|
+
acai --codex -m gpt-5.4-mini
|
|
53
57
|
|
|
54
58
|
# Stage everything, generate, and commit — no prompts
|
|
55
59
|
acai --yolo
|
|
@@ -62,14 +66,14 @@ acai --yolo
|
|
|
62
66
|
│
|
|
63
67
|
◇ 3 files staged
|
|
64
68
|
│
|
|
65
|
-
|
|
69
|
+
◇ Here's what Claude (sonnet) came up with:
|
|
66
70
|
│
|
|
67
71
|
│ feat(auth): add session expiry validation
|
|
68
72
|
│
|
|
69
|
-
◆ What
|
|
73
|
+
◆ What's next?
|
|
70
74
|
│ ✓ Commit — accept and commit
|
|
71
75
|
│ ✎ Edit — open in $EDITOR before committing
|
|
72
|
-
│ ↻ Revise — give
|
|
76
|
+
│ ↻ Revise — give feedback and regenerate
|
|
73
77
|
│ ⎘ Copy — copy to clipboard, don't commit
|
|
74
78
|
│ ✕ Cancel
|
|
75
79
|
│
|
|
@@ -78,13 +82,13 @@ acai --yolo
|
|
|
78
82
|
|
|
79
83
|
### Revision loop
|
|
80
84
|
|
|
81
|
-
Choose **Revise** and tell
|
|
85
|
+
Choose **Revise** and tell the provider what to change:
|
|
82
86
|
|
|
83
87
|
```
|
|
84
88
|
◆ What should Claude change?
|
|
85
89
|
│ make it shorter, drop the scope
|
|
86
90
|
│
|
|
87
|
-
|
|
91
|
+
◇ Here's what Claude (sonnet) came up with:
|
|
88
92
|
│
|
|
89
93
|
│ feat: add session expiry validation
|
|
90
94
|
```
|
package/dist/cli.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import c from "readline";
|
|
|
7
7
|
import { ReadStream } from "tty";
|
|
8
8
|
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
9
9
|
import { delimiter, dirname, join, normalize, resolve } from "path";
|
|
10
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
11
|
+
import { Codex } from "@openai/codex-sdk";
|
|
10
12
|
import { spawn } from "child_process";
|
|
11
13
|
import { PassThrough } from "stream";
|
|
12
14
|
import { tmpdir } from "os";
|
|
@@ -1912,61 +1914,86 @@ const providers = {
|
|
|
1912
1914
|
bin: "claude",
|
|
1913
1915
|
versionArgs: ["--version"],
|
|
1914
1916
|
defaultModel: "sonnet",
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
model,
|
|
1923
|
-
"--tools",
|
|
1924
|
-
"",
|
|
1925
|
-
"--strict-mcp-config",
|
|
1926
|
-
"--no-session-persistence",
|
|
1927
|
-
"--system-prompt",
|
|
1928
|
-
systemPrompt
|
|
1929
|
-
];
|
|
1930
|
-
},
|
|
1931
|
-
parseOutput(stdout) {
|
|
1932
|
-
let parsed;
|
|
1917
|
+
async *generate(opts) {
|
|
1918
|
+
const systemPrompt = buildSystemPrompt(opts.commitLog, opts.instructions);
|
|
1919
|
+
const userPrompt = buildUserPrompt(opts.diff, opts.stat, opts.files);
|
|
1920
|
+
opts.log?.(`System prompt:\n${systemPrompt}`);
|
|
1921
|
+
opts.log?.(`User prompt:\n${userPrompt}`);
|
|
1922
|
+
const abortController = new AbortController();
|
|
1923
|
+
const timeout = setTimeout(() => abortController.abort(), 12e4);
|
|
1933
1924
|
try {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1925
|
+
let fullText = "";
|
|
1926
|
+
for await (const message of query({
|
|
1927
|
+
prompt: userPrompt,
|
|
1928
|
+
options: {
|
|
1929
|
+
systemPrompt,
|
|
1930
|
+
model: opts.model,
|
|
1931
|
+
tools: [],
|
|
1932
|
+
permissionMode: "bypassPermissions",
|
|
1933
|
+
allowDangerouslySkipPermissions: true,
|
|
1934
|
+
persistSession: false,
|
|
1935
|
+
includePartialMessages: true,
|
|
1936
|
+
maxTurns: 1,
|
|
1937
|
+
abortController
|
|
1938
|
+
}
|
|
1939
|
+
})) {
|
|
1940
|
+
if (message.type === "stream_event") {
|
|
1941
|
+
const event = message.event;
|
|
1942
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
1943
|
+
fullText += event.delta.text;
|
|
1944
|
+
yield event.delta.text;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (message.type === "assistant" && !message.error) {
|
|
1948
|
+
const content = message.message?.content;
|
|
1949
|
+
if (Array.isArray(content)) {
|
|
1950
|
+
for (const block of content) if ("text" in block && block.text && !fullText) {
|
|
1951
|
+
fullText = block.text;
|
|
1952
|
+
yield block.text;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
if (message.type === "result" && message.is_error) throw new Error(`Claude error: ${message.subtype === "success" ? message.result : message.errors?.join(", ")}`);
|
|
1957
|
+
if (message.type === "assistant" && message.error) throw new Error(`Claude ${message.error} error`);
|
|
1958
|
+
}
|
|
1959
|
+
if (!fullText.trim()) throw new Error("Claude returned an empty commit message.");
|
|
1960
|
+
} finally {
|
|
1961
|
+
clearTimeout(timeout);
|
|
1937
1962
|
}
|
|
1938
|
-
if (parsed.is_error) throw new Error(`Claude error: ${parsed.result}`);
|
|
1939
|
-
const result = (parsed.result || "").trim();
|
|
1940
|
-
if (!result) throw new Error("Claude returned an empty commit message.");
|
|
1941
|
-
return result;
|
|
1942
1963
|
}
|
|
1943
1964
|
},
|
|
1944
1965
|
codex: {
|
|
1945
1966
|
name: "Codex",
|
|
1946
1967
|
bin: "codex",
|
|
1947
1968
|
versionArgs: ["--version"],
|
|
1948
|
-
defaultModel: "gpt-5.
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
"
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1969
|
+
defaultModel: "gpt-5.4-mini",
|
|
1970
|
+
async *generate(opts) {
|
|
1971
|
+
const systemPrompt = buildSystemPrompt(opts.commitLog, opts.instructions);
|
|
1972
|
+
const userPrompt = buildUserPrompt(opts.diff, opts.stat, opts.files);
|
|
1973
|
+
opts.log?.(`System prompt:\n${systemPrompt}`);
|
|
1974
|
+
opts.log?.(`User prompt:\n${userPrompt}`);
|
|
1975
|
+
const { events } = await new Codex({ config: {
|
|
1976
|
+
developer_instructions: systemPrompt,
|
|
1977
|
+
model_reasoning_effort: "medium",
|
|
1978
|
+
check_for_update_on_startup: false
|
|
1979
|
+
} }).startThread({
|
|
1980
|
+
model: opts.model,
|
|
1981
|
+
sandboxMode: "read-only",
|
|
1982
|
+
skipGitRepoCheck: true
|
|
1983
|
+
}).runStreamed(userPrompt);
|
|
1984
|
+
let lastText = "";
|
|
1985
|
+
for await (const event of events) {
|
|
1986
|
+
opts.log?.(`Event: ${JSON.stringify(event).slice(0, 300)}`);
|
|
1987
|
+
if (event.type === "turn.failed") throw new Error(`Codex error: ${event.error.message}`);
|
|
1988
|
+
if ((event.type === "item.updated" || event.type === "item.completed") && event.item.type === "agent_message") {
|
|
1989
|
+
const newText = event.item.text;
|
|
1990
|
+
if (newText.length > lastText.length) {
|
|
1991
|
+
yield newText.slice(lastText.length);
|
|
1992
|
+
lastText = newText;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (!lastText.trim()) throw new Error("Codex returned an empty commit message.");
|
|
1970
1997
|
}
|
|
1971
1998
|
}
|
|
1972
1999
|
};
|
|
@@ -1981,32 +2008,6 @@ async function ensureProvider(provider) {
|
|
|
1981
2008
|
] } });
|
|
1982
2009
|
if (exitCode !== 0) throw new Error(`${provider.name} CLI ("${provider.bin}") not found. Make sure it is installed and on your PATH.`);
|
|
1983
2010
|
}
|
|
1984
|
-
/**
|
|
1985
|
-
* Generate a commit message using the given provider's CLI.
|
|
1986
|
-
*/
|
|
1987
|
-
async function generateCommitMessage(provider, opts) {
|
|
1988
|
-
const { diff, stat, files, commitLog, model, instructions, log } = opts;
|
|
1989
|
-
const systemPrompt = buildSystemPrompt(commitLog, instructions);
|
|
1990
|
-
const userPrompt = buildUserPrompt(diff, stat, files);
|
|
1991
|
-
log?.(`System prompt:\n${systemPrompt}`);
|
|
1992
|
-
log?.(`User prompt:\n${userPrompt}`);
|
|
1993
|
-
const args = provider.buildArgs({
|
|
1994
|
-
userPrompt,
|
|
1995
|
-
systemPrompt,
|
|
1996
|
-
model
|
|
1997
|
-
});
|
|
1998
|
-
const { stdout, stderr, exitCode } = await z(provider.bin, args, {
|
|
1999
|
-
timeout: 12e4,
|
|
2000
|
-
nodeOptions: { stdio: [
|
|
2001
|
-
"ignore",
|
|
2002
|
-
"pipe",
|
|
2003
|
-
"pipe"
|
|
2004
|
-
] }
|
|
2005
|
-
});
|
|
2006
|
-
log?.(`Raw response:\n${stdout}${stderr ? `\nStderr:\n${stderr}` : ""}`);
|
|
2007
|
-
if (exitCode !== 0) throw new Error(`${provider.name} exited with code ${exitCode}\n${stderr || stdout}`);
|
|
2008
|
-
return provider.parseOutput(stdout);
|
|
2009
|
-
}
|
|
2010
2011
|
//#endregion
|
|
2011
2012
|
//#region src/args.ts
|
|
2012
2013
|
const DEFAULT_PROVIDER = "claude";
|
|
@@ -2024,13 +2025,19 @@ const options = {
|
|
|
2024
2025
|
short: "y"
|
|
2025
2026
|
},
|
|
2026
2027
|
verbose: {
|
|
2028
|
+
type: "boolean",
|
|
2029
|
+
short: "V"
|
|
2030
|
+
},
|
|
2031
|
+
version: {
|
|
2027
2032
|
type: "boolean",
|
|
2028
2033
|
short: "v"
|
|
2029
2034
|
},
|
|
2030
2035
|
help: {
|
|
2031
2036
|
type: "boolean",
|
|
2032
2037
|
short: "h"
|
|
2033
|
-
}
|
|
2038
|
+
},
|
|
2039
|
+
claude: { type: "boolean" },
|
|
2040
|
+
codex: { type: "boolean" }
|
|
2034
2041
|
};
|
|
2035
2042
|
function parseConfig() {
|
|
2036
2043
|
const { values } = parseArgs({
|
|
@@ -2038,11 +2045,13 @@ function parseConfig() {
|
|
|
2038
2045
|
options,
|
|
2039
2046
|
strict: false
|
|
2040
2047
|
});
|
|
2048
|
+
const providerAlias = values.codex ? "codex" : values.claude ? "claude" : void 0;
|
|
2041
2049
|
return {
|
|
2042
|
-
provider: values.provider ?? process.env.ACAI_PROVIDER ?? DEFAULT_PROVIDER,
|
|
2050
|
+
provider: values.provider ?? providerAlias ?? process.env.ACAI_PROVIDER ?? DEFAULT_PROVIDER,
|
|
2043
2051
|
model: values.model ?? process.env.ACAI_MODEL ?? "",
|
|
2044
2052
|
yolo: values.yolo ?? false,
|
|
2045
2053
|
verbose: values.verbose ?? false,
|
|
2054
|
+
version: values.version ?? false,
|
|
2046
2055
|
help: values.help ?? false
|
|
2047
2056
|
};
|
|
2048
2057
|
}
|
|
@@ -2053,11 +2062,13 @@ Usage: acai [options]
|
|
|
2053
2062
|
|
|
2054
2063
|
Options:
|
|
2055
2064
|
-p, --provider <name> AI provider to use (${providerNames}) (default: ${DEFAULT_PROVIDER})
|
|
2065
|
+
--claude, --codex Shorthand for --provider <name>
|
|
2056
2066
|
Can also set ACAI_PROVIDER env var
|
|
2057
2067
|
-m, --model <model> Model to use (default: provider-specific)
|
|
2058
2068
|
Can also set ACAI_MODEL env var
|
|
2059
2069
|
-y, --yolo Stage all changes and commit without confirmation
|
|
2060
|
-
-
|
|
2070
|
+
-V, --verbose Print prompts sent to the provider and raw responses
|
|
2071
|
+
-v, --version Show version number
|
|
2061
2072
|
-h, --help Show this help message
|
|
2062
2073
|
|
|
2063
2074
|
Examples:
|
|
@@ -2070,19 +2081,51 @@ Examples:
|
|
|
2070
2081
|
}
|
|
2071
2082
|
//#endregion
|
|
2072
2083
|
//#region src/git.ts
|
|
2084
|
+
/**
|
|
2085
|
+
* Files excluded from diffs sent to the AI provider.
|
|
2086
|
+
* These are still listed as changed files — just their content is omitted
|
|
2087
|
+
* to avoid wasting tokens on generated/binary/noisy content.
|
|
2088
|
+
*/
|
|
2089
|
+
const DIFF_EXCLUDE_PATTERNS = [
|
|
2090
|
+
"package-lock.json",
|
|
2091
|
+
"yarn.lock",
|
|
2092
|
+
"pnpm-lock.yaml",
|
|
2093
|
+
"bun.lock",
|
|
2094
|
+
"bun.lockb",
|
|
2095
|
+
"deno.lock",
|
|
2096
|
+
"Cargo.lock",
|
|
2097
|
+
"Gemfile.lock",
|
|
2098
|
+
"composer.lock",
|
|
2099
|
+
"poetry.lock",
|
|
2100
|
+
"uv.lock",
|
|
2101
|
+
"go.sum",
|
|
2102
|
+
"flake.lock",
|
|
2103
|
+
"*.pbxproj",
|
|
2104
|
+
"*.xcworkspacedata",
|
|
2105
|
+
"*.map"
|
|
2106
|
+
];
|
|
2073
2107
|
async function ensureGitRepo() {
|
|
2074
2108
|
const { stdout, exitCode } = await z("git", ["rev-parse", "--is-inside-work-tree"]);
|
|
2075
2109
|
if (exitCode !== 0 || stdout.trim() !== "true") throw new Error("Not inside a git repository.");
|
|
2076
2110
|
}
|
|
2077
2111
|
async function getStagedDiff() {
|
|
2078
|
-
const { stdout, exitCode } = await z("git", [
|
|
2112
|
+
const { stdout, exitCode } = await z("git", [
|
|
2113
|
+
"diff",
|
|
2114
|
+
"--cached",
|
|
2115
|
+
"--",
|
|
2116
|
+
".",
|
|
2117
|
+
...DIFF_EXCLUDE_PATTERNS.map((p) => `:(exclude)${p}`)
|
|
2118
|
+
]);
|
|
2079
2119
|
return exitCode === 0 ? stdout.trim() : null;
|
|
2080
2120
|
}
|
|
2081
2121
|
async function getStagedStat() {
|
|
2082
2122
|
const { stdout, exitCode } = await z("git", [
|
|
2083
2123
|
"diff",
|
|
2084
2124
|
"--cached",
|
|
2085
|
-
"--stat"
|
|
2125
|
+
"--stat",
|
|
2126
|
+
"--",
|
|
2127
|
+
".",
|
|
2128
|
+
...DIFF_EXCLUDE_PATTERNS.map((p) => `:(exclude)${p}`)
|
|
2086
2129
|
]);
|
|
2087
2130
|
return exitCode === 0 ? stdout.trim() : null;
|
|
2088
2131
|
}
|
|
@@ -2177,6 +2220,12 @@ async function commit(message) {
|
|
|
2177
2220
|
//#region bin/cli.ts
|
|
2178
2221
|
async function main() {
|
|
2179
2222
|
const config = parseConfig();
|
|
2223
|
+
if (config.version) {
|
|
2224
|
+
const { createRequire } = await import("module");
|
|
2225
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
2226
|
+
console.log(pkg.version);
|
|
2227
|
+
process.exit(0);
|
|
2228
|
+
}
|
|
2180
2229
|
if (config.help) {
|
|
2181
2230
|
printUsage();
|
|
2182
2231
|
process.exit(0);
|
|
@@ -2189,7 +2238,6 @@ async function main() {
|
|
|
2189
2238
|
}
|
|
2190
2239
|
const model = config.model || provider.defaultModel;
|
|
2191
2240
|
Wt("acai");
|
|
2192
|
-
const s = be();
|
|
2193
2241
|
try {
|
|
2194
2242
|
await ensureGitRepo();
|
|
2195
2243
|
} catch {
|
|
@@ -2257,10 +2305,15 @@ async function main() {
|
|
|
2257
2305
|
const commitLog = await getRecentCommitLog(10) || "";
|
|
2258
2306
|
let instructions;
|
|
2259
2307
|
while (true) {
|
|
2260
|
-
s.start(`Waiting for ${provider.name}`);
|
|
2261
2308
|
let message;
|
|
2309
|
+
let firstToken = true;
|
|
2310
|
+
const BAR = import_picocolors.default.gray("│");
|
|
2311
|
+
const s = be();
|
|
2312
|
+
s.start(`Waiting for ${provider.name}`);
|
|
2262
2313
|
try {
|
|
2263
|
-
|
|
2314
|
+
let fullText = "";
|
|
2315
|
+
let inBody = false;
|
|
2316
|
+
const generator = provider.generate({
|
|
2264
2317
|
diff,
|
|
2265
2318
|
stat,
|
|
2266
2319
|
files,
|
|
@@ -2269,19 +2322,40 @@ async function main() {
|
|
|
2269
2322
|
instructions,
|
|
2270
2323
|
log: config.verbose ? (msg) => R$1.message(import_picocolors.default.dim(msg)) : void 0
|
|
2271
2324
|
});
|
|
2325
|
+
for await (const token of generator) {
|
|
2326
|
+
if (firstToken) {
|
|
2327
|
+
s.stop(`Here's what ${provider.name} ${import_picocolors.default.dim(`(${model})`)} came up with:\n`);
|
|
2328
|
+
process.stderr.write(`${BAR} `);
|
|
2329
|
+
firstToken = false;
|
|
2330
|
+
}
|
|
2331
|
+
fullText += token;
|
|
2332
|
+
const parts = token.split("\n");
|
|
2333
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2334
|
+
if (i > 0) {
|
|
2335
|
+
inBody = true;
|
|
2336
|
+
process.stderr.write(`\n${BAR} `);
|
|
2337
|
+
}
|
|
2338
|
+
if (parts[i]) process.stderr.write(inBody ? import_picocolors.default.dim(parts[i]) : import_picocolors.default.bold(import_picocolors.default.green(parts[i])));
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
process.stderr.write(`\n${BAR}\n`);
|
|
2342
|
+
if (firstToken) s.stop("No response");
|
|
2343
|
+
message = fullText.trim();
|
|
2272
2344
|
} catch (e) {
|
|
2273
|
-
s.stop("Failed");
|
|
2345
|
+
if (firstToken) s.stop("Failed");
|
|
2274
2346
|
Nt(`Generation failed: ${e.message}`);
|
|
2275
2347
|
process.exit(1);
|
|
2276
2348
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2349
|
+
if (!message) {
|
|
2350
|
+
Nt("Provider returned an empty message.");
|
|
2351
|
+
process.exit(1);
|
|
2352
|
+
}
|
|
2279
2353
|
if (config.yolo) {
|
|
2280
2354
|
await doCommit(message);
|
|
2281
2355
|
break;
|
|
2282
2356
|
}
|
|
2283
2357
|
const action = await Jt({
|
|
2284
|
-
message: "What
|
|
2358
|
+
message: "What's next?",
|
|
2285
2359
|
options: [
|
|
2286
2360
|
{
|
|
2287
2361
|
value: "commit",
|
|
@@ -2379,14 +2453,6 @@ async function doCommit(message) {
|
|
|
2379
2453
|
process.exit(1);
|
|
2380
2454
|
}
|
|
2381
2455
|
}
|
|
2382
|
-
function formatMessageForDisplay(message) {
|
|
2383
|
-
const lines = message.split("\n");
|
|
2384
|
-
const subject = lines[0];
|
|
2385
|
-
const body = lines.slice(1).join("\n").trim();
|
|
2386
|
-
let display = import_picocolors.default.bold(import_picocolors.default.green(subject));
|
|
2387
|
-
if (body) display += `\n${import_picocolors.default.dim(body)}`;
|
|
2388
|
-
return display;
|
|
2389
|
-
}
|
|
2390
2456
|
async function editInEditor(message) {
|
|
2391
2457
|
const { spawn } = await import("child_process");
|
|
2392
2458
|
const { writeFileSync, readFileSync, mkdtempSync, rmSync } = await import("fs");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jakejarvis/acai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Yet another AI commit message generator",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"type": "module",
|
|
22
22
|
"bin": {
|
|
23
|
-
"acai": "
|
|
23
|
+
"acai": "dist/cli.mjs"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|
|
@@ -30,15 +30,20 @@
|
|
|
30
30
|
"lint": "biome check",
|
|
31
31
|
"format": "biome format --write",
|
|
32
32
|
"check-types": "tsc --noEmit",
|
|
33
|
-
"
|
|
33
|
+
"prepack": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.81",
|
|
37
|
+
"@openai/codex-sdk": "^0.116.0",
|
|
38
|
+
"zod": "^4.3.6"
|
|
34
39
|
},
|
|
35
40
|
"devDependencies": {
|
|
36
|
-
"@biomejs/biome": "2.4.
|
|
41
|
+
"@biomejs/biome": "2.4.8",
|
|
37
42
|
"@clack/prompts": "1.1.0",
|
|
38
43
|
"@types/node": "^25.5.0",
|
|
39
44
|
"picocolors": "1.1.1",
|
|
40
45
|
"tinyexec": "1.0.4",
|
|
41
|
-
"tsdown": "
|
|
46
|
+
"tsdown": "0.21.4",
|
|
42
47
|
"typescript": "^5.9.3"
|
|
43
48
|
},
|
|
44
49
|
"engines": {
|