gitxplain 0.1.6 → 0.1.8
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/.github/workflows/release.yml +1 -1
- package/README.md +362 -133
- package/cli/index.js +189 -278
- package/cli/services/configService.js +75 -3
- package/cli/services/mergeService.js +56 -39
- package/cli/services/pipelineService.js +1 -1
- package/package.json +1 -1
package/cli/index.js
CHANGED
|
@@ -5,17 +5,16 @@ import process from "node:process";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { realpathSync } from "node:fs";
|
|
7
7
|
import { generateExplanation } from "./services/aiService.js";
|
|
8
|
-
import { startChatSession } from "./services/chatService.js";
|
|
9
8
|
import { loadEnvFile } from "./services/envLoader.js";
|
|
10
|
-
import {
|
|
11
|
-
saveGitConnection,
|
|
12
|
-
isGitConnected,
|
|
13
|
-
loadGitConnection,
|
|
14
|
-
getGitUserInfo,
|
|
15
|
-
verifyGitToken
|
|
16
|
-
} from "./services/gitConnectionService.js";
|
|
17
9
|
import { copyToClipboard } from "./services/clipboardService.js";
|
|
18
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
applyConfigEnvironment,
|
|
12
|
+
getProviderApiKeyField,
|
|
13
|
+
getUserConfigPath,
|
|
14
|
+
loadConfig,
|
|
15
|
+
loadUserConfig,
|
|
16
|
+
updateUserConfig
|
|
17
|
+
} from "./services/configService.js";
|
|
19
18
|
import {
|
|
20
19
|
buildBranchRange,
|
|
21
20
|
deletePaths,
|
|
@@ -85,6 +84,7 @@ const MODE_FLAGS = new Map([
|
|
|
85
84
|
["--merge", "merge"],
|
|
86
85
|
["--tag", "tag"],
|
|
87
86
|
["--commit", "commit"],
|
|
87
|
+
["--release", "release"],
|
|
88
88
|
["--log", "log"],
|
|
89
89
|
["--status", "status"],
|
|
90
90
|
["--pipeline", "pipeline"]
|
|
@@ -98,7 +98,7 @@ const FORMAT_FLAGS = new Map([
|
|
|
98
98
|
|
|
99
99
|
const RESERVED_SUBCOMMANDS = new Set([
|
|
100
100
|
"help",
|
|
101
|
-
"
|
|
101
|
+
"config",
|
|
102
102
|
"install-hook",
|
|
103
103
|
"git",
|
|
104
104
|
"add",
|
|
@@ -107,210 +107,95 @@ const RESERVED_SUBCOMMANDS = new Set([
|
|
|
107
107
|
"bin",
|
|
108
108
|
"pop",
|
|
109
109
|
"pull",
|
|
110
|
-
"push"
|
|
111
|
-
"commit",
|
|
112
|
-
"merge",
|
|
113
|
-
"tag",
|
|
114
|
-
"release",
|
|
115
|
-
"log",
|
|
116
|
-
"status",
|
|
117
|
-
"pipeline"
|
|
110
|
+
"push"
|
|
118
111
|
]);
|
|
119
112
|
|
|
120
113
|
function printHelp() {
|
|
121
114
|
console.log(`gitxplain - AI-powered Git change analysis, review, and commit workflow CLI
|
|
122
115
|
|
|
123
116
|
Usage:
|
|
124
|
-
gitxplain help
|
|
125
117
|
gitxplain --help
|
|
126
|
-
gitxplain
|
|
127
|
-
gitxplain --
|
|
128
|
-
gitxplain
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
gitxplain
|
|
118
|
+
gitxplain config set provider <name>
|
|
119
|
+
gitxplain config set api-key <value> [--provider <name>]
|
|
120
|
+
gitxplain config get [key]
|
|
121
|
+
gitxplain config list
|
|
122
|
+
gitxplain <commit-id> [options]
|
|
123
|
+
gitxplain <start>..<end> [options]
|
|
124
|
+
gitxplain --branch [base-ref] [options]
|
|
125
|
+
gitxplain --pr [base-ref] [options]
|
|
132
126
|
gitxplain --commit
|
|
133
|
-
gitxplain
|
|
127
|
+
gitxplain --release [status]
|
|
134
128
|
gitxplain --merge
|
|
135
|
-
gitxplain tag
|
|
136
129
|
gitxplain --tag
|
|
137
|
-
gitxplain release
|
|
138
|
-
gitxplain release status
|
|
139
|
-
gitxplain log
|
|
140
130
|
gitxplain --log
|
|
141
|
-
gitxplain status
|
|
142
131
|
gitxplain --status
|
|
143
132
|
gitxplain --pipeline
|
|
144
|
-
gitxplain add <path> [more-paths...]
|
|
145
|
-
gitxplain remove <path> [more-paths...]
|
|
146
|
-
gitxplain remove hard
|
|
147
|
-
gitxplain del <path> [more-paths...]
|
|
148
|
-
gitxplain bin
|
|
149
|
-
gitxplain pop [stash-index]
|
|
150
|
-
gitxplain pull [remote] [branch]
|
|
151
|
-
gitxplain push [remote] [branch]
|
|
152
|
-
gitxplain install-hook [hook-name]
|
|
153
|
-
|
|
154
|
-
GitHub:
|
|
155
|
-
gitxplain --connect-github [token]
|
|
156
|
-
gitxplain --boot [options]
|
|
157
133
|
|
|
158
134
|
Analysis:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
--
|
|
179
|
-
--
|
|
180
|
-
--
|
|
181
|
-
--impact Explain before-vs-after behavior changes
|
|
182
|
-
--full Generate a full structured analysis
|
|
183
|
-
--lines Walk through the changed code file by file
|
|
184
|
-
--review Generate review findings, risks, and suggestions
|
|
185
|
-
--security Focus on security-relevant changes and concerns
|
|
186
|
-
--split Propose splitting a commit into smaller atomic commits
|
|
187
|
-
--merge Preview or apply a merge into the release branch based on version bumps
|
|
188
|
-
--tag Preview or create release tags based on version bumps
|
|
189
|
-
release Show release branch health, missing tags, and next recommended action
|
|
190
|
-
--commit Propose commits for current uncommitted changes
|
|
191
|
-
--log Print Git log entries for the current repository
|
|
192
|
-
--status Print Git working tree status for the current repository
|
|
193
|
-
--pipeline Detect the current repository stack and create CI/CD workflow files
|
|
194
|
-
--execute Execute a proposed split or commit plan
|
|
195
|
-
--dry-run Preview the plan without executing it (default for --split and --commit)
|
|
135
|
+
--summary Generate a one-line summary of a change
|
|
136
|
+
--issues Focus on the issue or failure being addressed
|
|
137
|
+
--fix Explain the fix in simple terms
|
|
138
|
+
--impact Explain behavior changes before vs after
|
|
139
|
+
--full Generate a full structured analysis
|
|
140
|
+
--lines Walk through the changed code file by file
|
|
141
|
+
--review Generate review findings, risks, and suggestions
|
|
142
|
+
--security Focus on security-relevant changes and concerns
|
|
143
|
+
--split Propose splitting a commit into smaller atomic commits
|
|
144
|
+
--commit Propose commits for current uncommitted changes
|
|
145
|
+
--execute Execute a proposed split or commit plan
|
|
146
|
+
--dry-run Preview the plan without executing it
|
|
147
|
+
|
|
148
|
+
Release:
|
|
149
|
+
--release [status] Show release branch health and next recommended action
|
|
150
|
+
--merge Preview or apply a merge into the release branch
|
|
151
|
+
--tag Preview or create release tags from version bumps
|
|
152
|
+
|
|
153
|
+
Repo:
|
|
154
|
+
--log Print Git log entries for the current repository
|
|
155
|
+
--status Print Git working tree status for the current repository
|
|
156
|
+
--pipeline Detect the current repository stack and create CI/CD workflow files
|
|
196
157
|
|
|
197
158
|
Quick Actions:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
remove
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
159
|
+
config Persist provider, model, and API key settings
|
|
160
|
+
add Stage one or more files with git add
|
|
161
|
+
remove Unstage one or more files with git restore --staged
|
|
162
|
+
remove hard Hard reset the repository to HEAD
|
|
163
|
+
del Delete one or more files from the working tree
|
|
164
|
+
bin Soft reset HEAD~1 while keeping your changes
|
|
165
|
+
pop Pop a stash entry like "pop 2"
|
|
166
|
+
pull Run git pull, optionally with a remote and branch
|
|
167
|
+
push Run git push, optionally with a remote and branch
|
|
168
|
+
install-hook Install the gitxplain hook
|
|
169
|
+
git Pass through to native git commands
|
|
206
170
|
|
|
207
171
|
Output:
|
|
208
|
-
--
|
|
209
|
-
--
|
|
210
|
-
--
|
|
211
|
-
--
|
|
212
|
-
--
|
|
213
|
-
--
|
|
214
|
-
--
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
--
|
|
218
|
-
--boot Launch an interactive chat session for dynamic querying, PR creation, and cloning
|
|
219
|
-
|
|
220
|
-
Providers:
|
|
221
|
-
--provider LLM provider: openai, groq, openrouter, gemini, ollama, chutes
|
|
222
|
-
--model Override the model name
|
|
223
|
-
|
|
224
|
-
Diff Budget:
|
|
225
|
-
--max-diff-lines <n> Limit diff lines sent to the model
|
|
172
|
+
--provider <name>
|
|
173
|
+
--model <name>
|
|
174
|
+
--json
|
|
175
|
+
--markdown
|
|
176
|
+
--html
|
|
177
|
+
--quiet
|
|
178
|
+
--verbose
|
|
179
|
+
--clipboard
|
|
180
|
+
--stream
|
|
181
|
+
--max-diff-lines <n>
|
|
226
182
|
|
|
227
183
|
Comparison:
|
|
228
|
-
--branch [base-ref]
|
|
229
|
-
--pr [base-ref]
|
|
230
|
-
|
|
231
|
-
Provider Setup:
|
|
232
|
-
OpenAI:
|
|
233
|
-
export LLM_PROVIDER=openai
|
|
234
|
-
export OPENAI_API_KEY=your_key
|
|
235
|
-
|
|
236
|
-
Groq:
|
|
237
|
-
export LLM_PROVIDER=groq
|
|
238
|
-
export GROQ_API_KEY=your_key
|
|
239
|
-
|
|
240
|
-
OpenRouter:
|
|
241
|
-
export LLM_PROVIDER=openrouter
|
|
242
|
-
export OPENROUTER_API_KEY=your_key
|
|
243
|
-
|
|
244
|
-
Gemini:
|
|
245
|
-
export LLM_PROVIDER=gemini
|
|
246
|
-
export GEMINI_API_KEY=your_key
|
|
247
|
-
|
|
248
|
-
Ollama:
|
|
249
|
-
export LLM_PROVIDER=ollama
|
|
250
|
-
export OLLAMA_MODEL=llama3.2
|
|
251
|
-
|
|
252
|
-
Chutes:
|
|
253
|
-
export LLM_PROVIDER=chutes
|
|
254
|
-
export CHUTES_API_KEY=your_key
|
|
184
|
+
--branch [base-ref] Analyze the current branch against a base branch
|
|
185
|
+
--pr [base-ref] Alias for --branch, useful for PR-style comparisons
|
|
255
186
|
|
|
256
187
|
Config:
|
|
257
188
|
Project config: .gitxplainrc or .gitxplainrc.json
|
|
258
|
-
User config: ~/.gitxplain/config.json
|
|
259
|
-
|
|
260
|
-
Hook Installation:
|
|
261
|
-
gitxplain install-hook
|
|
262
|
-
gitxplain install-hook post-commit
|
|
189
|
+
User config: ~/.gitxplain/config.json (macOS/Linux) or %USERPROFILE%\\.gitxplain\\config.json (Windows)
|
|
263
190
|
|
|
264
191
|
Notes:
|
|
265
192
|
Run gitxplain inside a Git repository.
|
|
266
|
-
If no mode is supplied, gitxplain
|
|
193
|
+
If no command or mode is supplied, gitxplain prints this help text.
|
|
267
194
|
Use --provider or --model to override your config or environment for one command.
|
|
268
195
|
Use gitxplain git <args...> to run any native Git subcommand with its normal flags.
|
|
269
196
|
`);
|
|
270
197
|
}
|
|
271
198
|
|
|
272
|
-
function printExamples() {
|
|
273
|
-
console.log(`gitxplain examples
|
|
274
|
-
|
|
275
|
-
Examples:
|
|
276
|
-
gitxplain --connect-github <token>
|
|
277
|
-
gitxplain --boot
|
|
278
|
-
gitxplain HEAD~1 --full
|
|
279
|
-
gitxplain HEAD~1 --review
|
|
280
|
-
gitxplain HEAD~5..HEAD --markdown
|
|
281
|
-
gitxplain --branch main --review
|
|
282
|
-
gitxplain --pr origin/main --security --stream
|
|
283
|
-
gitxplain commit
|
|
284
|
-
gitxplain --commit --execute
|
|
285
|
-
gitxplain merge
|
|
286
|
-
gitxplain --merge --execute
|
|
287
|
-
gitxplain tag
|
|
288
|
-
gitxplain --tag --execute
|
|
289
|
-
gitxplain release
|
|
290
|
-
gitxplain release status
|
|
291
|
-
gitxplain log
|
|
292
|
-
gitxplain --log
|
|
293
|
-
gitxplain status
|
|
294
|
-
gitxplain --status
|
|
295
|
-
gitxplain pipeline
|
|
296
|
-
gitxplain --pipeline
|
|
297
|
-
gitxplain add README.md
|
|
298
|
-
gitxplain remove README.md
|
|
299
|
-
gitxplain remove hard
|
|
300
|
-
gitxplain del scratch.txt
|
|
301
|
-
gitxplain bin
|
|
302
|
-
gitxplain pop
|
|
303
|
-
gitxplain pop 2
|
|
304
|
-
gitxplain pull
|
|
305
|
-
gitxplain pull origin main
|
|
306
|
-
gitxplain push
|
|
307
|
-
gitxplain push origin main
|
|
308
|
-
gitxplain HEAD~1 --split
|
|
309
|
-
gitxplain HEAD --split --execute
|
|
310
|
-
gitxplain HEAD~1 --provider chutes --model deepseek-ai/DeepSeek-V3-0324
|
|
311
|
-
`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
199
|
function getFlagValue(args, flagName) {
|
|
315
200
|
const directIndex = args.findIndex((arg) => arg === flagName);
|
|
316
201
|
if (directIndex >= 0) {
|
|
@@ -339,6 +224,104 @@ function parseNumber(value, fallback = null) {
|
|
|
339
224
|
return parsed;
|
|
340
225
|
}
|
|
341
226
|
|
|
227
|
+
function redactConfigValue(key, value) {
|
|
228
|
+
if (typeof value !== "string") {
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!/api[_-]?key/i.test(key)) {
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (value.length <= 8) {
|
|
237
|
+
return "*".repeat(value.length);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function printConfigEntries(config) {
|
|
244
|
+
const entries = Object.entries(config).sort(([left], [right]) => left.localeCompare(right));
|
|
245
|
+
|
|
246
|
+
if (entries.length === 0) {
|
|
247
|
+
console.log("No user config saved yet.");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const [key, value] of entries) {
|
|
252
|
+
console.log(`${key}: ${redactConfigValue(key, value)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function resolveConfigSetUpdate(parsed, currentConfig) {
|
|
257
|
+
const key = parsed.configKey;
|
|
258
|
+
const value = parsed.configValue;
|
|
259
|
+
|
|
260
|
+
if (!key || !value) {
|
|
261
|
+
throw new Error('Usage: gitxplain config set <provider|model|api-key> <value> [--provider <name>]');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (key === "provider") {
|
|
265
|
+
return { provider: value.toLowerCase() };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (key === "model") {
|
|
269
|
+
return { model: value };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (key === "api-key") {
|
|
273
|
+
const resolvedProvider = (parsed.provider ?? currentConfig.provider ?? currentConfig.LLM_PROVIDER ?? "").toLowerCase();
|
|
274
|
+
const apiKeyField = getProviderApiKeyField(resolvedProvider);
|
|
275
|
+
|
|
276
|
+
if (!apiKeyField) {
|
|
277
|
+
throw new Error("Set a provider first with `gitxplain config set provider <name>`, or pass `--provider <name>`.");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { [apiKeyField]: value };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { [key]: value };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function handleConfigCommand(parsed) {
|
|
287
|
+
const currentConfig = loadUserConfig();
|
|
288
|
+
|
|
289
|
+
if (parsed.configAction === "list" || parsed.configAction == null) {
|
|
290
|
+
console.log(`User config: ${getUserConfigPath()}`);
|
|
291
|
+
printConfigEntries(currentConfig);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (parsed.configAction === "get") {
|
|
296
|
+
console.log(`User config: ${getUserConfigPath()}`);
|
|
297
|
+
|
|
298
|
+
if (!parsed.configKey) {
|
|
299
|
+
printConfigEntries(currentConfig);
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const value = currentConfig[parsed.configKey];
|
|
304
|
+
if (value === undefined) {
|
|
305
|
+
console.log(`No value saved for ${parsed.configKey}.`);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(`${parsed.configKey}: ${redactConfigValue(parsed.configKey, value)}`);
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (parsed.configAction === "set") {
|
|
314
|
+
const updates = resolveConfigSetUpdate(parsed, currentConfig);
|
|
315
|
+
const { configPath } = updateUserConfig(updates);
|
|
316
|
+
const [savedKey, savedValue] = Object.entries(updates)[0];
|
|
317
|
+
console.log(`Saved ${savedKey} to ${configPath}.`);
|
|
318
|
+
console.log(`${savedKey}: ${redactConfigValue(savedKey, savedValue)}`);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
throw new Error(`Unknown config subcommand: ${parsed.configAction}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
342
325
|
function isDirectNativeGitSubcommand(subcommand, knownGitSubcommands) {
|
|
343
326
|
if (!subcommand || subcommand.startsWith("-")) {
|
|
344
327
|
return false;
|
|
@@ -382,20 +365,13 @@ export function parseArgs(argv, options = {}) {
|
|
|
382
365
|
const explicitMode = [...MODE_FLAGS.entries()].find(([flag]) => flags.has(flag))?.[1] ?? null;
|
|
383
366
|
const explicitFormat = [...FORMAT_FLAGS.entries()].find(([flag]) => flags.has(flag))?.[1] ?? null;
|
|
384
367
|
const isInstallHook = subcommand === "install-hook";
|
|
385
|
-
const
|
|
368
|
+
const isConfigCommand = subcommand === "config";
|
|
386
369
|
const isNativeGitWrapper = subcommand === "git";
|
|
387
|
-
const
|
|
388
|
-
const isBoot = flags.has("--boot");
|
|
389
|
-
const isLogCommand = subcommand === "log";
|
|
390
|
-
const isStatusCommand = subcommand === "status";
|
|
391
|
-
const isCommitCommand = subcommand === "commit";
|
|
392
|
-
const isMergeCommand = subcommand === "merge";
|
|
393
|
-
const isTagCommand = subcommand === "tag";
|
|
394
|
-
const isReleaseCommand = subcommand === "release";
|
|
370
|
+
const isReleaseCommand = flags.has("--release");
|
|
395
371
|
const isAddCommand = subcommand === "add";
|
|
396
372
|
const isRemoveCommand = subcommand === "remove";
|
|
397
373
|
const isDeleteCommand = subcommand === "del";
|
|
398
|
-
const isPipelineCommand =
|
|
374
|
+
const isPipelineCommand = flags.has("--pipeline");
|
|
399
375
|
const isBinCommand = subcommand === "bin";
|
|
400
376
|
const isPopCommand = subcommand === "pop";
|
|
401
377
|
const isPullCommand = subcommand === "pull";
|
|
@@ -406,18 +382,19 @@ export function parseArgs(argv, options = {}) {
|
|
|
406
382
|
return {
|
|
407
383
|
subcommand,
|
|
408
384
|
help: flags.has("--help") || subcommand === "help",
|
|
409
|
-
example: isExample,
|
|
410
385
|
nativeGitCommand: isNativeGitCommand,
|
|
411
386
|
installHook: isInstallHook,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
387
|
+
configCommand: isConfigCommand,
|
|
388
|
+
configAction: isConfigCommand ? positional[1] ?? null : null,
|
|
389
|
+
configKey: isConfigCommand ? positional[2] ?? null : null,
|
|
390
|
+
configValue: isConfigCommand ? positional.slice(3).join(" ") || null : null,
|
|
391
|
+
logCommand: false,
|
|
392
|
+
statusCommand: false,
|
|
393
|
+
commitCommand: false,
|
|
394
|
+
mergeCommand: false,
|
|
395
|
+
tagCommand: false,
|
|
419
396
|
releaseCommand: isReleaseCommand,
|
|
420
|
-
releaseAction: isReleaseCommand ? positional[
|
|
397
|
+
releaseAction: isReleaseCommand ? positional[0] ?? "status" : null,
|
|
421
398
|
addCommand: isAddCommand,
|
|
422
399
|
removeCommand: isRemoveCommand,
|
|
423
400
|
deleteCommand: isDeleteCommand,
|
|
@@ -431,7 +408,6 @@ export function parseArgs(argv, options = {}) {
|
|
|
431
408
|
hookName: isInstallHook ? positional[1] ?? "post-commit" : null,
|
|
432
409
|
actionPaths:
|
|
433
410
|
isAddCommand || isDeleteCommand ? positional.slice(1) : isRemoveHardCommand ? [] : isRemoveCommand ? positional.slice(1) : [],
|
|
434
|
-
connectToken: isConnectGitHub ? positional[0] : null,
|
|
435
411
|
stashIndex: isPopCommand ? positional[1] ?? null : null,
|
|
436
412
|
pullRemote: isPullCommand ? positional[1] ?? null : null,
|
|
437
413
|
pullBranch: isPullCommand ? positional[2] ?? null : null,
|
|
@@ -439,15 +415,8 @@ export function parseArgs(argv, options = {}) {
|
|
|
439
415
|
pushBranch: isPushCommand ? positional[2] ?? null : null,
|
|
440
416
|
commitRef:
|
|
441
417
|
isInstallHook ||
|
|
442
|
-
|
|
418
|
+
isConfigCommand ||
|
|
443
419
|
isNativeGitCommand ||
|
|
444
|
-
isConnectGitHub ||
|
|
445
|
-
isBoot ||
|
|
446
|
-
isLogCommand ||
|
|
447
|
-
isStatusCommand ||
|
|
448
|
-
isCommitCommand ||
|
|
449
|
-
isMergeCommand ||
|
|
450
|
-
isTagCommand ||
|
|
451
420
|
isReleaseCommand ||
|
|
452
421
|
isAddCommand ||
|
|
453
422
|
isRemoveCommand ||
|
|
@@ -475,6 +444,7 @@ export function parseArgs(argv, options = {}) {
|
|
|
475
444
|
quiet: flags.has("--quiet"),
|
|
476
445
|
execute: flags.has("--execute"),
|
|
477
446
|
dryRun: flags.has("--dry-run"),
|
|
447
|
+
release: flags.has("--release"),
|
|
478
448
|
log: flags.has("--log"),
|
|
479
449
|
status: flags.has("--status"),
|
|
480
450
|
merge: flags.has("--merge"),
|
|
@@ -563,15 +533,6 @@ function resolveTargetRef(parsed, cwd) {
|
|
|
563
533
|
return null;
|
|
564
534
|
}
|
|
565
535
|
|
|
566
|
-
export function buildBootSessionArgs(connection, userInfo, parsed) {
|
|
567
|
-
return {
|
|
568
|
-
token: connection.token,
|
|
569
|
-
providerOverride: parsed.provider,
|
|
570
|
-
modelOverride: parsed.model,
|
|
571
|
-
username: userInfo.name || connection.user?.login || null
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
536
|
function renderFinalOutput({ runtimeOptions, mode, commitData, explanation, responseMeta, promptMeta }) {
|
|
576
537
|
if (runtimeOptions.format === "json") {
|
|
577
538
|
return formatJsonOutput({ mode, commitData, explanation, responseMeta, promptMeta });
|
|
@@ -597,20 +558,20 @@ function renderFinalOutput({ runtimeOptions, mode, commitData, explanation, resp
|
|
|
597
558
|
|
|
598
559
|
export async function main(argv = process.argv) {
|
|
599
560
|
const cwd = process.cwd();
|
|
600
|
-
const config = loadConfig(cwd);
|
|
601
561
|
const parsed = parseArgs(argv);
|
|
602
562
|
const hasNoCommandOrFlags = argv.slice(2).length === 0;
|
|
603
563
|
|
|
604
564
|
loadEnvFile(cwd); // Ensure environment is loaded first
|
|
565
|
+
const config = loadConfig(cwd);
|
|
566
|
+
applyConfigEnvironment(config);
|
|
605
567
|
|
|
606
568
|
if (parsed.help || hasNoCommandOrFlags) {
|
|
607
569
|
printHelp();
|
|
608
570
|
return 0;
|
|
609
571
|
}
|
|
610
572
|
|
|
611
|
-
if (parsed.
|
|
612
|
-
|
|
613
|
-
return 0;
|
|
573
|
+
if (parsed.configCommand) {
|
|
574
|
+
return handleConfigCommand(parsed);
|
|
614
575
|
}
|
|
615
576
|
|
|
616
577
|
if (parsed.nativeGitCommand) {
|
|
@@ -628,62 +589,12 @@ export async function main(argv = process.argv) {
|
|
|
628
589
|
return 0;
|
|
629
590
|
}
|
|
630
591
|
|
|
631
|
-
if (parsed.
|
|
632
|
-
let token = parsed.connectToken;
|
|
633
|
-
if (!token) {
|
|
634
|
-
if (process.env.GITHUB_TOKEN) {
|
|
635
|
-
token = process.env.GITHUB_TOKEN;
|
|
636
|
-
} else {
|
|
637
|
-
console.error("Please provide your GitHub Personal Access Token.\nRun: gitxplain --connect-github <YOUR_TOKEN>\nOr set it in your .env as GITHUB_TOKEN=...");
|
|
638
|
-
return 1;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
try {
|
|
642
|
-
console.log("Verifying token with GitHub API...");
|
|
643
|
-
const userInfo = await verifyGitToken(token);
|
|
644
|
-
await saveGitConnection(token, "github", userInfo);
|
|
645
|
-
console.log(`\nSuccessfully connected to GitHub as: \x1b[36m${userInfo.login}\x1b[0m`);
|
|
646
|
-
console.log(`Token saved securely to your local configuration.\n`);
|
|
647
|
-
} catch (e) {
|
|
648
|
-
console.error(`Token verification failed: ${e.message}`);
|
|
649
|
-
return 1;
|
|
650
|
-
}
|
|
651
|
-
return 0;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (parsed.boot) {
|
|
655
|
-
if (!isGitConnected()) {
|
|
656
|
-
console.error("You must connect a GitHub account first to use the interactive agent.\nCommand: gitxplain --connect-github <YOUR_TOKEN>");
|
|
657
|
-
return 1;
|
|
658
|
-
}
|
|
659
|
-
const connection = loadGitConnection();
|
|
660
|
-
const userInfo = getGitUserInfo();
|
|
661
|
-
try {
|
|
662
|
-
const { getProviderConfig, validateProviderConfig } = await import(
|
|
663
|
-
"./services/aiService.js"
|
|
664
|
-
);
|
|
665
|
-
const config = getProviderConfig(parsed.provider, parsed.model);
|
|
666
|
-
validateProviderConfig(config);
|
|
667
|
-
const sessionArgs = buildBootSessionArgs(connection, userInfo, parsed);
|
|
668
|
-
await startChatSession(
|
|
669
|
-
sessionArgs.token,
|
|
670
|
-
sessionArgs.providerOverride,
|
|
671
|
-
sessionArgs.modelOverride,
|
|
672
|
-
sessionArgs.username
|
|
673
|
-
);
|
|
674
|
-
} catch (configError) {
|
|
675
|
-
console.error(`Missing LLM Key. Please check your .env variables or --provider flags.\n${configError.message}`);
|
|
676
|
-
return 1;
|
|
677
|
-
}
|
|
678
|
-
return 0;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (parsed.logCommand || parsed.log) {
|
|
592
|
+
if (parsed.log) {
|
|
682
593
|
console.log(getRepositoryLog(cwd));
|
|
683
594
|
return 0;
|
|
684
595
|
}
|
|
685
596
|
|
|
686
|
-
if (parsed.
|
|
597
|
+
if (parsed.status) {
|
|
687
598
|
console.log(getRepositoryStatus(cwd));
|
|
688
599
|
return 0;
|
|
689
600
|
}
|
|
@@ -827,7 +738,7 @@ export async function main(argv = process.argv) {
|
|
|
827
738
|
return 0;
|
|
828
739
|
}
|
|
829
740
|
|
|
830
|
-
if (mode === "commit"
|
|
741
|
+
if (mode === "commit") {
|
|
831
742
|
const commitData = fetchWorkingTreeData(cwd);
|
|
832
743
|
|
|
833
744
|
if (commitData.filesChanged.length === 0 || commitData.diff === "") {
|
|
@@ -877,7 +788,7 @@ export async function main(argv = process.argv) {
|
|
|
877
788
|
return 0;
|
|
878
789
|
}
|
|
879
790
|
|
|
880
|
-
if (mode === "merge" || parsed.
|
|
791
|
+
if (mode === "merge" || parsed.merge) {
|
|
881
792
|
if (parsed.commitRef) {
|
|
882
793
|
throw new Error("--merge works from the current branch and does not accept a commit ref.");
|
|
883
794
|
}
|
|
@@ -909,7 +820,7 @@ export async function main(argv = process.argv) {
|
|
|
909
820
|
return 0;
|
|
910
821
|
}
|
|
911
822
|
|
|
912
|
-
if (mode === "tag" || parsed.
|
|
823
|
+
if (mode === "tag" || parsed.tag) {
|
|
913
824
|
if (parsed.commitRef) {
|
|
914
825
|
throw new Error("--tag works from the current branch and does not accept a commit ref.");
|
|
915
826
|
}
|