gitxplain 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/.env.example ADDED
@@ -0,0 +1,28 @@
1
+ LLM_PROVIDER=openai
2
+ LLM_MODEL=
3
+
4
+ OPENAI_API_KEY=
5
+ OPENAI_MODEL=gpt-4.1-mini
6
+ OPENAI_BASE_URL=https://api.openai.com/v1
7
+
8
+ GROQ_API_KEY=
9
+ GROQ_MODEL=llama-3.3-70b-versatile
10
+ GROQ_BASE_URL=https://api.groq.com/openai/v1
11
+
12
+ OPENROUTER_API_KEY=
13
+ OPENROUTER_MODEL=openai/gpt-4.1-mini
14
+ OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
15
+ OPENROUTER_SITE_URL=https://github.com
16
+ OPENROUTER_APP_NAME=gitxplain
17
+
18
+ GEMINI_API_KEY=
19
+ GEMINI_MODEL=gemini-2.5-flash
20
+ GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta
21
+
22
+ OLLAMA_API_KEY=ollama
23
+ OLLAMA_MODEL=llama3.2
24
+ OLLAMA_BASE_URL=http://127.0.0.1:11434/v1
25
+
26
+ CHUTES_API_KEY=
27
+ CHUTES_MODEL=deepseek-ai/DeepSeek-V3-0324
28
+ CHUTES_BASE_URL=https://llm.chutes.ai/v1
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # gitxplain
2
+
3
+ `gitxplain` is a Node.js CLI that analyzes Git commits, commit ranges, and branch diffs to generate structured, human-readable explanations with AI.
4
+
5
+ Supported providers:
6
+
7
+ - OpenAI
8
+ - Groq
9
+ - OpenRouter
10
+ - Gemini
11
+ - Ollama
12
+ - Chutes AI
13
+
14
+ ## Features
15
+
16
+ - Explains what a commit does, why it exists, and how the fix works
17
+ - Supports focused output modes like summary, issue, fix, impact, review, security, and line-by-line walkthroughs
18
+ - Supports single commits, commit ranges, and branch-vs-base comparisons
19
+ - Truncates oversized diffs before sending them to the model and reports that truncation
20
+ - Streams output for supported providers
21
+ - Caches responses locally to reduce repeat API costs
22
+ - Supports plain, JSON, Markdown, and HTML output
23
+ - Supports clipboard copy, verbosity controls, and hook installation
24
+ - Supports project-level and user-level config files
25
+ - Falls back to an interactive prompt when no analysis flag is supplied
26
+ - Returns plain text or JSON output
27
+ - Uses native Node APIs only, so the MVP has no runtime dependencies
28
+
29
+ ## Requirements
30
+
31
+ - Node.js 18+
32
+ - A Git repository in your current working directory
33
+ - An API key for your chosen provider, or a local Ollama instance
34
+
35
+ Optional environment variables:
36
+
37
+ - `LLM_PROVIDER` default: `openai`
38
+ - `LLM_MODEL` optional shared model override
39
+ - `OPENAI_API_KEY`, `OPENAI_MODEL`, `OPENAI_BASE_URL`
40
+ - `GROQ_API_KEY`, `GROQ_MODEL`, `GROQ_BASE_URL`
41
+ - `OPENROUTER_API_KEY`, `OPENROUTER_MODEL`, `OPENROUTER_BASE_URL`
42
+ - `OPENROUTER_SITE_URL`, `OPENROUTER_APP_NAME`
43
+ - `GEMINI_API_KEY`, `GEMINI_MODEL`, `GEMINI_BASE_URL`
44
+ - `OLLAMA_API_KEY` optional, default: `ollama`
45
+ - `OLLAMA_MODEL`, `OLLAMA_BASE_URL` default: `http://127.0.0.1:11434/v1`
46
+ - `CHUTES_API_KEY`, `CHUTES_MODEL`, `CHUTES_BASE_URL`
47
+
48
+ Optional config files:
49
+
50
+ - Project: `.gitxplainrc` or `.gitxplainrc.json`
51
+ - User: `~/.gitxplain/config.json`
52
+
53
+ You can start from:
54
+
55
+ ```bash
56
+ cp .env.example .env
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ gitxplain help
63
+ gitxplain <commit-id>
64
+ gitxplain <commit-id> --summary
65
+ gitxplain <commit-id> --issues
66
+ gitxplain <commit-id> --fix
67
+ gitxplain <commit-id> --impact
68
+ gitxplain <commit-id> --full
69
+ gitxplain <commit-id> --lines
70
+ gitxplain <commit-id> --review
71
+ gitxplain <commit-id> --security
72
+ gitxplain <commit-id> --json
73
+ gitxplain <commit-id> --markdown
74
+ gitxplain <commit-id> --html
75
+ gitxplain <commit-id> --stream
76
+ gitxplain <commit-id> --clipboard
77
+ gitxplain <commit-id> --verbose
78
+ gitxplain <commit-id> --quiet
79
+ gitxplain <start>..<end> --markdown
80
+ gitxplain --branch main --review
81
+ gitxplain --pr origin/main --security
82
+ gitxplain install-hook
83
+ gitxplain <commit-id> --provider openrouter --model anthropic/claude-3.7-sonnet
84
+ gitxplain <commit-id> --provider chutes --model deepseek-ai/DeepSeek-V3-0324
85
+ ```
86
+
87
+ Examples:
88
+
89
+ ```bash
90
+ npm start -- HEAD~1 --summary
91
+ npm start -- a1b2c3d --full
92
+ npm start -- HEAD~1 --lines
93
+ npm start -- HEAD~5..HEAD --markdown
94
+ npm start -- --branch main --review
95
+ npm start -- HEAD~1 --provider groq --model llama-3.3-70b-versatile
96
+ npm start -- HEAD~1 --provider gemini --model gemini-2.5-flash
97
+ npm start -- HEAD~1 --provider chutes --model deepseek-ai/DeepSeek-V3-0324
98
+ ```
99
+
100
+ ## Running The CLI
101
+
102
+ To use the actual `gitxplain` command directly:
103
+
104
+ ```bash
105
+ cd /home/guru/Dev/gitxplain
106
+ npm link
107
+ ```
108
+
109
+ Then from any Git repository:
110
+
111
+ ```bash
112
+ gitxplain help
113
+ gitxplain HEAD~1 --full
114
+ gitxplain a1b2c3d --summary
115
+ gitxplain HEAD~1 --lines
116
+ gitxplain HEAD~5..HEAD --markdown
117
+ gitxplain --branch main --review
118
+ ```
119
+
120
+ The `gitxplain help` command also prints quick API-key setup examples for:
121
+
122
+ - OpenAI
123
+ - Groq
124
+ - OpenRouter
125
+ - Gemini
126
+ - Ollama
127
+ - Chutes AI
128
+
129
+ If you do not want to link it globally, you can still run it locally:
130
+
131
+ ```bash
132
+ node /home/guru/Dev/gitxplain/cli/index.js HEAD~1 --full
133
+ ```
134
+
135
+ ## Output Modes
136
+
137
+ - `--summary`: one-sentence commit summary
138
+ - `--issues`: bug or issue-oriented analysis
139
+ - `--fix`: junior-friendly explanation of the fix
140
+ - `--impact`: before-vs-after explanation focused on behavior changes
141
+ - `--full`: full structured analysis
142
+ - `--lines`: file-by-file, line-by-line walkthrough of the changed code
143
+ - `--review`: code review findings with actionable suggestions
144
+ - `--security`: security-focused analysis of the change
145
+ - `--json`: return structured JSON instead of formatted text
146
+ - `--markdown`: return Markdown output
147
+ - `--html`: return HTML output
148
+
149
+ If no analysis flag is supplied, the CLI asks what kind of explanation you want.
150
+
151
+ ## Comparison Modes
152
+
153
+ Single commit:
154
+
155
+ ```bash
156
+ gitxplain HEAD~1 --full
157
+ ```
158
+
159
+ Commit range:
160
+
161
+ ```bash
162
+ gitxplain HEAD~5..HEAD --markdown
163
+ ```
164
+
165
+ Branch or PR-style comparison:
166
+
167
+ ```bash
168
+ gitxplain --branch main --review
169
+ gitxplain --pr origin/main --security
170
+ ```
171
+
172
+ `--branch` and `--pr` compare the current branch to a base ref using the merge base with `HEAD`.
173
+
174
+ ## Config File
175
+
176
+ Example `.gitxplainrc`:
177
+
178
+ ```json
179
+ {
180
+ "provider": "groq",
181
+ "model": "llama-3.3-70b-versatile",
182
+ "mode": "full",
183
+ "format": "markdown",
184
+ "maxDiffLines": 600,
185
+ "stream": true,
186
+ "verbose": false
187
+ }
188
+ ```
189
+
190
+ CLI flags still override config values for a single command.
191
+
192
+ ## Clipboard, Streaming, And Hooks
193
+
194
+ Copy the final output to your clipboard:
195
+
196
+ ```bash
197
+ gitxplain HEAD~1 --markdown --clipboard
198
+ ```
199
+
200
+ Stream long responses as they arrive:
201
+
202
+ ```bash
203
+ gitxplain HEAD~1 --full --stream
204
+ ```
205
+
206
+ Install a post-commit hook that saves a Markdown explanation under `.git/gitxplain/last-explanation.md`:
207
+
208
+ ```bash
209
+ gitxplain install-hook
210
+ ```
211
+
212
+ ## Provider Setup
213
+
214
+ OpenAI:
215
+
216
+ ```bash
217
+ export LLM_PROVIDER=openai
218
+ export OPENAI_API_KEY=your_key
219
+ ```
220
+
221
+ Groq:
222
+
223
+ ```bash
224
+ export LLM_PROVIDER=groq
225
+ export GROQ_API_KEY=your_key
226
+ ```
227
+
228
+ OpenRouter:
229
+
230
+ ```bash
231
+ export LLM_PROVIDER=openrouter
232
+ export OPENROUTER_API_KEY=your_key
233
+ ```
234
+
235
+ Gemini:
236
+
237
+ ```bash
238
+ export LLM_PROVIDER=gemini
239
+ export GEMINI_API_KEY=your_key
240
+ ```
241
+
242
+ Ollama:
243
+
244
+ ```bash
245
+ export LLM_PROVIDER=ollama
246
+ export OLLAMA_MODEL=llama3.2
247
+ ```
248
+
249
+ Chutes AI:
250
+
251
+ ```bash
252
+ export LLM_PROVIDER=chutes
253
+ export CHUTES_API_KEY=your_key
254
+ export CHUTES_MODEL=deepseek-ai/DeepSeek-V3-0324
255
+ ```
256
+
257
+ ## Development
258
+
259
+ ```bash
260
+ npm run lint
261
+ npm test
262
+ ```
263
+
264
+ To make the command globally available during local development:
265
+
266
+ ```bash
267
+ npm link
268
+ ```
package/cli/index.js ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { realpathSync } from "node:fs";
7
+ import { generateExplanation } from "./services/aiService.js";
8
+ import { copyToClipboard } from "./services/clipboardService.js";
9
+ import { loadConfig } from "./services/configService.js";
10
+ import {
11
+ buildBranchRange,
12
+ fetchCommitData,
13
+ getDefaultBaseRef,
14
+ isGitRepository
15
+ } from "./services/gitService.js";
16
+ import { installHook } from "./services/hookService.js";
17
+ import {
18
+ formatFooter,
19
+ formatHtmlOutput,
20
+ formatJsonOutput,
21
+ formatMarkdownOutput,
22
+ formatOutput,
23
+ formatPreamble
24
+ } from "./services/outputFormatter.js";
25
+
26
+ const MODE_FLAGS = new Map([
27
+ ["--summary", "summary"],
28
+ ["--issues", "issues"],
29
+ ["--fix", "fix"],
30
+ ["--impact", "impact"],
31
+ ["--full", "full"],
32
+ ["--lines", "lines"],
33
+ ["--review", "review"],
34
+ ["--security", "security"]
35
+ ]);
36
+
37
+ const FORMAT_FLAGS = new Map([
38
+ ["--json", "json"],
39
+ ["--markdown", "markdown"],
40
+ ["--html", "html"]
41
+ ]);
42
+
43
+ function printHelp() {
44
+ console.log(`gitxplain - AI-powered Git and branch explainer
45
+
46
+ Usage:
47
+ gitxplain help
48
+ gitxplain --help
49
+ gitxplain install-hook [hook-name]
50
+ gitxplain <commit-id> [options]
51
+ gitxplain <start>..<end> [options]
52
+ gitxplain --branch [base-ref] [options]
53
+ gitxplain --pr [base-ref] [options]
54
+
55
+ Modes:
56
+ --summary Generate a one-line summary
57
+ --issues Focus on bug or issue analysis
58
+ --fix Explain the fix in simple terms
59
+ --impact Explain before-vs-after behavior changes
60
+ --full Generate a full structured analysis
61
+ --lines Explain the changed code line by line
62
+ --review Generate a code review with risks and suggestions
63
+ --security Focus on security risks introduced by the change
64
+
65
+ Output:
66
+ --json Print JSON output
67
+ --markdown Print Markdown output
68
+ --html Print HTML output
69
+ --quiet Print only the explanation body
70
+ --verbose Print provider, model, cache, latency, and usage details
71
+ --clipboard Copy the final output to the system clipboard
72
+ --stream Stream the explanation as it is generated when supported
73
+
74
+ Providers:
75
+ --provider LLM provider: openai, groq, openrouter, gemini, ollama, chutes
76
+ --model Override the model name
77
+
78
+ Diff Budget:
79
+ --max-diff-lines <n> Limit diff lines sent to the model
80
+
81
+ Comparison:
82
+ --branch [base-ref] Analyze current branch against base branch
83
+ --pr [base-ref] Alias for --branch, useful for PR-style summaries
84
+
85
+ Examples:
86
+ gitxplain HEAD~1 --full
87
+ gitxplain HEAD~5..HEAD --markdown
88
+ gitxplain --branch main --review
89
+ gitxplain --pr origin/main --security --stream
90
+ gitxplain HEAD~1 --provider chutes --model deepseek-ai/DeepSeek-V3-0324
91
+
92
+ Provider Setup:
93
+ OpenAI:
94
+ export LLM_PROVIDER=openai
95
+ export OPENAI_API_KEY=your_key
96
+
97
+ Groq:
98
+ export LLM_PROVIDER=groq
99
+ export GROQ_API_KEY=your_key
100
+
101
+ OpenRouter:
102
+ export LLM_PROVIDER=openrouter
103
+ export OPENROUTER_API_KEY=your_key
104
+
105
+ Gemini:
106
+ export LLM_PROVIDER=gemini
107
+ export GEMINI_API_KEY=your_key
108
+
109
+ Ollama:
110
+ export LLM_PROVIDER=ollama
111
+ export OLLAMA_MODEL=llama3.2
112
+
113
+ Chutes:
114
+ export LLM_PROVIDER=chutes
115
+ export CHUTES_API_KEY=your_key
116
+
117
+ Config:
118
+ Project config: .gitxplainrc or .gitxplainrc.json
119
+ User config: ~/.gitxplain/config.json
120
+
121
+ Hook Installation:
122
+ gitxplain install-hook
123
+ gitxplain install-hook post-commit
124
+
125
+ Notes:
126
+ Run gitxplain inside a Git repository.
127
+ Use --provider or --model to override your config or environment for one command.
128
+ `);
129
+ }
130
+
131
+ function getFlagValue(args, flagName) {
132
+ const directIndex = args.findIndex((arg) => arg === flagName);
133
+ if (directIndex >= 0) {
134
+ const nextArg = args[directIndex + 1];
135
+ if (nextArg && !nextArg.startsWith("--")) {
136
+ return nextArg;
137
+ }
138
+
139
+ return null;
140
+ }
141
+
142
+ const inline = args.find((arg) => arg.startsWith(`${flagName}=`));
143
+ return inline ? inline.slice(flagName.length + 1) : null;
144
+ }
145
+
146
+ function parseNumber(value, fallback = null) {
147
+ if (value == null || value === "") {
148
+ return fallback;
149
+ }
150
+
151
+ const parsed = Number.parseInt(value, 10);
152
+ if (Number.isNaN(parsed) || parsed <= 0) {
153
+ throw new Error(`Invalid numeric value: ${value}`);
154
+ }
155
+
156
+ return parsed;
157
+ }
158
+
159
+ export function parseArgs(argv) {
160
+ const args = argv.slice(2);
161
+ const subcommand = args[0];
162
+ const flags = new Set(args.filter((arg) => arg.startsWith("--")));
163
+ const valueFlags = new Set(["--provider", "--model", "--max-diff-lines", "--branch", "--pr"]);
164
+ const positional = [];
165
+
166
+ for (let index = 0; index < args.length; index += 1) {
167
+ const arg = args[index];
168
+
169
+ if (!arg.startsWith("--")) {
170
+ positional.push(arg);
171
+ continue;
172
+ }
173
+
174
+ if (arg.includes("=")) {
175
+ continue;
176
+ }
177
+
178
+ if (valueFlags.has(arg)) {
179
+ const nextArg = args[index + 1];
180
+ if (nextArg && !nextArg.startsWith("--")) {
181
+ index += 1;
182
+ }
183
+ }
184
+ }
185
+
186
+ const explicitMode = [...MODE_FLAGS.entries()].find(([flag]) => flags.has(flag))?.[1] ?? null;
187
+ const explicitFormat = [...FORMAT_FLAGS.entries()].find(([flag]) => flags.has(flag))?.[1] ?? null;
188
+ const isInstallHook = subcommand === "install-hook";
189
+
190
+ return {
191
+ subcommand,
192
+ help: flags.has("--help") || subcommand === "help",
193
+ installHook: isInstallHook,
194
+ hookName: isInstallHook ? positional[1] ?? "post-commit" : null,
195
+ commitRef: isInstallHook || subcommand === "help" ? null : positional[0] ?? null,
196
+ mode: explicitMode,
197
+ format: explicitFormat,
198
+ provider: getFlagValue(args, "--provider"),
199
+ model: getFlagValue(args, "--model"),
200
+ maxDiffLines: parseNumber(getFlagValue(args, "--max-diff-lines")),
201
+ hasBranchFlag: flags.has("--branch") || args.some((arg) => arg.startsWith("--branch=")),
202
+ branchBase: getFlagValue(args, "--branch"),
203
+ hasPrFlag: flags.has("--pr") || args.some((arg) => arg.startsWith("--pr=")),
204
+ prBase: getFlagValue(args, "--pr"),
205
+ clipboard: flags.has("--clipboard"),
206
+ stream: flags.has("--stream"),
207
+ verbose: flags.has("--verbose"),
208
+ quiet: flags.has("--quiet")
209
+ };
210
+ }
211
+
212
+ function askQuestion(prompt) {
213
+ return new Promise((resolve) => {
214
+ process.stdout.write(prompt);
215
+ process.stdin.resume();
216
+ process.stdin.setEncoding("utf8");
217
+ process.stdin.once("data", (input) => {
218
+ process.stdin.pause();
219
+ resolve(input.trim());
220
+ });
221
+ });
222
+ }
223
+
224
+ async function chooseModeInteractively() {
225
+ const answer = await askQuestion(
226
+ [
227
+ "What do you want to know?",
228
+ "1. Summary",
229
+ "2. Issues Fixed",
230
+ "3. Fix Explanation",
231
+ "4. Impact",
232
+ "5. Full Analysis",
233
+ "6. Line-by-Line Code Walkthrough",
234
+ "7. Code Review",
235
+ "8. Security Review",
236
+ "> "
237
+ ].join("\n")
238
+ );
239
+
240
+ const selections = {
241
+ "1": "summary",
242
+ "2": "issues",
243
+ "3": "fix",
244
+ "4": "impact",
245
+ "5": "full",
246
+ "6": "lines",
247
+ "7": "review",
248
+ "8": "security"
249
+ };
250
+
251
+ return selections[answer] ?? "full";
252
+ }
253
+
254
+ function resolveRuntimeOptions(parsed, config) {
255
+ return {
256
+ mode: parsed.mode ?? config.mode ?? "full",
257
+ format: parsed.format ?? config.format ?? "plain",
258
+ provider: parsed.provider ?? config.provider ?? null,
259
+ model: parsed.model ?? config.model ?? null,
260
+ maxDiffLines: parsed.maxDiffLines ?? config.maxDiffLines ?? 800,
261
+ clipboard: parsed.clipboard || config.clipboard === true,
262
+ stream: parsed.stream || config.stream === true,
263
+ verbose: parsed.verbose || config.verbose === true,
264
+ quiet: parsed.quiet || config.quiet === true
265
+ };
266
+ }
267
+
268
+ function resolveTargetRef(parsed, cwd) {
269
+ if (parsed.commitRef) {
270
+ return parsed.commitRef;
271
+ }
272
+
273
+ if (parsed.hasBranchFlag || parsed.hasPrFlag) {
274
+ const baseRef = parsed.branchBase || parsed.prBase || getDefaultBaseRef(cwd);
275
+ return buildBranchRange(baseRef, cwd);
276
+ }
277
+
278
+ return null;
279
+ }
280
+
281
+ function renderFinalOutput({ runtimeOptions, mode, commitData, explanation, responseMeta, promptMeta }) {
282
+ if (runtimeOptions.format === "json") {
283
+ return formatJsonOutput({ mode, commitData, explanation, responseMeta, promptMeta });
284
+ }
285
+
286
+ if (runtimeOptions.format === "markdown") {
287
+ return formatMarkdownOutput({ mode, commitData, explanation, responseMeta, promptMeta });
288
+ }
289
+
290
+ if (runtimeOptions.format === "html") {
291
+ return formatHtmlOutput({ mode, commitData, explanation, responseMeta, promptMeta });
292
+ }
293
+
294
+ return formatOutput({
295
+ mode,
296
+ commitData,
297
+ explanation,
298
+ responseMeta,
299
+ promptMeta,
300
+ options: runtimeOptions
301
+ });
302
+ }
303
+
304
+ export async function main(argv = process.argv) {
305
+ const cwd = process.cwd();
306
+ const config = loadConfig(cwd);
307
+ const parsed = parseArgs(argv);
308
+
309
+ if (parsed.help) {
310
+ printHelp();
311
+ return 0;
312
+ }
313
+
314
+ if (!isGitRepository(cwd)) {
315
+ console.error("gitxplain must be run inside a Git repository.");
316
+ return 1;
317
+ }
318
+
319
+ if (parsed.installHook) {
320
+ const hookPath = installHook({ cwd, hookName: parsed.hookName });
321
+ console.log(`Installed ${parsed.hookName} hook at ${hookPath}`);
322
+ return 0;
323
+ }
324
+
325
+ const runtimeOptions = resolveRuntimeOptions(parsed, config);
326
+ const targetRef = resolveTargetRef(parsed, cwd);
327
+
328
+ if (!targetRef) {
329
+ printHelp();
330
+ return 1;
331
+ }
332
+
333
+ const mode = parsed.mode ?? config.mode ?? (await chooseModeInteractively());
334
+ const commitData = fetchCommitData(targetRef, cwd);
335
+ const canStream = runtimeOptions.stream && runtimeOptions.format === "plain";
336
+ let streamStarted = false;
337
+
338
+ const { explanation, responseMeta, promptMeta } = await generateExplanation({
339
+ mode,
340
+ commitData,
341
+ providerOverride: runtimeOptions.provider,
342
+ modelOverride: runtimeOptions.model,
343
+ maxDiffLines: runtimeOptions.maxDiffLines,
344
+ stream: canStream,
345
+ onStart: canStream
346
+ ? ({ promptMeta: streamPromptMeta }) => {
347
+ if (!runtimeOptions.quiet && !streamStarted) {
348
+ process.stdout.write(
349
+ formatPreamble({
350
+ mode,
351
+ commitData,
352
+ responseMeta: null,
353
+ promptMeta: streamPromptMeta,
354
+ options: runtimeOptions
355
+ })
356
+ );
357
+ streamStarted = true;
358
+ }
359
+ }
360
+ : null,
361
+ onChunk: canStream ? (chunk) => process.stdout.write(chunk) : null
362
+ });
363
+
364
+ let renderedOutput;
365
+
366
+ if (canStream) {
367
+ process.stdout.write("\n");
368
+ if (runtimeOptions.verbose) {
369
+ process.stdout.write(formatFooter({ responseMeta, promptMeta, options: runtimeOptions }));
370
+ }
371
+
372
+ renderedOutput = renderFinalOutput({
373
+ runtimeOptions,
374
+ mode,
375
+ commitData,
376
+ explanation,
377
+ responseMeta,
378
+ promptMeta
379
+ });
380
+ } else {
381
+ renderedOutput = renderFinalOutput({
382
+ runtimeOptions,
383
+ mode,
384
+ commitData,
385
+ explanation,
386
+ responseMeta,
387
+ promptMeta
388
+ });
389
+ console.log(renderedOutput);
390
+ }
391
+
392
+ if (runtimeOptions.clipboard) {
393
+ copyToClipboard(renderedOutput);
394
+ if (!runtimeOptions.quiet) {
395
+ console.error("Copied output to clipboard.");
396
+ }
397
+ }
398
+
399
+ return 0;
400
+ }
401
+
402
+ const entryFile = fileURLToPath(import.meta.url);
403
+ const executedFile = process.argv[1] ? realpathSync(path.resolve(process.argv[1])) : "";
404
+
405
+ if (executedFile === entryFile) {
406
+ main().then(
407
+ (code) => process.exit(code),
408
+ (error) => {
409
+ console.error(error.message);
410
+ process.exit(1);
411
+ }
412
+ );
413
+ }