gitxelectron 1.3.9 → 1.4.1
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/bin/ai-setup.js +46 -0
- package/bin/cli.js +35 -13
- package/out/main/index.js +240 -84
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/index-CvICUEsw.css +2989 -0
- package/out/renderer/assets/{index-Dtxyy2BB.js → index-DypwT1IG.js} +1463 -500
- package/out/renderer/index.html +2 -2
- package/package.json +2 -1
- package/out/renderer/assets/index-Bhk4vMRr.css +0 -2989
package/out/main/index.js
CHANGED
|
@@ -111,8 +111,24 @@ async function logout(sessionId) {
|
|
|
111
111
|
}
|
|
112
112
|
async function getAuthStatus(sessionId) {
|
|
113
113
|
const activeAccount = getActiveAccount(sessionId);
|
|
114
|
-
if (!activeAccount)
|
|
115
|
-
|
|
114
|
+
if (!activeAccount) {
|
|
115
|
+
return {
|
|
116
|
+
status: "Not Authenticated",
|
|
117
|
+
username: null,
|
|
118
|
+
avatarUrl: null,
|
|
119
|
+
authMethod: "Local Git Credentials",
|
|
120
|
+
aiProvider: "GitHub Models",
|
|
121
|
+
securityStatus: "Encrypted Local Keystore"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
status: "Verified",
|
|
126
|
+
username: activeAccount.username,
|
|
127
|
+
avatarUrl: activeAccount.avatar_url || null,
|
|
128
|
+
authMethod: "GitHub OAuth (Verified)",
|
|
129
|
+
aiProvider: "GitHub Models (gpt-4o-mini)",
|
|
130
|
+
securityStatus: "End-to-End Encrypted Session"
|
|
131
|
+
};
|
|
116
132
|
}
|
|
117
133
|
async function startDeviceFlow(_sessionId) {
|
|
118
134
|
const response = await fetch("https://github.com/login/device/code", {
|
|
@@ -601,7 +617,7 @@ function initGit(sessionId, baseDir) {
|
|
|
601
617
|
try {
|
|
602
618
|
const wc = electron.webContents.fromId(sessionId);
|
|
603
619
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
604
|
-
} catch
|
|
620
|
+
} catch {
|
|
605
621
|
}
|
|
606
622
|
}
|
|
607
623
|
};
|
|
@@ -642,7 +658,7 @@ function createSafeGit(baseDir, sessionId) {
|
|
|
642
658
|
try {
|
|
643
659
|
const wc = electron.webContents.fromId(sessionId);
|
|
644
660
|
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
645
|
-
} catch
|
|
661
|
+
} catch {
|
|
646
662
|
}
|
|
647
663
|
}
|
|
648
664
|
};
|
|
@@ -701,9 +717,9 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
701
717
|
let existing = "";
|
|
702
718
|
try {
|
|
703
719
|
existing = await fs__namespace.readFile(attrsPath, "utf8");
|
|
704
|
-
} catch
|
|
720
|
+
} catch {
|
|
705
721
|
}
|
|
706
|
-
|
|
722
|
+
const newLines = [];
|
|
707
723
|
for (const ext of DEFAULT_LFS_EXTENSIONS) {
|
|
708
724
|
if (!existing.includes(ext)) {
|
|
709
725
|
newLines.push(`${ext} filter=lfs diff=lfs merge=lfs -text`);
|
|
@@ -713,7 +729,7 @@ async function setupDefaultGitAttributes(repoPath) {
|
|
|
713
729
|
const appendContent = (existing && !existing.endsWith("\n") ? "\n" : "") + newLines.join("\n") + "\n";
|
|
714
730
|
await fs__namespace.appendFile(attrsPath, appendContent);
|
|
715
731
|
}
|
|
716
|
-
} catch
|
|
732
|
+
} catch {
|
|
717
733
|
}
|
|
718
734
|
}
|
|
719
735
|
async function isGitRepo(dir) {
|
|
@@ -843,6 +859,7 @@ async function getStatus(sessionId) {
|
|
|
843
859
|
async function stageFiles(sessionId, files) {
|
|
844
860
|
const { git, repoPath } = getGitSession(sessionId);
|
|
845
861
|
const fileArray = Array.isArray(files) ? files : [files];
|
|
862
|
+
await ensureAiWeightsUseLfs(git);
|
|
846
863
|
if (repoPath) {
|
|
847
864
|
const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
|
|
848
865
|
const largeFiles = [];
|
|
@@ -853,7 +870,7 @@ async function stageFiles(sessionId, files) {
|
|
|
853
870
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
854
871
|
largeFiles.push(file);
|
|
855
872
|
}
|
|
856
|
-
} catch
|
|
873
|
+
} catch {
|
|
857
874
|
}
|
|
858
875
|
}
|
|
859
876
|
if (largeFiles.length > 0) {
|
|
@@ -907,6 +924,7 @@ async function unstageFiles(sessionId, files) {
|
|
|
907
924
|
}
|
|
908
925
|
async function commitChanges(sessionId, message) {
|
|
909
926
|
const { git } = getGitSession(sessionId);
|
|
927
|
+
await ensureAiWeightsUseLfs(git);
|
|
910
928
|
try {
|
|
911
929
|
const nameConfig = await git.getConfig("user.name");
|
|
912
930
|
const emailConfig = await git.getConfig("user.email");
|
|
@@ -936,7 +954,7 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
936
954
|
parsed.password = token;
|
|
937
955
|
return parsed.toString();
|
|
938
956
|
}
|
|
939
|
-
} catch
|
|
957
|
+
} catch {
|
|
940
958
|
if (remoteUrl.includes("github.com")) {
|
|
941
959
|
return remoteUrl.replace(
|
|
942
960
|
/https:\/\/(?:[^@]+@)?github\.com\//,
|
|
@@ -947,7 +965,8 @@ function injectAuthToken(sessionId, remoteUrl) {
|
|
|
947
965
|
return remoteUrl;
|
|
948
966
|
}
|
|
949
967
|
async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3, onProgress, onDetailedProgress) {
|
|
950
|
-
const { repoPath } = getGitSession(sessionId);
|
|
968
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
969
|
+
await ensureAiWeightsUseLfs(git);
|
|
951
970
|
if (onProgress) onProgress("Starting rapid file count to prepare massive chunking...");
|
|
952
971
|
const totalFiles = await new Promise((resolve) => {
|
|
953
972
|
const child2 = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
|
|
@@ -965,7 +984,6 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
965
984
|
return true;
|
|
966
985
|
}
|
|
967
986
|
if (onProgress) onProgress(`Total files to push: ${totalFiles}. Starting chunked upload...`);
|
|
968
|
-
const { git } = getGitSession(sessionId);
|
|
969
987
|
const remotes = await getRemotes(sessionId);
|
|
970
988
|
const remoteObj = remotes.find((r) => r.name === remote);
|
|
971
989
|
if (onProgress) onProgress("Starting massive repository push...");
|
|
@@ -1018,7 +1036,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1018
1036
|
if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
|
|
1019
1037
|
largeFiles.push(file);
|
|
1020
1038
|
}
|
|
1021
|
-
} catch
|
|
1039
|
+
} catch {
|
|
1022
1040
|
}
|
|
1023
1041
|
})
|
|
1024
1042
|
);
|
|
@@ -1055,7 +1073,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1055
1073
|
} finally {
|
|
1056
1074
|
try {
|
|
1057
1075
|
await fs__namespace.unlink(tempFile);
|
|
1058
|
-
} catch
|
|
1076
|
+
} catch {
|
|
1059
1077
|
}
|
|
1060
1078
|
}
|
|
1061
1079
|
};
|
|
@@ -1083,7 +1101,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1083
1101
|
if (onProgress) onProgress("Committing all files...");
|
|
1084
1102
|
try {
|
|
1085
1103
|
await git.commit(`Auto-sync (Massive Push)`);
|
|
1086
|
-
} catch
|
|
1104
|
+
} catch {
|
|
1087
1105
|
}
|
|
1088
1106
|
if (onProgress) onProgress("Pushing to remote...");
|
|
1089
1107
|
const pushArgs = [
|
|
@@ -1108,7 +1126,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
|
|
|
1108
1126
|
} catch (err) {
|
|
1109
1127
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1110
1128
|
if (onProgress) onProgress("Remote branch is ahead. Pulling changes...");
|
|
1111
|
-
await git.pull(authUrl, branch, { "--rebase":
|
|
1129
|
+
await git.pull(authUrl, branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1112
1130
|
if (onProgress) onProgress("Pushing to remote after pull...");
|
|
1113
1131
|
await git.raw([...pushArgs, authUrl, branch]);
|
|
1114
1132
|
} else {
|
|
@@ -1162,7 +1180,7 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1162
1180
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1163
1181
|
console.log("Push rejected because branch is behind. Auto-pulling with rebase...");
|
|
1164
1182
|
const { git } = getGitSession(sessionId);
|
|
1165
|
-
const options = { "--rebase":
|
|
1183
|
+
const options = { "--rebase": "false", "--autostash": null, "-X": "ours" };
|
|
1166
1184
|
if (remoteObj && remoteObj.refs.fetch) {
|
|
1167
1185
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
|
|
1168
1186
|
await git.pull(authUrl, branch, options);
|
|
@@ -1175,10 +1193,40 @@ async function pushToRemote(sessionId, remote, branch) {
|
|
|
1175
1193
|
}
|
|
1176
1194
|
}
|
|
1177
1195
|
async function masterForcePush(sessionId, remote, branch) {
|
|
1178
|
-
const { git } = getGitSession(sessionId);
|
|
1196
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1179
1197
|
const remotes = await getRemotes(sessionId);
|
|
1180
|
-
const remoteObj = remotes.find((r) => r.name === remote);
|
|
1181
|
-
|
|
1198
|
+
const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
|
|
1199
|
+
try {
|
|
1200
|
+
const branchSummary = await git.branchLocal();
|
|
1201
|
+
let current = branchSummary.current || branch;
|
|
1202
|
+
if (current && current.startsWith("__master_push_temp_")) {
|
|
1203
|
+
const candidates = ["main", "master", branch].filter(
|
|
1204
|
+
(b) => b && !b.startsWith("__master_push_temp_") && branchSummary.all.includes(b)
|
|
1205
|
+
);
|
|
1206
|
+
const fallbackBranch = candidates[0] || "main";
|
|
1207
|
+
try {
|
|
1208
|
+
await git.checkout(fallbackBranch);
|
|
1209
|
+
current = fallbackBranch;
|
|
1210
|
+
branch = fallbackBranch;
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
for (const b of branchSummary.all) {
|
|
1215
|
+
if (b.startsWith("__master_push_temp_") && b !== current) {
|
|
1216
|
+
try {
|
|
1217
|
+
await git.deleteLocalBranch(b, true);
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} catch {
|
|
1223
|
+
}
|
|
1224
|
+
await ensureAiWeightsUseLfs(git);
|
|
1225
|
+
try {
|
|
1226
|
+
await git.add(".");
|
|
1227
|
+
} catch {
|
|
1228
|
+
await git.raw(["add", "-A"]);
|
|
1229
|
+
}
|
|
1182
1230
|
try {
|
|
1183
1231
|
const name = await git.getConfig("user.name");
|
|
1184
1232
|
if (!name.value) await git.addConfig("user.name", "GitElectron User");
|
|
@@ -1188,34 +1236,40 @@ async function masterForcePush(sessionId, remote, branch) {
|
|
|
1188
1236
|
console.warn("Failed to ensure git identity before master push:", e);
|
|
1189
1237
|
}
|
|
1190
1238
|
try {
|
|
1191
|
-
await git.commit("Master Force Push -
|
|
1239
|
+
await git.commit("Master Force Push - Sync changes");
|
|
1192
1240
|
} catch (e) {
|
|
1193
1241
|
if (!e.message.includes("nothing to commit")) {
|
|
1194
|
-
|
|
1242
|
+
console.warn("Commit note:", e.message);
|
|
1195
1243
|
}
|
|
1196
1244
|
}
|
|
1197
1245
|
const tempGit = simpleGit.simpleGit({
|
|
1198
|
-
baseDir:
|
|
1246
|
+
baseDir: repoPath,
|
|
1199
1247
|
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
1200
1248
|
}).env({
|
|
1201
1249
|
...process.env,
|
|
1202
1250
|
GCM_INTERACTIVE: "false",
|
|
1203
1251
|
GIT_TERMINAL_PROMPT: "0",
|
|
1204
|
-
GIT_ASKPASS: "echo"
|
|
1252
|
+
GIT_ASKPASS: "echo",
|
|
1253
|
+
GIT_LFS_SKIP_SMUDGE: "1",
|
|
1254
|
+
GIT_LFS_SKIP_PUSH: "1"
|
|
1205
1255
|
});
|
|
1206
1256
|
const pushArgs = [
|
|
1207
1257
|
"-c",
|
|
1208
1258
|
"credential.helper=",
|
|
1259
|
+
"-c",
|
|
1260
|
+
"filter.lfs.required=false",
|
|
1261
|
+
"-c",
|
|
1262
|
+
"lfs.allowincompletepush=true",
|
|
1209
1263
|
"push",
|
|
1210
1264
|
"--progress",
|
|
1211
1265
|
"--no-verify",
|
|
1212
1266
|
"-f"
|
|
1213
1267
|
];
|
|
1214
|
-
if (remoteObj && remoteObj.refs
|
|
1268
|
+
if (remoteObj && remoteObj.refs?.push) {
|
|
1215
1269
|
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1216
1270
|
return await tempGit.raw([...pushArgs, authUrl, branch]);
|
|
1217
1271
|
}
|
|
1218
|
-
return await tempGit.raw([...pushArgs, remote, branch]);
|
|
1272
|
+
return await tempGit.raw([...pushArgs, remote || "origin", branch]);
|
|
1219
1273
|
}
|
|
1220
1274
|
async function pullFromRemote(sessionId, remote, branch) {
|
|
1221
1275
|
const { git, repoPath } = getGitSession(sessionId);
|
|
@@ -1280,6 +1334,8 @@ async function pullFromRemote(sessionId, remote, branch) {
|
|
|
1280
1334
|
"pull",
|
|
1281
1335
|
"--no-rebase",
|
|
1282
1336
|
"--allow-unrelated-histories",
|
|
1337
|
+
"-X",
|
|
1338
|
+
"ours",
|
|
1283
1339
|
authUrl,
|
|
1284
1340
|
targetBranch
|
|
1285
1341
|
];
|
|
@@ -1439,7 +1495,7 @@ async function getBranches(sessionId) {
|
|
|
1439
1495
|
)
|
|
1440
1496
|
]);
|
|
1441
1497
|
}
|
|
1442
|
-
} catch
|
|
1498
|
+
} catch {
|
|
1443
1499
|
}
|
|
1444
1500
|
let branches = await git.branch(["-a"]);
|
|
1445
1501
|
if (branches.all.length === 0) {
|
|
@@ -1461,10 +1517,14 @@ async function getBranches(sessionId) {
|
|
|
1461
1517
|
}
|
|
1462
1518
|
const allBranches = /* @__PURE__ */ new Set();
|
|
1463
1519
|
branches.all.forEach((b) => {
|
|
1520
|
+
if (b.includes("__master_push_temp_")) return;
|
|
1464
1521
|
if (b.startsWith("remotes/")) {
|
|
1465
1522
|
const parts = b.split("/");
|
|
1466
1523
|
if (parts.length > 2) {
|
|
1467
|
-
|
|
1524
|
+
const branchName = parts.slice(2).join("/");
|
|
1525
|
+
if (!branchName.includes("__master_push_temp_")) {
|
|
1526
|
+
allBranches.add(branchName);
|
|
1527
|
+
}
|
|
1468
1528
|
}
|
|
1469
1529
|
} else {
|
|
1470
1530
|
allBranches.add(b);
|
|
@@ -1551,15 +1611,11 @@ async function pushAllBranches(sessionId, remote) {
|
|
|
1551
1611
|
GIT_TERMINAL_PROMPT: "0",
|
|
1552
1612
|
GIT_ASKPASS: "echo"
|
|
1553
1613
|
});
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
|
|
1558
|
-
}
|
|
1559
|
-
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1560
|
-
} catch (err) {
|
|
1561
|
-
throw err;
|
|
1614
|
+
if (remoteObj && remoteObj.refs.push) {
|
|
1615
|
+
const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
|
|
1616
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
|
|
1562
1617
|
}
|
|
1618
|
+
return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
|
|
1563
1619
|
}
|
|
1564
1620
|
async function getCommitHistory(sessionId) {
|
|
1565
1621
|
const { git } = getGitSession(sessionId);
|
|
@@ -1577,17 +1633,27 @@ async function getCommitHistory(sessionId) {
|
|
|
1577
1633
|
`+refs/heads/*:refs/remotes/${remoteObj.name}/*`
|
|
1578
1634
|
]);
|
|
1579
1635
|
}
|
|
1580
|
-
} catch
|
|
1636
|
+
} catch {
|
|
1637
|
+
}
|
|
1638
|
+
try {
|
|
1639
|
+
const log = await git.log(["--all", "--stat", "--root", "--cc"]);
|
|
1640
|
+
return JSON.parse(JSON.stringify(log));
|
|
1641
|
+
} catch {
|
|
1642
|
+
try {
|
|
1643
|
+
const log = await git.log(["--all", "--stat", "--root"]);
|
|
1644
|
+
return JSON.parse(JSON.stringify(log));
|
|
1645
|
+
} catch {
|
|
1646
|
+
const log = await git.log(["--all", "--name-status"]);
|
|
1647
|
+
return JSON.parse(JSON.stringify(log));
|
|
1648
|
+
}
|
|
1581
1649
|
}
|
|
1582
|
-
const log = await git.log(["--all", "--name-status"]);
|
|
1583
|
-
return JSON.parse(JSON.stringify(log));
|
|
1584
1650
|
}
|
|
1585
1651
|
async function getDeletedHistory(sessionId) {
|
|
1586
1652
|
const { git } = getGitSession(sessionId);
|
|
1587
1653
|
try {
|
|
1588
1654
|
const log = await git.log(["--all", "--diff-filter=D", "--name-status", "-n", "20"]);
|
|
1589
1655
|
return JSON.parse(JSON.stringify(log));
|
|
1590
|
-
} catch
|
|
1656
|
+
} catch {
|
|
1591
1657
|
return { all: [] };
|
|
1592
1658
|
}
|
|
1593
1659
|
}
|
|
@@ -1681,7 +1747,7 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
|
|
|
1681
1747
|
await setupDefaultGitAttributes(workspacePath);
|
|
1682
1748
|
let authUrl = remoteUrl;
|
|
1683
1749
|
if (remoteUrl.includes("github.com")) {
|
|
1684
|
-
|
|
1750
|
+
const cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
|
|
1685
1751
|
if (cleanUrl.startsWith("https://github.com/")) {
|
|
1686
1752
|
authUrl = cleanUrl.replace(
|
|
1687
1753
|
"https://github.com/",
|
|
@@ -1744,7 +1810,7 @@ async function getUniversalDiff(workspacePath, filePath) {
|
|
|
1744
1810
|
try {
|
|
1745
1811
|
const diff = await git.raw(["diff", "--", filePath]);
|
|
1746
1812
|
return diff;
|
|
1747
|
-
} catch
|
|
1813
|
+
} catch {
|
|
1748
1814
|
return "";
|
|
1749
1815
|
}
|
|
1750
1816
|
}
|
|
@@ -1759,6 +1825,7 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1759
1825
|
GIT_ASKPASS: "echo"
|
|
1760
1826
|
});
|
|
1761
1827
|
try {
|
|
1828
|
+
await ensureAiWeightsUseLfs(git);
|
|
1762
1829
|
await git.raw([
|
|
1763
1830
|
"-c",
|
|
1764
1831
|
"core.safecrlf=false",
|
|
@@ -1781,7 +1848,7 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
|
|
|
1781
1848
|
} catch (err) {
|
|
1782
1849
|
if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
|
|
1783
1850
|
console.log("Universal push rejected. Auto-pulling with rebase...");
|
|
1784
|
-
await git.pull("origin", branch, { "--rebase":
|
|
1851
|
+
await git.pull("origin", branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
1785
1852
|
await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
|
|
1786
1853
|
return;
|
|
1787
1854
|
}
|
|
@@ -2064,14 +2131,65 @@ async function scanForRepoFolder(repoName, candidateBaseDirs) {
|
|
|
2064
2131
|
async function removeGitLock(sessionId) {
|
|
2065
2132
|
const { repoPath } = getGitSession(sessionId);
|
|
2066
2133
|
if (!repoPath) return false;
|
|
2067
|
-
const lockPath =
|
|
2134
|
+
const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
|
|
2068
2135
|
try {
|
|
2069
|
-
await
|
|
2136
|
+
await fs__namespace.unlink(lockPath);
|
|
2070
2137
|
return true;
|
|
2071
|
-
} catch
|
|
2138
|
+
} catch {
|
|
2072
2139
|
return false;
|
|
2073
2140
|
}
|
|
2074
2141
|
}
|
|
2142
|
+
async function ensureAiWeightsUseLfs(git) {
|
|
2143
|
+
try {
|
|
2144
|
+
const aiExtensions = [
|
|
2145
|
+
"*.pt",
|
|
2146
|
+
"*.pth",
|
|
2147
|
+
"*.ckpt",
|
|
2148
|
+
"*.safetensors",
|
|
2149
|
+
"*.bin",
|
|
2150
|
+
"*.onnx",
|
|
2151
|
+
"*.h5",
|
|
2152
|
+
"*.tflite",
|
|
2153
|
+
"*.pb",
|
|
2154
|
+
"*.gguf"
|
|
2155
|
+
];
|
|
2156
|
+
await git.raw(["lfs", "install"]);
|
|
2157
|
+
for (const ext of aiExtensions) {
|
|
2158
|
+
await git.raw(["lfs", "track", ext]);
|
|
2159
|
+
}
|
|
2160
|
+
} catch (e) {
|
|
2161
|
+
console.warn("Failed to configure LFS for AI weights:", e);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const GITHUB_MODELS_CONFIG = {
|
|
2165
|
+
baseURL: "https://models.inference.ai.azure.com",
|
|
2166
|
+
defaultModel: "gpt-4o-mini",
|
|
2167
|
+
fallbackModel: "meta-llama-3.1-8b-instruct"
|
|
2168
|
+
};
|
|
2169
|
+
const CIPHER_ALGO = "aes-256-cbc";
|
|
2170
|
+
const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
|
|
2171
|
+
const CIPHER_SALT = "salt_gitelectron";
|
|
2172
|
+
const ENCRYPTED_BACKUP_PAYLOAD = "1aa43686251fc27545934564c92ebcd9:acac927d94c7479c0df45b9245f61f47bd4184fca2323e6ce971bc5c74dffaac925caa81788ce81374a05c7072301ee2eac10531be99a850453ff3fd23ea21cd11a8fa95d07d19fe1a8abcdd9a282b06d3e391a621137027c03765f65e977ed432c00e0e9627b2dad77ca708273eff14eac1bbfd01085d4b645a271167bae46edec2a09491b877fba73c3d29dc2e0e2120785347c9189db6bc9892a6018b6dea7ac064b991e9687e2df56e983743985f";
|
|
2173
|
+
function getBackupApiKeys() {
|
|
2174
|
+
try {
|
|
2175
|
+
const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
|
|
2176
|
+
if (!ivHex || !dataHex) return [];
|
|
2177
|
+
const key = crypto.scryptSync(CIPHER_SECRET, CIPHER_SALT, 32);
|
|
2178
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
2179
|
+
const decipher = crypto.createDecipheriv(CIPHER_ALGO, key, iv);
|
|
2180
|
+
let decrypted = decipher.update(dataHex, "hex", "utf8");
|
|
2181
|
+
decrypted += decipher.final("utf8");
|
|
2182
|
+
return decrypted.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
console.warn("[AI Setup] Failed to decrypt backup keys in memory:", err);
|
|
2185
|
+
return [];
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
function isValidGitHubToken(token) {
|
|
2189
|
+
if (!token || typeof token !== "string") return false;
|
|
2190
|
+
const trimmed = token.trim();
|
|
2191
|
+
return trimmed.startsWith("gho_") || trimmed.startsWith("ghp_") || trimmed.startsWith("github_pat_") || trimmed.length >= 30;
|
|
2192
|
+
}
|
|
2075
2193
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
2076
2194
|
The user has encountered a Git error during a push, pull, or merge operation and you are executing as a background process to silently fix it.
|
|
2077
2195
|
You will receive the Git Status and the Raw Error Message.
|
|
@@ -2090,14 +2208,7 @@ Return the response strictly as a JSON object with this shape, and absolutely no
|
|
|
2090
2208
|
"commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
|
|
2091
2209
|
}`;
|
|
2092
2210
|
async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
|
|
2093
|
-
|
|
2094
|
-
if (provider === "gemini") {
|
|
2095
|
-
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
2096
|
-
} else {
|
|
2097
|
-
apiKeyInput = "deepseek-hardcoded-key-if-applicable";
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
2211
|
+
const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
|
|
2101
2212
|
const prompt = `
|
|
2102
2213
|
GIT STATUS:
|
|
2103
2214
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2140,7 +2251,7 @@ ${errorMsg}
|
|
|
2140
2251
|
try {
|
|
2141
2252
|
if (provider === "gemini") {
|
|
2142
2253
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2143
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2254
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2144
2255
|
const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
|
|
2145
2256
|
responseText = result.response.text();
|
|
2146
2257
|
} else if (provider === "deepseek") {
|
|
@@ -2245,10 +2356,7 @@ Guidelines:
|
|
|
2245
2356
|
- If there are no conflicted files (e.g. just a standard rejection that a pull will fix), omit "resolvedCode".
|
|
2246
2357
|
- Return ONLY valid JSON. No markdown code block wrappers.`;
|
|
2247
2358
|
async function analyzePushErrorForAutoSync(provider, apiKeyInput, errorMsg, gitStatus, _repoPath, conflictedFilesContext, githubToken) {
|
|
2248
|
-
|
|
2249
|
-
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
2250
|
-
}
|
|
2251
|
-
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
2359
|
+
const keys = getBackupApiKeys();
|
|
2252
2360
|
let prompt = `
|
|
2253
2361
|
GIT STATUS:
|
|
2254
2362
|
${JSON.stringify(gitStatus, null, 2)}
|
|
@@ -2269,14 +2377,14 @@ ${content}
|
|
|
2269
2377
|
}
|
|
2270
2378
|
}
|
|
2271
2379
|
let lastError = null;
|
|
2272
|
-
if (githubToken) {
|
|
2380
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2273
2381
|
try {
|
|
2274
2382
|
const openai = new OpenAI({
|
|
2275
2383
|
apiKey: githubToken,
|
|
2276
|
-
baseURL:
|
|
2384
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2277
2385
|
});
|
|
2278
2386
|
const response = await openai.chat.completions.create({
|
|
2279
|
-
model:
|
|
2387
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2280
2388
|
messages: [
|
|
2281
2389
|
{ role: "system", content: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2282
2390
|
{ role: "user", content: prompt }
|
|
@@ -2298,7 +2406,7 @@ ${content}
|
|
|
2298
2406
|
try {
|
|
2299
2407
|
if (provider === "gemini") {
|
|
2300
2408
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2301
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2409
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2302
2410
|
const result = await model.generateContent([
|
|
2303
2411
|
{ text: AUTO_SYNC_SYSTEM_PROMPT },
|
|
2304
2412
|
{ text: prompt }
|
|
@@ -2330,17 +2438,17 @@ If the diff is empty or invalid, return "Auto-sync: update files".`;
|
|
|
2330
2438
|
const prompt = `GIT DIFF:
|
|
2331
2439
|
${truncatedDiff}`;
|
|
2332
2440
|
let keys = "".split(",").map((k) => k.trim()).filter((k) => k);
|
|
2333
|
-
if (!keys.length
|
|
2334
|
-
keys =
|
|
2441
|
+
if (!keys.length) {
|
|
2442
|
+
keys = getBackupApiKeys();
|
|
2335
2443
|
}
|
|
2336
|
-
if (githubToken) {
|
|
2444
|
+
if (isValidGitHubToken(githubToken)) {
|
|
2337
2445
|
try {
|
|
2338
2446
|
const openai = new OpenAI({
|
|
2339
2447
|
apiKey: githubToken,
|
|
2340
|
-
baseURL:
|
|
2448
|
+
baseURL: GITHUB_MODELS_CONFIG.baseURL
|
|
2341
2449
|
});
|
|
2342
2450
|
const response = await openai.chat.completions.create({
|
|
2343
|
-
model:
|
|
2451
|
+
model: GITHUB_MODELS_CONFIG.defaultModel,
|
|
2344
2452
|
messages: [
|
|
2345
2453
|
{ role: "system", content: systemPrompt },
|
|
2346
2454
|
{ role: "user", content: prompt }
|
|
@@ -2356,7 +2464,7 @@ ${truncatedDiff}`;
|
|
|
2356
2464
|
try {
|
|
2357
2465
|
if (provider === "gemini") {
|
|
2358
2466
|
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
2359
|
-
const model = genAI.getGenerativeModel({ model: "gemini-
|
|
2467
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
2360
2468
|
const result = await model.generateContent([{ text: systemPrompt }, { text: prompt }]);
|
|
2361
2469
|
return result.response.text().trim();
|
|
2362
2470
|
}
|
|
@@ -2457,7 +2565,11 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
|
|
|
2457
2565
|
// ignore Office temp lock files (~$README.md, etc.)
|
|
2458
2566
|
],
|
|
2459
2567
|
persistent: true,
|
|
2460
|
-
ignoreInitial: true
|
|
2568
|
+
ignoreInitial: true,
|
|
2569
|
+
awaitWriteFinish: {
|
|
2570
|
+
stabilityThreshold: 2e3,
|
|
2571
|
+
pollInterval: 100
|
|
2572
|
+
}
|
|
2461
2573
|
});
|
|
2462
2574
|
currentWatcher.on("all", (_event, filePath) => {
|
|
2463
2575
|
if (!isAutoSyncEnabled || isResolving) return;
|
|
@@ -2514,7 +2626,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2514
2626
|
return;
|
|
2515
2627
|
}
|
|
2516
2628
|
const branch = finalStatus.current || "main";
|
|
2517
|
-
onLog("Generating
|
|
2629
|
+
onLog("🤖 Generating commit summary via GitHub Models...");
|
|
2518
2630
|
let commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
|
|
2519
2631
|
try {
|
|
2520
2632
|
const diffStr = await getFileDiff(sessionId, "", true);
|
|
@@ -2532,7 +2644,7 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
2532
2644
|
await commitChanges(sessionId, commitMsg);
|
|
2533
2645
|
onLog(`Pushing to ${branch}...`);
|
|
2534
2646
|
await pushToRemote(sessionId, "origin", branch);
|
|
2535
|
-
onLog(`✅
|
|
2647
|
+
onLog(`✅ Pushed to origin/${branch} [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`);
|
|
2536
2648
|
} catch (error) {
|
|
2537
2649
|
onLog(`❌ Auto-sync failed: ${error.message}`);
|
|
2538
2650
|
if (globalOnErrorApproval && !isResolving) {
|
|
@@ -2564,7 +2676,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
|
2564
2676
|
onLog("Pulling remote changes with rebase...");
|
|
2565
2677
|
const status2 = await getStatus(sessionId);
|
|
2566
2678
|
const currentBranch = status2.current || "main";
|
|
2567
|
-
await git.pull("origin", currentBranch, { "--rebase": "
|
|
2679
|
+
await git.pull("origin", currentBranch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
|
|
2568
2680
|
onLog("Pull successful, attempting push again...");
|
|
2569
2681
|
await pushToRemote(sessionId, "origin", currentBranch);
|
|
2570
2682
|
onLog("✅ Error resolved and push resumed!");
|
|
@@ -2677,6 +2789,27 @@ function cancelAiFix() {
|
|
|
2677
2789
|
isResolving = false;
|
|
2678
2790
|
}
|
|
2679
2791
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
2792
|
+
if (process.platform === "darwin") {
|
|
2793
|
+
const macPaths = [
|
|
2794
|
+
"/opt/homebrew/bin",
|
|
2795
|
+
"/opt/homebrew/sbin",
|
|
2796
|
+
"/usr/local/bin",
|
|
2797
|
+
"/usr/local/sbin",
|
|
2798
|
+
"/usr/bin",
|
|
2799
|
+
"/bin",
|
|
2800
|
+
"/usr/sbin",
|
|
2801
|
+
"/sbin"
|
|
2802
|
+
];
|
|
2803
|
+
const currentPath = process.env.PATH || "";
|
|
2804
|
+
const missingPaths = macPaths.filter((p) => !currentPath.includes(p));
|
|
2805
|
+
if (missingPaths.length > 0) {
|
|
2806
|
+
process.env.PATH = `${missingPaths.join(":")}:${currentPath}`;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (process.platform === "win32") {
|
|
2810
|
+
electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
|
2811
|
+
electron.app.commandLine.appendSwitch("disable-color-correct-rendering");
|
|
2812
|
+
}
|
|
2680
2813
|
function createWindow(initialPath) {
|
|
2681
2814
|
const mainWindow = new electron.BrowserWindow({
|
|
2682
2815
|
width: 1100,
|
|
@@ -3031,6 +3164,20 @@ electron.app.whenReady().then(async () => {
|
|
|
3031
3164
|
electron.ipcMain.handle("remove-git-lock", async (event) => {
|
|
3032
3165
|
return await removeGitLock(event.sender.id);
|
|
3033
3166
|
});
|
|
3167
|
+
electron.ipcMain.handle("clear-app-cache", async (event) => {
|
|
3168
|
+
try {
|
|
3169
|
+
if (electron.session.defaultSession) {
|
|
3170
|
+
await electron.session.defaultSession.clearCache();
|
|
3171
|
+
}
|
|
3172
|
+
try {
|
|
3173
|
+
await removeGitLock(event.sender.id);
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
return { success: true };
|
|
3177
|
+
} catch (e) {
|
|
3178
|
+
return { success: false, error: e.message };
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3034
3181
|
electron.ipcMain.handle("check-path-exists", async (_event, targetPath) => {
|
|
3035
3182
|
if (!targetPath) return false;
|
|
3036
3183
|
try {
|
|
@@ -3116,18 +3263,27 @@ electron.app.whenReady().then(async () => {
|
|
|
3116
3263
|
electron.ipcMain.handle("install-update", async () => {
|
|
3117
3264
|
return new Promise((resolve) => {
|
|
3118
3265
|
const { spawn } = require("child_process");
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3266
|
+
if (process.platform === "win32") {
|
|
3267
|
+
const cmdArgs = [
|
|
3268
|
+
"/c",
|
|
3269
|
+
"start",
|
|
3270
|
+
"cmd.exe",
|
|
3271
|
+
"/c",
|
|
3272
|
+
"echo Waiting for Git Electron to close so it can be safely updated... & timeout /t 3 /nobreak > nul & echo Installing Update... & npm i -g gitelectron@latest gitquickpush-cli@latest & echo Update Complete! Starting application... & gitelectron open & exit"
|
|
3273
|
+
];
|
|
3274
|
+
const child = spawn("cmd.exe", cmdArgs, {
|
|
3275
|
+
detached: true,
|
|
3276
|
+
stdio: "ignore"
|
|
3277
|
+
});
|
|
3278
|
+
child.unref();
|
|
3279
|
+
} else {
|
|
3280
|
+
const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
|
|
3281
|
+
const child = spawn("sh", ["-c", shScript], {
|
|
3282
|
+
detached: true,
|
|
3283
|
+
stdio: "ignore"
|
|
3284
|
+
});
|
|
3285
|
+
child.unref();
|
|
3286
|
+
}
|
|
3131
3287
|
resolve(true);
|
|
3132
3288
|
setTimeout(() => {
|
|
3133
3289
|
electron.app.exit(0);
|
package/out/preload/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const api = {
|
|
|
15
15
|
initRepo: (path) => electron.ipcRenderer.invoke("init-repo", path),
|
|
16
16
|
removeGitMetadata: (repoPath) => electron.ipcRenderer.invoke("remove-git-metadata", repoPath),
|
|
17
17
|
removeGitLock: () => electron.ipcRenderer.invoke("remove-git-lock"),
|
|
18
|
+
clearAppCache: () => electron.ipcRenderer.invoke("clear-app-cache"),
|
|
18
19
|
getStatus: () => electron.ipcRenderer.invoke("get-status"),
|
|
19
20
|
stageFiles: (files) => electron.ipcRenderer.invoke("stage-files", files),
|
|
20
21
|
unstageFiles: (files) => electron.ipcRenderer.invoke("unstage-files", files),
|