gitxelectron 1.1.8 → 1.2.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/out/main/index.js CHANGED
@@ -58,15 +58,23 @@ function cleanupSession(sessionId) {
58
58
  activeSessions.delete(sessionId);
59
59
  }
60
60
  function getToken(sessionId) {
61
- const accountId = activeSessions.get(sessionId);
61
+ let accountId = activeSessions.get(sessionId);
62
+ if (!accountId && accounts.length > 0) {
63
+ accountId = accounts[0].id;
64
+ activeSessions.set(sessionId, accountId);
65
+ }
62
66
  if (!accountId) return null;
63
67
  const acc = accounts.find((a) => a.id === accountId);
64
- return acc ? acc.token : null;
68
+ return acc ? acc.token : accounts.length > 0 ? accounts[0].token : null;
65
69
  }
66
70
  function getActiveAccount(sessionId) {
67
- const accountId = activeSessions.get(sessionId);
71
+ let accountId = activeSessions.get(sessionId);
72
+ if (!accountId && accounts.length > 0) {
73
+ accountId = accounts[0].id;
74
+ activeSessions.set(sessionId, accountId);
75
+ }
68
76
  if (!accountId) return null;
69
- return accounts.find((a) => a.id === accountId) || null;
77
+ return accounts.find((a) => a.id === accountId) || (accounts.length > 0 ? accounts[0] : null);
70
78
  }
71
79
  async function getAccounts(sessionId) {
72
80
  return {
@@ -578,6 +586,22 @@ function initGit(sessionId, baseDir) {
578
586
  });
579
587
  gitSessions.set(sessionId, { git, repoPath: baseDir });
580
588
  }
589
+ function createSafeGit(baseDir) {
590
+ const options = {
591
+ binary: "git",
592
+ maxConcurrentProcesses: 6,
593
+ unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
594
+ };
595
+ if (baseDir) {
596
+ options.baseDir = baseDir;
597
+ }
598
+ return simpleGit.simpleGit(options).env({
599
+ ...process.env,
600
+ GCM_INTERACTIVE: "false",
601
+ GIT_TERMINAL_PROMPT: "0",
602
+ GIT_ASKPASS: "echo"
603
+ });
604
+ }
581
605
  function cleanupGitSession(sessionId) {
582
606
  gitSessions.delete(sessionId);
583
607
  }
@@ -709,10 +733,20 @@ async function commitChanges(sessionId, message) {
709
733
  function injectAuthToken(sessionId, remoteUrl) {
710
734
  const token = getToken(sessionId);
711
735
  if (!token) return remoteUrl;
712
- if (remoteUrl.includes("github.com")) {
713
- let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
714
- if (cleanUrl.startsWith("https://github.com/")) {
715
- return cleanUrl.replace("https://github.com/", `https://x-access-token:${token}@github.com/`);
736
+ try {
737
+ let cleanUrl = remoteUrl.trim();
738
+ if (cleanUrl.startsWith("git@github.com:")) {
739
+ cleanUrl = cleanUrl.replace("git@github.com:", "https://github.com/");
740
+ }
741
+ if (cleanUrl.includes("github.com")) {
742
+ const parsed = new URL(cleanUrl);
743
+ parsed.username = "x-access-token";
744
+ parsed.password = token;
745
+ return parsed.toString();
746
+ }
747
+ } catch (e) {
748
+ if (remoteUrl.includes("github.com")) {
749
+ return remoteUrl.replace(/https:\/\/(?:[^@]+@)?github\.com\//, `https://x-access-token:${token}@github.com/`);
716
750
  }
717
751
  }
718
752
  return remoteUrl;
@@ -1089,20 +1123,95 @@ async function executeRawCommand(sessionId, args) {
1089
1123
  return await git.raw(args);
1090
1124
  }
1091
1125
  async function cloneRepository(sessionId, remoteUrl, destPath) {
1092
- const authUrl = injectAuthToken(sessionId, remoteUrl);
1126
+ let normalizedUrl = remoteUrl.trim();
1127
+ if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://") && !normalizedUrl.startsWith("git@")) {
1128
+ normalizedUrl = "https://github.com/" + normalizedUrl;
1129
+ }
1130
+ if (normalizedUrl.startsWith("git@github.com:")) {
1131
+ normalizedUrl = normalizedUrl.replace("git@github.com:", "https://github.com/");
1132
+ }
1133
+ if (!normalizedUrl.endsWith(".git") && normalizedUrl.startsWith("http")) {
1134
+ normalizedUrl += ".git";
1135
+ }
1136
+ const authUrl = injectAuthToken(sessionId, normalizedUrl);
1093
1137
  await fs__namespace.mkdir(path__namespace.dirname(destPath), { recursive: true });
1094
- const git = simpleGit.simpleGit().env({
1095
- ...process.env,
1096
- GCM_INTERACTIVE: "false",
1097
- GIT_TERMINAL_PROMPT: "0",
1098
- GIT_ASKPASS: "echo"
1099
- });
1100
- await git.clone(authUrl, destPath);
1138
+ let destExists = false;
1139
+ let destIsEmpty = false;
1140
+ let destHasGit = false;
1101
1141
  try {
1102
- await simpleGit.simpleGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
1103
- } catch (e) {
1142
+ const stats = await fs__namespace.stat(destPath);
1143
+ if (stats.isDirectory()) {
1144
+ destExists = true;
1145
+ const files = await fs__namespace.readdir(destPath);
1146
+ const realFiles = files.filter(
1147
+ (f) => ![".ds_store", "desktop.ini", "thumbs.db"].includes(f.toLowerCase())
1148
+ );
1149
+ destIsEmpty = realFiles.length === 0;
1150
+ destHasGit = files.includes(".git");
1151
+ }
1152
+ } catch {
1153
+ destExists = false;
1154
+ }
1155
+ try {
1156
+ if (destExists && destHasGit) {
1157
+ const existingGit = createSafeGit(destPath);
1158
+ try {
1159
+ await existingGit.raw(["config", "--global", "--add", "safe.directory", destPath]);
1160
+ const remotes = await existingGit.getRemotes();
1161
+ if (remotes.some((r) => r.name === "origin")) {
1162
+ await existingGit.raw(["remote", "set-url", "origin", authUrl]);
1163
+ } else {
1164
+ await existingGit.raw(["remote", "add", "origin", authUrl]);
1165
+ }
1166
+ await existingGit.raw(["-c", "credential.helper=", "fetch", "origin"]);
1167
+ const st = await existingGit.status();
1168
+ const branch = st.current || "main";
1169
+ try {
1170
+ await existingGit.raw(["-c", "credential.helper=", "pull", "origin", branch]);
1171
+ } catch (e) {
1172
+ }
1173
+ return destPath;
1174
+ } catch (err) {
1175
+ console.warn("Re-using existing repo failed, falling through to clone attempt:", err);
1176
+ }
1177
+ }
1178
+ if (destExists && destIsEmpty) {
1179
+ const emptyDirGit = createSafeGit(destPath);
1180
+ await emptyDirGit.raw(["-c", "credential.helper=", "clone", authUrl, "."]);
1181
+ try {
1182
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
1183
+ } catch (e) {
1184
+ }
1185
+ return destPath;
1186
+ }
1187
+ if (destExists && !destIsEmpty && !destHasGit) {
1188
+ throw new Error(
1189
+ `The folder '${path__namespace.basename(destPath)}' already contains files. Please select an empty folder or a different location to clone.`
1190
+ );
1191
+ }
1192
+ const baseDir = path__namespace.dirname(destPath);
1193
+ const targetDirName = path__namespace.basename(destPath);
1194
+ const parentGit = createSafeGit(baseDir);
1195
+ await parentGit.raw(["-c", "credential.helper=", "clone", authUrl, targetDirName]);
1196
+ try {
1197
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
1198
+ } catch (e) {
1199
+ }
1200
+ return destPath;
1201
+ } catch (err) {
1202
+ const token = getToken(sessionId);
1203
+ let cleanMsg = err?.message || String(err);
1204
+ if (token) {
1205
+ cleanMsg = cleanMsg.split(token).join("***");
1206
+ }
1207
+ cleanMsg = cleanMsg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
1208
+ if (cleanMsg.includes("terminal prompts disabled") || cleanMsg.includes("Authentication failed") || cleanMsg.includes("could not read Username")) {
1209
+ throw new Error(
1210
+ `Authentication failed for GitHub repository. Please verify your account is logged in and has access to this repository.`
1211
+ );
1212
+ }
1213
+ throw new Error(cleanMsg);
1104
1214
  }
1105
- return destPath;
1106
1215
  }
1107
1216
  const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
1108
1217
  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.
@@ -1209,6 +1318,32 @@ ${errorMsg}
1209
1318
  `AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
1210
1319
  );
1211
1320
  }
1321
+ const getConfigPath = () => path__namespace.join(electron.app.getPath("userData"), "gitx-config.json");
1322
+ const saveLastWorkspace = async (workspacePath) => {
1323
+ try {
1324
+ const configPath = getConfigPath();
1325
+ let config = {};
1326
+ try {
1327
+ const data = await fs__namespace.readFile(configPath, "utf8");
1328
+ config = JSON.parse(data);
1329
+ } catch (e) {
1330
+ }
1331
+ config.lastWorkspace = workspacePath;
1332
+ await fs__namespace.writeFile(configPath, JSON.stringify(config, null, 2));
1333
+ } catch (e) {
1334
+ console.error("Failed to save workspace", e);
1335
+ }
1336
+ };
1337
+ const getLastWorkspace = async () => {
1338
+ try {
1339
+ const configPath = getConfigPath();
1340
+ const data = await fs__namespace.readFile(configPath, "utf8");
1341
+ const config = JSON.parse(data);
1342
+ return config.lastWorkspace || null;
1343
+ } catch (e) {
1344
+ return null;
1345
+ }
1346
+ };
1212
1347
  let currentWatcher = null;
1213
1348
  let syncTimeout = null;
1214
1349
  let isAutoSyncEnabled = false;
@@ -1235,7 +1370,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
1235
1370
  // ignore dotfiles (.git, .DS_Store)
1236
1371
  /node_modules/,
1237
1372
  /dist/,
1238
- /build/
1373
+ /build/,
1374
+ /~\$/
1375
+ // ignore Office temp lock files (~$README.md, etc.)
1239
1376
  ],
1240
1377
  persistent: true,
1241
1378
  ignoreInitial: true
@@ -1259,7 +1396,12 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
1259
1396
  changedFiles.clear();
1260
1397
  try {
1261
1398
  onLog(`Staging ${filesToSync.length} file(s)...`);
1262
- await stageFiles(sessionId, filesToSync);
1399
+ const existingFiles = filesToSync.filter((f) => !f.startsWith("~$") && !f.includes("/~$"));
1400
+ if (existingFiles.length === 0) {
1401
+ onLog("No valid files to sync (temp files filtered).");
1402
+ return;
1403
+ }
1404
+ await stageFiles(sessionId, existingFiles);
1263
1405
  const status = await getStatus(sessionId);
1264
1406
  const branch = status.current || "main";
1265
1407
  const commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
@@ -1383,6 +1525,7 @@ electron.app.whenReady().then(async () => {
1383
1525
  if (isRepo) {
1384
1526
  initGit(sessionId, path2);
1385
1527
  }
1528
+ await saveLastWorkspace(path2);
1386
1529
  return { path: path2, isRepo };
1387
1530
  });
1388
1531
  electron.ipcMain.handle("set-repo-path", async (event, path2) => {
@@ -1391,6 +1534,7 @@ electron.app.whenReady().then(async () => {
1391
1534
  if (isRepo) {
1392
1535
  initGit(sessionId, path2);
1393
1536
  }
1537
+ await saveLastWorkspace(path2);
1394
1538
  return { path: path2, isRepo };
1395
1539
  });
1396
1540
  electron.ipcMain.handle("init-repo", async (event, path2) => {
@@ -1678,7 +1822,10 @@ electron.app.whenReady().then(async () => {
1678
1822
  }, 500);
1679
1823
  });
1680
1824
  });
1681
- createWindow();
1825
+ if (!initialRepo) {
1826
+ initialRepo = await getLastWorkspace();
1827
+ }
1828
+ createWindow(initialRepo || void 0);
1682
1829
  electron.app.on("activate", function() {
1683
1830
  if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
1684
1831
  });