gitxelectron 1.2.3 → 1.2.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/out/main/index.js +229 -6
- package/out/preload/index.js +10 -0
- package/out/renderer/assets/index-Bl7RyoCQ.css +1337 -0
- package/out/renderer/assets/{index-C9VJOfEp.js → index-Bzibm93j.js} +338 -46
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/out/renderer/assets/index-iCHfrYpB.css +0 -193
package/out/main/index.js
CHANGED
|
@@ -727,7 +727,7 @@ async function stageFiles(sessionId, files) {
|
|
|
727
727
|
const chunkSize = 50;
|
|
728
728
|
for (let i = 0; i < fileArray.length; i += chunkSize) {
|
|
729
729
|
const chunk = fileArray.slice(i, i + chunkSize);
|
|
730
|
-
await git.add(chunk);
|
|
730
|
+
await git.add(["--force", ...chunk]);
|
|
731
731
|
}
|
|
732
732
|
return true;
|
|
733
733
|
}
|
|
@@ -1348,6 +1348,93 @@ ${errorMsg}
|
|
|
1348
1348
|
`AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
|
|
1349
1349
|
);
|
|
1350
1350
|
}
|
|
1351
|
+
const AUTO_SYNC_SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitQuickPush" app.
|
|
1352
|
+
The user's Auto-Push process failed (likely due to remote changes or a merge conflict).
|
|
1353
|
+
You will receive the Git Status, the Raw Error Message, and optionally the contents of any Conflicted Files.
|
|
1354
|
+
|
|
1355
|
+
Your job is to analyze the issue and return a JSON object with this exact shape:
|
|
1356
|
+
{
|
|
1357
|
+
"reason": "A clear, user-friendly explanation of why the push failed.",
|
|
1358
|
+
"proposedFix": "A clear explanation of what specific changes you will make to fix it (e.g. 'I will run git pull --rebase and resolve the merge conflict in app.js by keeping both changes.')",
|
|
1359
|
+
"resolvedCode": {
|
|
1360
|
+
"path/to/file.ts": "THE_FULL_RESOLVED_FILE_CONTENT_HERE"
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
Guidelines:
|
|
1365
|
+
- If there are conflicted files provided, you MUST include the "resolvedCode" object. The keys must be the exact file paths, and the values must be the ENTIRE file content with all \`<<<<<<< HEAD\` conflict markers cleanly resolved.
|
|
1366
|
+
- If there are no conflicted files (e.g. just a standard rejection that a pull will fix), omit "resolvedCode".
|
|
1367
|
+
- Return ONLY valid JSON. No markdown code block wrappers.`;
|
|
1368
|
+
async function analyzePushErrorForAutoSync(provider, apiKeyInput, errorMsg, gitStatus, _repoPath, conflictedFilesContext, githubToken) {
|
|
1369
|
+
if (!apiKeyInput && provider === "gemini") {
|
|
1370
|
+
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
1371
|
+
}
|
|
1372
|
+
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
1373
|
+
let prompt = `
|
|
1374
|
+
GIT STATUS:
|
|
1375
|
+
${JSON.stringify(gitStatus, null, 2)}
|
|
1376
|
+
|
|
1377
|
+
RAW ERROR MESSAGE:
|
|
1378
|
+
${errorMsg}
|
|
1379
|
+
`;
|
|
1380
|
+
if (conflictedFilesContext && Object.keys(conflictedFilesContext).length > 0) {
|
|
1381
|
+
prompt += `
|
|
1382
|
+
CONFLICTED FILES CONTENT (Resolve these markers):
|
|
1383
|
+
`;
|
|
1384
|
+
for (const [filePath, content] of Object.entries(conflictedFilesContext)) {
|
|
1385
|
+
prompt += `
|
|
1386
|
+
--- FILE: ${filePath} ---
|
|
1387
|
+
${content}
|
|
1388
|
+
--------------------------
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
let lastError = null;
|
|
1393
|
+
if (githubToken) {
|
|
1394
|
+
try {
|
|
1395
|
+
const openai = new OpenAI({
|
|
1396
|
+
apiKey: githubToken,
|
|
1397
|
+
baseURL: "https://models.inference.ai.azure.com"
|
|
1398
|
+
});
|
|
1399
|
+
const response = await openai.chat.completions.create({
|
|
1400
|
+
model: "gpt-4o-mini",
|
|
1401
|
+
messages: [
|
|
1402
|
+
{ role: "system", content: AUTO_SYNC_SYSTEM_PROMPT },
|
|
1403
|
+
{ role: "user", content: prompt }
|
|
1404
|
+
],
|
|
1405
|
+
response_format: { type: "json_object" }
|
|
1406
|
+
});
|
|
1407
|
+
let responseText = response.choices[0].message.content || "{}";
|
|
1408
|
+
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
1409
|
+
const parsed = JSON.parse(responseText);
|
|
1410
|
+
if (parsed.reason && parsed.proposedFix) {
|
|
1411
|
+
return parsed;
|
|
1412
|
+
}
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
lastError = e;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const apiKey of keys) {
|
|
1418
|
+
let responseText = "";
|
|
1419
|
+
try {
|
|
1420
|
+
if (provider === "gemini") {
|
|
1421
|
+
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
1422
|
+
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
1423
|
+
const result = await model.generateContent([{ text: AUTO_SYNC_SYSTEM_PROMPT }, { text: prompt }]);
|
|
1424
|
+
responseText = result.response.text();
|
|
1425
|
+
}
|
|
1426
|
+
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
1427
|
+
const parsed = JSON.parse(responseText);
|
|
1428
|
+
if (!parsed.reason || !parsed.proposedFix) {
|
|
1429
|
+
throw new Error("AI returned an invalid response format.");
|
|
1430
|
+
}
|
|
1431
|
+
return parsed;
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
lastError = e;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
throw new Error(`AI Analysis failed. Last error: ${lastError?.message}`);
|
|
1437
|
+
}
|
|
1351
1438
|
async function generateCommitMessage(provider, apiKeyInput, diffStr, githubToken) {
|
|
1352
1439
|
const systemPrompt = `You are an expert developer. Generate a concise, clear Git commit message based on the provided git diff.
|
|
1353
1440
|
Format your response exactly like a standard Git commit message:
|
|
@@ -1426,10 +1513,17 @@ const getLastWorkspace = async () => {
|
|
|
1426
1513
|
let currentWatcher = null;
|
|
1427
1514
|
let syncTimeout = null;
|
|
1428
1515
|
let isAutoSyncEnabled = false;
|
|
1516
|
+
let isResolving = false;
|
|
1517
|
+
let globalOnLog = null;
|
|
1518
|
+
let globalOnErrorApproval = null;
|
|
1519
|
+
let globalOnResolveComplete = null;
|
|
1429
1520
|
const DEBOUNCE_MS = 3e3;
|
|
1430
1521
|
const changedFiles = /* @__PURE__ */ new Set();
|
|
1431
|
-
function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
|
|
1522
|
+
function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete) {
|
|
1432
1523
|
isAutoSyncEnabled = enabled;
|
|
1524
|
+
globalOnLog = onLog;
|
|
1525
|
+
if (onErrorApproval) globalOnErrorApproval = onErrorApproval;
|
|
1526
|
+
if (onResolveComplete) globalOnResolveComplete = onResolveComplete;
|
|
1433
1527
|
if (currentWatcher) {
|
|
1434
1528
|
currentWatcher.close();
|
|
1435
1529
|
currentWatcher = null;
|
|
@@ -1457,7 +1551,7 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
|
|
|
1457
1551
|
ignoreInitial: true
|
|
1458
1552
|
});
|
|
1459
1553
|
currentWatcher.on("all", (_event, filePath) => {
|
|
1460
|
-
if (!isAutoSyncEnabled) return;
|
|
1554
|
+
if (!isAutoSyncEnabled || isResolving) return;
|
|
1461
1555
|
const relativePath = path__namespace.relative(repoPath, filePath).replace(/\\/g, "/");
|
|
1462
1556
|
changedFiles.add(relativePath);
|
|
1463
1557
|
if (syncTimeout) {
|
|
@@ -1504,8 +1598,120 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
1504
1598
|
onLog(`✅ Successfully pushed auto-sync commit!`);
|
|
1505
1599
|
} catch (error) {
|
|
1506
1600
|
onLog(`❌ Auto-sync failed: ${error.message}`);
|
|
1601
|
+
if (globalOnErrorApproval && !isResolving) {
|
|
1602
|
+
isResolving = true;
|
|
1603
|
+
onLog("Attempting to resolve push failure...");
|
|
1604
|
+
handleAutoSyncError(sessionId, error.message, onLog).catch(
|
|
1605
|
+
(err) => onLog(`Resolution failed: ${err.message}`)
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
1611
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1612
|
+
if (!repoPath) {
|
|
1613
|
+
isResolving = false;
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
onLog("Pulling remote changes with rebase...");
|
|
1618
|
+
await git.pull("origin", void 0, { "--rebase": "true" });
|
|
1619
|
+
onLog("Pull successful, attempting push again...");
|
|
1620
|
+
const status2 = await getStatus(sessionId);
|
|
1621
|
+
await pushToRemote(sessionId, "origin", status2.current || "main");
|
|
1622
|
+
onLog("✅ Error resolved and push resumed!");
|
|
1623
|
+
isResolving = false;
|
|
1624
|
+
if (globalOnResolveComplete) globalOnResolveComplete();
|
|
1625
|
+
return;
|
|
1626
|
+
} catch (pullErr) {
|
|
1627
|
+
onLog(`Pull failed (possible conflict): ${pullErr.message}`);
|
|
1628
|
+
}
|
|
1629
|
+
const status = await getStatus(sessionId);
|
|
1630
|
+
if (status.conflicted.length === 0) {
|
|
1631
|
+
onLog("No conflicts found but resolution failed. Giving up.");
|
|
1632
|
+
isResolving = false;
|
|
1633
|
+
if (globalOnErrorApproval) {
|
|
1634
|
+
globalOnErrorApproval({
|
|
1635
|
+
reason: "Failed to resolve error automatically. No file conflicts found.",
|
|
1636
|
+
proposedFix: "Manual intervention required.",
|
|
1637
|
+
files: []
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
onLog("Analyzing conflicts with AI...");
|
|
1643
|
+
const conflictedFilesContext = {};
|
|
1644
|
+
for (const f of status.conflicted) {
|
|
1645
|
+
try {
|
|
1646
|
+
const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
|
|
1647
|
+
conflictedFilesContext[f] = content;
|
|
1648
|
+
} catch (e) {
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
const githubToken = getToken(sessionId) || void 0;
|
|
1653
|
+
const aiResponse = await analyzePushErrorForAutoSync(
|
|
1654
|
+
"gemini",
|
|
1655
|
+
"",
|
|
1656
|
+
errorMsg,
|
|
1657
|
+
status,
|
|
1658
|
+
repoPath,
|
|
1659
|
+
conflictedFilesContext,
|
|
1660
|
+
githubToken
|
|
1661
|
+
);
|
|
1662
|
+
if (globalOnErrorApproval) {
|
|
1663
|
+
globalOnErrorApproval({
|
|
1664
|
+
reason: aiResponse.reason,
|
|
1665
|
+
proposedFix: aiResponse.proposedFix,
|
|
1666
|
+
files: status.conflicted,
|
|
1667
|
+
resolvedCode: aiResponse.resolvedCode,
|
|
1668
|
+
sessionId
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
onLog(`AI analysis failed: ${err.message}`);
|
|
1673
|
+
isResolving = false;
|
|
1674
|
+
if (globalOnErrorApproval) {
|
|
1675
|
+
globalOnErrorApproval({
|
|
1676
|
+
reason: "AI analysis failed.",
|
|
1677
|
+
proposedFix: "Manual intervention required.",
|
|
1678
|
+
files: status.conflicted
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1507
1681
|
}
|
|
1508
1682
|
}
|
|
1683
|
+
async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
1684
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1685
|
+
if (!repoPath) return false;
|
|
1686
|
+
if (globalOnLog) globalOnLog("Applying AI resolution to conflicted files...");
|
|
1687
|
+
try {
|
|
1688
|
+
if (resolvedCode) {
|
|
1689
|
+
for (const [file, content] of Object.entries(resolvedCode)) {
|
|
1690
|
+
await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
|
|
1691
|
+
await git.add(["--force", file]);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
await git.rebase({ "--continue": null });
|
|
1696
|
+
} catch (e) {
|
|
1697
|
+
await git.commit("Auto-resolved merge conflict by AI");
|
|
1698
|
+
}
|
|
1699
|
+
const status = await getStatus(sessionId);
|
|
1700
|
+
if (globalOnLog) globalOnLog("Pushing AI-resolved changes...");
|
|
1701
|
+
await pushToRemote(sessionId, "origin", status.current || "main");
|
|
1702
|
+
if (globalOnLog) globalOnLog("✅ Push successful!");
|
|
1703
|
+
isResolving = false;
|
|
1704
|
+
if (globalOnResolveComplete) globalOnResolveComplete();
|
|
1705
|
+
return true;
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
if (globalOnLog) globalOnLog(`AI resolution failed: ${err.message}`);
|
|
1708
|
+
isResolving = false;
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
function cancelAiFix() {
|
|
1713
|
+
isResolving = false;
|
|
1714
|
+
}
|
|
1509
1715
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
1510
1716
|
function createWindow(initialPath) {
|
|
1511
1717
|
const mainWindow = new electron.BrowserWindow({
|
|
@@ -1863,9 +2069,26 @@ electron.app.whenReady().then(async () => {
|
|
|
1863
2069
|
return { success: true, count: copiedFiles.length, files: copiedFiles };
|
|
1864
2070
|
});
|
|
1865
2071
|
electron.ipcMain.on("toggle-auto-sync", (event, repoPath, enabled) => {
|
|
1866
|
-
toggleAutoSync(
|
|
1867
|
-
event.sender.
|
|
1868
|
-
|
|
2072
|
+
toggleAutoSync(
|
|
2073
|
+
event.sender.id,
|
|
2074
|
+
repoPath,
|
|
2075
|
+
enabled,
|
|
2076
|
+
(msg) => {
|
|
2077
|
+
event.sender.send("auto-sync-log", msg);
|
|
2078
|
+
},
|
|
2079
|
+
(payload) => {
|
|
2080
|
+
event.sender.send("auto-sync-error-approval", payload);
|
|
2081
|
+
},
|
|
2082
|
+
() => {
|
|
2083
|
+
event.sender.send("auto-sync-resolved");
|
|
2084
|
+
}
|
|
2085
|
+
);
|
|
2086
|
+
});
|
|
2087
|
+
electron.ipcMain.handle("approve-ai-fix", async (event, resolvedCode) => {
|
|
2088
|
+
return await applyAiFixAndResume(event.sender.id, resolvedCode);
|
|
2089
|
+
});
|
|
2090
|
+
electron.ipcMain.on("reject-ai-fix", () => {
|
|
2091
|
+
cancelAiFix();
|
|
1869
2092
|
});
|
|
1870
2093
|
electron.ipcMain.handle("get-app-version", () => {
|
|
1871
2094
|
return electron.app.getVersion();
|
package/out/preload/index.js
CHANGED
|
@@ -37,6 +37,8 @@ const api = {
|
|
|
37
37
|
setGitConfig: (key, value, global = false) => electron.ipcRenderer.invoke("set-git-config", key, value, global),
|
|
38
38
|
readFile: (file) => electron.ipcRenderer.invoke("read-file", file),
|
|
39
39
|
writeFile: (file, content) => electron.ipcRenderer.invoke("write-file", file, content),
|
|
40
|
+
approveAiFix: (resolvedCode) => electron.ipcRenderer.invoke("approve-ai-fix", resolvedCode),
|
|
41
|
+
rejectAiFix: () => electron.ipcRenderer.send("reject-ai-fix"),
|
|
40
42
|
executeRawGit: (args) => electron.ipcRenderer.invoke("execute-raw-git", args),
|
|
41
43
|
// NEW: Multi-Account & Window
|
|
42
44
|
newWindow: (repoPath) => electron.ipcRenderer.invoke("new-window", repoPath),
|
|
@@ -83,6 +85,14 @@ const api = {
|
|
|
83
85
|
electron.ipcRenderer.removeAllListeners("auto-sync-log");
|
|
84
86
|
electron.ipcRenderer.on("auto-sync-log", (_event, msg) => callback(msg));
|
|
85
87
|
},
|
|
88
|
+
onAutoSyncErrorApproval: (callback) => {
|
|
89
|
+
electron.ipcRenderer.removeAllListeners("auto-sync-error-approval");
|
|
90
|
+
electron.ipcRenderer.on("auto-sync-error-approval", (_event, payload) => callback(payload));
|
|
91
|
+
},
|
|
92
|
+
onAutoSyncResolved: (callback) => {
|
|
93
|
+
electron.ipcRenderer.removeAllListeners("auto-sync-resolved");
|
|
94
|
+
electron.ipcRenderer.on("auto-sync-resolved", () => callback());
|
|
95
|
+
},
|
|
86
96
|
getAppVersion: () => electron.ipcRenderer.invoke("get-app-version"),
|
|
87
97
|
// AI Fixer API
|
|
88
98
|
askAi: (provider, apiKey, errorMsg, gitStatus, repoPath) => electron.ipcRenderer.invoke("ask-ai", provider, apiKey, errorMsg, gitStatus, repoPath),
|