gitxelectron 1.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.
@@ -0,0 +1,1450 @@
1
+ "use strict";
2
+ const electron = require("electron");
3
+ const path = require("path");
4
+ const simpleGit = require("simple-git");
5
+ const fs = require("fs/promises");
6
+ const crypto = require("crypto");
7
+ const generativeAi = require("@google/generative-ai");
8
+ const OpenAI = require("openai");
9
+ const chokidar = require("chokidar");
10
+ const utils = require("@electron-toolkit/utils");
11
+ function _interopNamespaceDefault(e) {
12
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
13
+ if (e) {
14
+ for (const k in e) {
15
+ if (k !== "default") {
16
+ const d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: () => e[k]
20
+ });
21
+ }
22
+ }
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
28
+ const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
29
+ const GITHUB_CLIENT_ID = "Ov23livWfiaPADGiwSXq";
30
+ let accounts = [];
31
+ const activeSessions = /* @__PURE__ */ new Map();
32
+ const ACCOUNTS_PATH = path__namespace.join(electron.app.getPath("userData"), "github_accounts.enc");
33
+ async function loadAccounts() {
34
+ try {
35
+ const encrypted = await fs__namespace.readFile(ACCOUNTS_PATH);
36
+ if (electron.safeStorage.isEncryptionAvailable()) {
37
+ const decrypted = electron.safeStorage.decryptString(encrypted);
38
+ accounts = JSON.parse(decrypted);
39
+ }
40
+ } catch (e) {
41
+ accounts = [];
42
+ }
43
+ }
44
+ async function saveAccounts() {
45
+ if (electron.safeStorage.isEncryptionAvailable()) {
46
+ const encrypted = electron.safeStorage.encryptString(JSON.stringify(accounts));
47
+ await fs__namespace.writeFile(ACCOUNTS_PATH, encrypted);
48
+ }
49
+ }
50
+ function initSession(sessionId, accountId) {
51
+ if (accounts.length > 0) {
52
+ activeSessions.set(sessionId, accounts[0].id);
53
+ } else {
54
+ activeSessions.delete(sessionId);
55
+ }
56
+ }
57
+ function cleanupSession(sessionId) {
58
+ activeSessions.delete(sessionId);
59
+ }
60
+ function getToken(sessionId) {
61
+ const accountId = activeSessions.get(sessionId);
62
+ if (!accountId) return null;
63
+ const acc = accounts.find((a) => a.id === accountId);
64
+ return acc ? acc.token : null;
65
+ }
66
+ function getActiveAccount(sessionId) {
67
+ const accountId = activeSessions.get(sessionId);
68
+ if (!accountId) return null;
69
+ return accounts.find((a) => a.id === accountId) || null;
70
+ }
71
+ async function getAccounts(sessionId) {
72
+ return {
73
+ accounts: accounts.map((a) => ({ id: a.id, username: a.username, avatar_url: a.avatar_url })),
74
+ activeAccountId: activeSessions.get(sessionId) || null
75
+ };
76
+ }
77
+ async function switchAccount(sessionId, accountId) {
78
+ if (accounts.find((a) => a.id === accountId)) {
79
+ activeSessions.set(sessionId, accountId);
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ async function removeAccount(accountId) {
85
+ accounts = accounts.filter((a) => a.id !== accountId);
86
+ await saveAccounts();
87
+ for (const [sId, aId] of activeSessions.entries()) {
88
+ if (aId === accountId) {
89
+ if (accounts.length > 0) {
90
+ activeSessions.set(sId, accounts[0].id);
91
+ } else {
92
+ activeSessions.delete(sId);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ async function logout(sessionId) {
98
+ const accountId = activeSessions.get(sessionId);
99
+ if (accountId) {
100
+ await removeAccount(accountId);
101
+ }
102
+ }
103
+ async function getAuthStatus(sessionId) {
104
+ const activeAccount = getActiveAccount(sessionId);
105
+ if (!activeAccount) return { status: "Not Authenticated", username: null };
106
+ return { status: "Verified", username: activeAccount.username };
107
+ }
108
+ async function startDeviceFlow(_sessionId) {
109
+ const response = await fetch("https://github.com/login/device/code", {
110
+ method: "POST",
111
+ headers: {
112
+ Accept: "application/json",
113
+ "Content-Type": "application/json"
114
+ },
115
+ body: JSON.stringify({
116
+ client_id: GITHUB_CLIENT_ID,
117
+ scope: "repo user"
118
+ })
119
+ });
120
+ const data = await response.json();
121
+ if (data.error) throw new Error(data.error_description || data.error);
122
+ return {
123
+ ...data,
124
+ verification_uri_complete: data.verification_uri_complete || `${data.verification_uri}?user_code=${data.user_code}`
125
+ };
126
+ }
127
+ async function pollDeviceFlow(sessionId, deviceCode, intervalMs) {
128
+ const TIMEOUT_MS = 15 * 60 * 1e3;
129
+ const startTime = Date.now();
130
+ let currentInterval = Math.max(intervalMs, 5e3);
131
+ return new Promise((resolve, reject) => {
132
+ const poll = async () => {
133
+ if (Date.now() - startTime > TIMEOUT_MS) {
134
+ reject(new Error("Authentication timed out."));
135
+ return;
136
+ }
137
+ try {
138
+ const response = await fetch("https://github.com/login/oauth/access_token", {
139
+ method: "POST",
140
+ headers: {
141
+ Accept: "application/json",
142
+ "Content-Type": "application/json"
143
+ },
144
+ body: JSON.stringify({
145
+ client_id: GITHUB_CLIENT_ID,
146
+ device_code: deviceCode,
147
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
148
+ })
149
+ });
150
+ const data = await response.json();
151
+ if (data.access_token) {
152
+ const userRes = await fetch("https://api.github.com/user", {
153
+ headers: {
154
+ Authorization: `Bearer ${data.access_token}`,
155
+ Accept: "application/vnd.github.v3+json",
156
+ "User-Agent": "gitelectron-App"
157
+ }
158
+ });
159
+ if (userRes.ok) {
160
+ const userData = await userRes.json();
161
+ const username = userData.login;
162
+ const existingIndex = accounts.findIndex((a) => a.username === username);
163
+ const accountId = existingIndex >= 0 ? accounts[existingIndex].id : crypto.randomUUID();
164
+ const newAccount = {
165
+ id: accountId,
166
+ username,
167
+ token: data.access_token,
168
+ avatar_url: userData.avatar_url
169
+ };
170
+ if (existingIndex >= 0) {
171
+ accounts[existingIndex] = newAccount;
172
+ } else {
173
+ accounts.push(newAccount);
174
+ }
175
+ await saveAccounts();
176
+ activeSessions.set(sessionId, accountId);
177
+ try {
178
+ const { setGitConfig: setGitConfig2 } = require("./gitService");
179
+ const email = `${userData.id}+${username}@users.noreply.github.com`;
180
+ await setGitConfig2(sessionId, "user.name", username);
181
+ await setGitConfig2(sessionId, "user.email", email);
182
+ } catch (e) {
183
+ console.error("Failed to configure git user", e);
184
+ }
185
+ resolve(true);
186
+ return;
187
+ } else {
188
+ reject(new Error("Failed to fetch user profile after auth."));
189
+ return;
190
+ }
191
+ } else if (data.error === "authorization_pending") {
192
+ setTimeout(poll, currentInterval);
193
+ } else if (data.error === "slow_down") {
194
+ currentInterval += 5e3;
195
+ setTimeout(poll, currentInterval);
196
+ } else if (data.error === "expired_token") {
197
+ reject(new Error("The device code has expired."));
198
+ } else if (data.error === "access_denied") {
199
+ reject(new Error("Access was denied."));
200
+ } else if (data.error) {
201
+ reject(new Error(data.error_description || data.error));
202
+ } else {
203
+ setTimeout(poll, currentInterval);
204
+ }
205
+ } catch (e) {
206
+ reject(e);
207
+ }
208
+ };
209
+ poll();
210
+ });
211
+ }
212
+ async function verifyRepositoryPermission(sessionId, remoteUrl) {
213
+ if (!remoteUrl.includes("github.com")) return true;
214
+ const activeAccount = getActiveAccount(sessionId);
215
+ if (!activeAccount) return true;
216
+ let owner = "";
217
+ let repo = "";
218
+ let parts = remoteUrl.split("github.com");
219
+ let pathPart = parts[1].replace(/^[:\/]/, "").replace(/\.git$/, "");
220
+ const pathSplits = pathPart.split("/");
221
+ if (pathSplits.length >= 2) {
222
+ owner = pathSplits[0];
223
+ repo = pathSplits[1];
224
+ }
225
+ if (!owner || !repo) return true;
226
+ try {
227
+ const res = await fetch(
228
+ `https://api.github.com/repos/${owner}/${repo}/collaborators/${activeAccount.username}/permission`,
229
+ {
230
+ headers: {
231
+ Authorization: `Bearer ${activeAccount.token}`,
232
+ Accept: "application/vnd.github.v3+json",
233
+ "User-Agent": "gitelectron-App"
234
+ }
235
+ }
236
+ );
237
+ if (res.ok) {
238
+ const data = await res.json();
239
+ return ["admin", "write"].includes(data.permission);
240
+ }
241
+ if (res.status === 403 || res.status === 404) return false;
242
+ return true;
243
+ } catch (e) {
244
+ return true;
245
+ }
246
+ }
247
+ async function getUserRepositories(sessionId) {
248
+ const token = getToken(sessionId);
249
+ if (!token) throw new Error("Not authenticated in this session");
250
+ const res = await fetch("https://api.github.com/user/repos?sort=updated&per_page=100", {
251
+ headers: {
252
+ Authorization: `Bearer ${token}`,
253
+ Accept: "application/vnd.github.v3+json",
254
+ "User-Agent": "gitelectron-App"
255
+ }
256
+ });
257
+ if (!res.ok) {
258
+ const errorData = await res.json().catch(() => ({}));
259
+ throw new Error(errorData.message || "Failed to fetch repositories");
260
+ }
261
+ return await res.json();
262
+ }
263
+ async function createGitHubRepository(sessionId, name, description, isPrivate, autoInit = false, gitignoreTemplate = "", licenseTemplate = "") {
264
+ const token = getToken(sessionId);
265
+ if (!token) throw new Error("Not authenticated in this session");
266
+ const bodyData = { name, description, private: isPrivate, auto_init: autoInit };
267
+ if (gitignoreTemplate) bodyData.gitignore_template = gitignoreTemplate;
268
+ if (licenseTemplate) bodyData.license_template = licenseTemplate;
269
+ const res = await fetch("https://api.github.com/user/repos", {
270
+ method: "POST",
271
+ headers: {
272
+ Authorization: `Bearer ${token}`,
273
+ Accept: "application/vnd.github.v3+json",
274
+ "User-Agent": "gitelectron-App",
275
+ "Content-Type": "application/json"
276
+ },
277
+ body: JSON.stringify(bodyData)
278
+ });
279
+ if (!res.ok) {
280
+ const errorData = await res.json().catch(() => ({}));
281
+ throw new Error(errorData.message || "Failed to create repository");
282
+ }
283
+ return await res.json();
284
+ }
285
+ function parseGithubUrl(remoteUrl) {
286
+ if (!remoteUrl.includes("github.com")) throw new Error("Not a GitHub repository");
287
+ let parts = remoteUrl.split("github.com");
288
+ let pathPart = parts[1].replace(/^[:\/]/, "").replace(/\.git$/, "");
289
+ const pathSplits = pathPart.split("/");
290
+ if (pathSplits.length >= 2) {
291
+ return { owner: pathSplits[0], repo: pathSplits[1] };
292
+ }
293
+ throw new Error("Invalid GitHub repository URL");
294
+ }
295
+ async function getCollaborators(sessionId, remoteUrl) {
296
+ const token = getToken(sessionId);
297
+ if (!token) throw new Error("Not authenticated");
298
+ const { owner, repo } = parseGithubUrl(remoteUrl);
299
+ const headers = {
300
+ Authorization: `Bearer ${token}`,
301
+ Accept: "application/vnd.github.v3+json",
302
+ "User-Agent": "gitelectron-App"
303
+ };
304
+ const [collabRes, invRes] = await Promise.all([
305
+ fetch(`https://api.github.com/repos/${owner}/${repo}/collaborators`, { headers }),
306
+ fetch(`https://api.github.com/repos/${owner}/${repo}/invitations`, { headers })
307
+ ]);
308
+ if (!collabRes.ok) {
309
+ const errorData = await collabRes.json().catch(() => ({}));
310
+ throw new Error(
311
+ errorData.message || "Failed to fetch collaborators (Requires Admin permissions)"
312
+ );
313
+ }
314
+ const collaborators = await collabRes.json();
315
+ const invitations = invRes.ok ? await invRes.json() : [];
316
+ return { collaborators, invitations };
317
+ }
318
+ async function addCollaborator(sessionId, remoteUrl, username) {
319
+ const token = getToken(sessionId);
320
+ if (!token) throw new Error("Not authenticated");
321
+ const { owner, repo } = parseGithubUrl(remoteUrl);
322
+ const res = await fetch(
323
+ `https://api.github.com/repos/${owner}/${repo}/collaborators/${username}`,
324
+ {
325
+ method: "PUT",
326
+ headers: {
327
+ Authorization: `Bearer ${token}`,
328
+ Accept: "application/vnd.github.v3+json",
329
+ "User-Agent": "gitelectron-App",
330
+ "Content-Type": "application/json"
331
+ },
332
+ body: JSON.stringify({ permission: "push" })
333
+ }
334
+ );
335
+ if (!res.ok) {
336
+ const errorData = await res.json().catch(() => ({}));
337
+ throw new Error(errorData.message || "Failed to add collaborator");
338
+ }
339
+ if (res.status === 204) return { status: "already_collaborator" };
340
+ return await res.json();
341
+ }
342
+ async function removeCollaborator(sessionId, remoteUrl, usernameOrId, isInvitation = false) {
343
+ const token = getToken(sessionId);
344
+ if (!token) throw new Error("Not authenticated");
345
+ const { owner, repo } = parseGithubUrl(remoteUrl);
346
+ const url = isInvitation ? `https://api.github.com/repos/${owner}/${repo}/invitations/${usernameOrId}` : `https://api.github.com/repos/${owner}/${repo}/collaborators/${usernameOrId}`;
347
+ const res = await fetch(url, {
348
+ method: "DELETE",
349
+ headers: {
350
+ Authorization: `Bearer ${token}`,
351
+ Accept: "application/vnd.github.v3+json",
352
+ "User-Agent": "gitelectron-App"
353
+ }
354
+ });
355
+ if (!res.ok) {
356
+ const errorData = await res.json().catch(() => ({}));
357
+ throw new Error(errorData.message || "Failed to remove collaborator");
358
+ }
359
+ }
360
+ async function searchGitHubUser(sessionId, username) {
361
+ const token = getToken(sessionId);
362
+ if (!token) throw new Error("Not authenticated");
363
+ const res = await fetch(`https://api.github.com/users/${username}`, {
364
+ headers: {
365
+ Authorization: `Bearer ${token}`,
366
+ Accept: "application/vnd.github.v3+json",
367
+ "User-Agent": "gitelectron-App"
368
+ }
369
+ });
370
+ if (!res.ok) {
371
+ if (res.status === 404) return null;
372
+ const errorData = await res.json().catch(() => ({}));
373
+ throw new Error(errorData.message || "Failed to search user");
374
+ }
375
+ return await res.json();
376
+ }
377
+ async function getRepositoryBranches(sessionId, owner, repo) {
378
+ const token = getToken(sessionId);
379
+ if (!token) throw new Error("Not authenticated");
380
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/branches`, {
381
+ headers: {
382
+ Authorization: `Bearer ${token}`,
383
+ Accept: "application/vnd.github.v3+json",
384
+ "User-Agent": "gitelectron-App"
385
+ }
386
+ });
387
+ if (!res.ok) {
388
+ const errorData = await res.json().catch(() => ({}));
389
+ throw new Error(errorData.message || "Failed to fetch branches");
390
+ }
391
+ return await res.json();
392
+ }
393
+ async function getUserInvitations(sessionId) {
394
+ const token = getToken(sessionId);
395
+ if (!token) throw new Error("Not authenticated");
396
+ const res = await fetch("https://api.github.com/user/repository_invitations", {
397
+ headers: {
398
+ Authorization: `Bearer ${token}`,
399
+ Accept: "application/vnd.github.v3+json",
400
+ "User-Agent": "gitelectron-App"
401
+ }
402
+ });
403
+ if (!res.ok) {
404
+ const errorData = await res.json().catch(() => ({}));
405
+ throw new Error(errorData.message || "Failed to fetch invitations");
406
+ }
407
+ return await res.json();
408
+ }
409
+ async function acceptInvitation(sessionId, invitationId) {
410
+ const token = getToken(sessionId);
411
+ if (!token) throw new Error("Not authenticated");
412
+ const res = await fetch(`https://api.github.com/user/repository_invitations/${invitationId}`, {
413
+ method: "PATCH",
414
+ headers: {
415
+ Authorization: `Bearer ${token}`,
416
+ Accept: "application/vnd.github.v3+json",
417
+ "User-Agent": "gitelectron-App"
418
+ }
419
+ });
420
+ if (!res.ok) {
421
+ const errorData = await res.json().catch(() => ({}));
422
+ throw new Error(errorData.message || "Failed to accept invitation");
423
+ }
424
+ }
425
+ async function declineInvitation(sessionId, invitationId) {
426
+ const token = getToken(sessionId);
427
+ if (!token) throw new Error("Not authenticated");
428
+ const res = await fetch(`https://api.github.com/user/repository_invitations/${invitationId}`, {
429
+ method: "DELETE",
430
+ headers: {
431
+ Authorization: `Bearer ${token}`,
432
+ Accept: "application/vnd.github.v3+json",
433
+ "User-Agent": "gitelectron-App"
434
+ }
435
+ });
436
+ if (!res.ok) {
437
+ const errorData = await res.json().catch(() => ({}));
438
+ throw new Error(errorData.message || "Failed to decline invitation");
439
+ }
440
+ }
441
+ const gitSessions = /* @__PURE__ */ new Map();
442
+ function initGit(sessionId, baseDir) {
443
+ const options = {
444
+ baseDir,
445
+ binary: "git",
446
+ maxConcurrentProcesses: 6
447
+ };
448
+ const git = simpleGit.simpleGit(options);
449
+ gitSessions.set(sessionId, { git, repoPath: baseDir });
450
+ }
451
+ function cleanupGitSession(sessionId) {
452
+ gitSessions.delete(sessionId);
453
+ }
454
+ function getGitSession(sessionId) {
455
+ const session = gitSessions.get(sessionId);
456
+ if (!session) throw new Error("Git not initialized for this window");
457
+ return session;
458
+ }
459
+ async function isGitRepo(dir) {
460
+ try {
461
+ const tempGit = simpleGit.simpleGit(dir);
462
+ return await tempGit.checkIsRepo();
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+ async function initializeRepo(sessionId, dir) {
468
+ try {
469
+ const tempGit = simpleGit.simpleGit(dir);
470
+ await tempGit.init();
471
+ const branches = await tempGit.branchLocal();
472
+ if (branches.all.length === 0) {
473
+ await tempGit.raw([
474
+ "-c",
475
+ "user.name=gitelectron",
476
+ "-c",
477
+ "user.email=noreply@gitelectron.local",
478
+ "commit",
479
+ "--allow-empty",
480
+ "-m",
481
+ "Initial repository setup"
482
+ ]);
483
+ }
484
+ initGit(sessionId, dir);
485
+ return true;
486
+ } catch (e) {
487
+ console.error("Failed to init repo", e);
488
+ return false;
489
+ }
490
+ }
491
+ async function getStatus(sessionId) {
492
+ const { git } = getGitSession(sessionId);
493
+ const status = await git.status();
494
+ return JSON.parse(JSON.stringify(status));
495
+ }
496
+ async function stageFiles(sessionId, files) {
497
+ const { git, repoPath } = getGitSession(sessionId);
498
+ const fileArray = Array.isArray(files) ? files : [files];
499
+ if (repoPath) {
500
+ const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
501
+ const largeFiles = [];
502
+ for (const file of fileArray) {
503
+ try {
504
+ const fullPath = path__namespace.join(repoPath, file);
505
+ const stats = await fs__namespace.stat(fullPath);
506
+ if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
507
+ largeFiles.push(file);
508
+ }
509
+ } catch (e) {
510
+ }
511
+ }
512
+ if (largeFiles.length > 0) {
513
+ try {
514
+ await git.raw(["lfs", "install"]);
515
+ for (const file of largeFiles) {
516
+ await git.raw(["lfs", "track", file]);
517
+ }
518
+ if (!fileArray.includes(".gitattributes")) {
519
+ fileArray.push(".gitattributes");
520
+ }
521
+ } catch (e) {
522
+ console.warn("Git LFS not available or failed to track large files:", e);
523
+ }
524
+ }
525
+ }
526
+ const chunkSize = 50;
527
+ for (let i = 0; i < fileArray.length; i += chunkSize) {
528
+ const chunk = fileArray.slice(i, i + chunkSize);
529
+ await git.add(chunk);
530
+ }
531
+ return true;
532
+ }
533
+ async function unstageFiles(sessionId, files) {
534
+ const { git } = getGitSession(sessionId);
535
+ const fileArray = Array.isArray(files) ? files : [files];
536
+ const chunkSize = 50;
537
+ for (let i = 0; i < fileArray.length; i += chunkSize) {
538
+ const chunk = fileArray.slice(i, i + chunkSize);
539
+ await git.reset(["--", ...chunk]);
540
+ }
541
+ return true;
542
+ }
543
+ async function commitChanges(sessionId, message) {
544
+ const { git } = getGitSession(sessionId);
545
+ return await git.commit(message);
546
+ }
547
+ function injectAuthToken(sessionId, remoteUrl) {
548
+ const token = getToken(sessionId);
549
+ if (!token) return remoteUrl;
550
+ if (remoteUrl.includes("github.com")) {
551
+ let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
552
+ if (cleanUrl.startsWith("https://github.com/")) {
553
+ return cleanUrl.replace("https://github.com/", `https://x-access-token:${token}@github.com/`);
554
+ }
555
+ }
556
+ return remoteUrl;
557
+ }
558
+ async function pushToRemote(sessionId, remote, branch) {
559
+ const { git } = getGitSession(sessionId);
560
+ const remotes = await getRemotes(sessionId);
561
+ const remoteObj = remotes.find((r) => r.name === remote);
562
+ const tempGit = git.env({
563
+ ...process.env,
564
+ GCM_INTERACTIVE: "false",
565
+ GIT_TERMINAL_PROMPT: "0",
566
+ GIT_ASKPASS: "echo"
567
+ });
568
+ try {
569
+ if (remoteObj && remoteObj.refs.push) {
570
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
571
+ return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, branch]);
572
+ }
573
+ return await tempGit.raw(["-c", "credential.helper=", "push", remote, branch]);
574
+ } catch (err) {
575
+ const status = await git.status();
576
+ if (status.ahead === 0) {
577
+ console.warn("Git push threw an error but status is ahead 0. Push actually succeeded!", err);
578
+ return { success: true, warning: err.message };
579
+ }
580
+ throw err;
581
+ }
582
+ }
583
+ async function pullFromRemote(sessionId, remote, branch) {
584
+ const { git } = getGitSession(sessionId);
585
+ const remotes = await getRemotes(sessionId);
586
+ const remoteObj = remotes.find((r) => r.name === remote);
587
+ const options = { "--allow-unrelated-histories": null };
588
+ if (remoteObj && remoteObj.refs.fetch) {
589
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
590
+ return await git.pull(authUrl, branch, options);
591
+ }
592
+ return await git.pull(remote, branch, options);
593
+ }
594
+ async function resetToRemote(sessionId, remote, branch) {
595
+ const { git } = getGitSession(sessionId);
596
+ const remotes = await getRemotes(sessionId);
597
+ const remoteObj = remotes.find((r) => r.name === remote);
598
+ if (remoteObj && remoteObj.refs.fetch) {
599
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
600
+ await git.fetch(authUrl, branch);
601
+ return await git.reset(["--hard", "FETCH_HEAD"]);
602
+ } else {
603
+ await git.fetch(remote, branch);
604
+ return await git.reset(["--hard", `${remote}/${branch}`]);
605
+ }
606
+ }
607
+ async function fetchRemote(sessionId) {
608
+ const { git } = getGitSession(sessionId);
609
+ return await git.fetch();
610
+ }
611
+ async function getBranches(sessionId) {
612
+ const { git } = getGitSession(sessionId);
613
+ try {
614
+ const remotes = await git.getRemotes(true);
615
+ if (remotes.length > 0) {
616
+ const remoteObj = remotes[0];
617
+ const tempGit = simpleGit.simpleGit(getGitSession(sessionId).repoPath).env({
618
+ GCM_INTERACTIVE: "false",
619
+ GIT_TERMINAL_PROMPT: "0",
620
+ GIT_ASKPASS: "echo"
621
+ });
622
+ if (remoteObj.refs.fetch) {
623
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
624
+ await tempGit.fetch(authUrl, { "--prune": null });
625
+ } else {
626
+ await tempGit.fetch(remoteObj.name, { "--prune": null });
627
+ }
628
+ }
629
+ } catch (e) {
630
+ }
631
+ let branches = await git.branch(["-a"]);
632
+ if (branches.all.length === 0) {
633
+ try {
634
+ await git.raw([
635
+ "-c",
636
+ "user.name=gitelectron",
637
+ "-c",
638
+ "user.email=noreply@gitelectron.local",
639
+ "commit",
640
+ "--allow-empty",
641
+ "-m",
642
+ "Initial repository setup"
643
+ ]);
644
+ branches = await git.branch(["-a"]);
645
+ } catch (e) {
646
+ console.error("Failed to create initial empty commit on getBranches", e);
647
+ }
648
+ }
649
+ const allBranches = /* @__PURE__ */ new Set();
650
+ branches.all.forEach((b) => {
651
+ if (b.startsWith("remotes/")) {
652
+ const parts = b.split("/");
653
+ if (parts.length > 2) {
654
+ allBranches.add(parts.slice(2).join("/"));
655
+ }
656
+ } else {
657
+ allBranches.add(b);
658
+ }
659
+ });
660
+ branches.all = Array.from(allBranches);
661
+ return JSON.parse(JSON.stringify(branches));
662
+ }
663
+ async function checkoutBranch(sessionId, branch) {
664
+ const { git } = getGitSession(sessionId);
665
+ return await git.checkout(branch);
666
+ }
667
+ async function createBranch(sessionId, branch, baseBranch) {
668
+ const { git } = getGitSession(sessionId);
669
+ let res;
670
+ if (baseBranch) {
671
+ res = await git.checkoutBranch(branch, baseBranch);
672
+ } else {
673
+ res = await git.checkoutLocalBranch(branch);
674
+ }
675
+ try {
676
+ const remotes = await git.getRemotes(true);
677
+ if (remotes && remotes.length > 0) {
678
+ const remoteName = remotes[0].name;
679
+ await git.addConfig(`branch.${branch}.remote`, remoteName);
680
+ await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
681
+ const remoteObj = remotes[0];
682
+ if (remoteObj && remoteObj.refs.push) {
683
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
684
+ await git.push(authUrl, branch);
685
+ } else {
686
+ await git.push(remoteName, branch);
687
+ }
688
+ }
689
+ } catch (e) {
690
+ console.error("Failed to push and track new branch", e);
691
+ }
692
+ return res;
693
+ }
694
+ async function addRemote(sessionId, name, url) {
695
+ const { git } = getGitSession(sessionId);
696
+ return await git.addRemote(name, url);
697
+ }
698
+ async function pushAllBranches(sessionId, remote) {
699
+ const { git } = getGitSession(sessionId);
700
+ const remotes = await getRemotes(sessionId);
701
+ const remoteObj = remotes.find((r) => r.name === remote);
702
+ const tempGit = git.env({
703
+ ...process.env,
704
+ GCM_INTERACTIVE: "false",
705
+ GIT_TERMINAL_PROMPT: "0",
706
+ GIT_ASKPASS: "echo"
707
+ });
708
+ try {
709
+ if (remoteObj && remoteObj.refs.push) {
710
+ const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
711
+ return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
712
+ }
713
+ return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
714
+ } catch (err) {
715
+ const status = await git.status();
716
+ if (status.ahead === 0) {
717
+ console.warn("Git push --all threw an error but current branch is pushed.", err);
718
+ return { success: true, warning: err.message };
719
+ }
720
+ throw err;
721
+ }
722
+ }
723
+ async function getCommitHistory(sessionId) {
724
+ const { git } = getGitSession(sessionId);
725
+ const log = await git.log(["--all", "--name-status"]);
726
+ return JSON.parse(JSON.stringify(log));
727
+ }
728
+ async function getDeletedHistory(sessionId) {
729
+ const { git } = getGitSession(sessionId);
730
+ try {
731
+ const log = await git.log(["--all", "--diff-filter=D", "--name-status", "-n", "20"]);
732
+ return JSON.parse(JSON.stringify(log));
733
+ } catch (e) {
734
+ return { all: [] };
735
+ }
736
+ }
737
+ async function getRemotes(sessionId) {
738
+ const { git } = getGitSession(sessionId);
739
+ const remotes = await git.getRemotes(true);
740
+ return JSON.parse(JSON.stringify(remotes));
741
+ }
742
+ async function getFileDiff(sessionId, file, staged = false) {
743
+ const { git } = getGitSession(sessionId);
744
+ const args = staged ? ["diff", "--cached", "--", file] : ["diff", "--", file];
745
+ return await git.raw(args);
746
+ }
747
+ async function getGitConfig(sessionId, key, global = false) {
748
+ const { git } = getGitSession(sessionId);
749
+ try {
750
+ const args = ["config"];
751
+ if (global) args.push("--global");
752
+ args.push("--get", key);
753
+ const value = await git.raw(args);
754
+ return value.trim();
755
+ } catch {
756
+ return "";
757
+ }
758
+ }
759
+ async function setGitConfig(sessionId, key, value, global = false) {
760
+ const { git } = getGitSession(sessionId);
761
+ const args = ["config"];
762
+ if (global) args.push("--global");
763
+ args.push(key, value);
764
+ await git.raw(args);
765
+ }
766
+ async function readFile(sessionId, file) {
767
+ const { repoPath } = getGitSession(sessionId);
768
+ const fullPath = path__namespace.join(repoPath, file);
769
+ return await fs__namespace.readFile(fullPath, "utf8");
770
+ }
771
+ async function writeFile(sessionId, file, content) {
772
+ const { repoPath } = getGitSession(sessionId);
773
+ const fullPath = path__namespace.join(repoPath, file);
774
+ await fs__namespace.writeFile(fullPath, content, "utf8");
775
+ }
776
+ async function compareExternalFile(sessionId, sourcePath, repoRelativePath) {
777
+ const { repoPath } = getGitSession(sessionId);
778
+ const fullRepoPath = path__namespace.join(repoPath, repoRelativePath);
779
+ const sourceContent = await fs__namespace.readFile(sourcePath, "utf8");
780
+ let repoContent = null;
781
+ try {
782
+ repoContent = await fs__namespace.readFile(fullRepoPath, "utf8");
783
+ } catch {
784
+ }
785
+ return { sourceContent, repoContent };
786
+ }
787
+ async function copyExternalFiles(sessionId, files, destinationDir) {
788
+ const { repoPath } = getGitSession(sessionId);
789
+ const destDirFull = path__namespace.join(repoPath, destinationDir);
790
+ try {
791
+ await fs__namespace.mkdir(destDirFull, { recursive: true });
792
+ } catch (e) {
793
+ console.error("Failed to create destination dir", e);
794
+ }
795
+ const copiedFiles = [];
796
+ for (const file of files) {
797
+ const filename = path__namespace.basename(file);
798
+ const destFileFull = path__namespace.join(destDirFull, filename);
799
+ await fs__namespace.copyFile(file, destFileFull);
800
+ copiedFiles.push(path__namespace.join(destinationDir, filename).replace(/\\/g, "/"));
801
+ }
802
+ return copiedFiles;
803
+ }
804
+ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch, destinationPath) {
805
+ const token = getToken(sessionId);
806
+ if (!token) throw new Error("Not authenticated");
807
+ const tempId = crypto.randomUUID();
808
+ const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
809
+ await fs__namespace.mkdir(workspacePath, { recursive: true });
810
+ const git = simpleGit.simpleGit(workspacePath);
811
+ await git.init();
812
+ let authUrl = remoteUrl;
813
+ if (remoteUrl.includes("github.com")) {
814
+ let cleanUrl = remoteUrl.replace("git@github.com:", "https://github.com/");
815
+ if (cleanUrl.startsWith("https://github.com/")) {
816
+ authUrl = cleanUrl.replace(
817
+ "https://github.com/",
818
+ `https://x-access-token:${token}@github.com/`
819
+ );
820
+ }
821
+ }
822
+ await git.addRemote("origin", authUrl);
823
+ const activeAccount = getActiveAccount(sessionId);
824
+ if (activeAccount) {
825
+ const email = `${activeAccount.id}+${activeAccount.username}@users.noreply.github.com`;
826
+ await git.addConfig("user.name", activeAccount.username);
827
+ await git.addConfig("user.email", email);
828
+ } else {
829
+ await git.addConfig("user.name", "gitelectron");
830
+ await git.addConfig("user.email", "noreply@gitelectron.local");
831
+ }
832
+ await git.raw(["sparse-checkout", "init", "--cone"]);
833
+ const sparsePath = destinationPath === "/" || destinationPath === "" ? "" : destinationPath;
834
+ await git.raw(["sparse-checkout", "set", sparsePath]);
835
+ const branchToFetch = baseBranch || branch;
836
+ try {
837
+ await git.fetch(["--depth", "1", "origin", branchToFetch]);
838
+ await git.raw(["checkout", "-b", branchToFetch, `origin/${branchToFetch}`]);
839
+ if (baseBranch && branch !== baseBranch) {
840
+ await git.checkoutLocalBranch(branch);
841
+ }
842
+ } catch (e) {
843
+ if (baseBranch) {
844
+ throw new Error(`Failed to fetch or checkout branch ${branchToFetch}: ` + e.message);
845
+ } else {
846
+ await git.checkout(["--orphan", branch]);
847
+ }
848
+ }
849
+ return workspacePath;
850
+ }
851
+ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationPath) {
852
+ const destDirFull = path__namespace.join(workspacePath, destinationPath === "/" ? "" : destinationPath);
853
+ await fs__namespace.mkdir(destDirFull, { recursive: true });
854
+ for (const source of sourceFiles) {
855
+ const stats = await fs__namespace.stat(source);
856
+ if (stats.isDirectory()) {
857
+ await fs__namespace.cp(source, path__namespace.join(destDirFull, path__namespace.basename(source)), { recursive: true });
858
+ } else {
859
+ await fs__namespace.copyFile(source, path__namespace.join(destDirFull, path__namespace.basename(source)));
860
+ }
861
+ }
862
+ const git = simpleGit.simpleGit(workspacePath);
863
+ const status = await git.status();
864
+ return {
865
+ modified: status.modified,
866
+ newFiles: [...status.not_added, ...status.created]
867
+ };
868
+ }
869
+ async function getUniversalDiff(workspacePath, filePath) {
870
+ const git = simpleGit.simpleGit(workspacePath);
871
+ try {
872
+ const diff = await git.raw(["diff", "--", filePath]);
873
+ return diff;
874
+ } catch (e) {
875
+ return "";
876
+ }
877
+ }
878
+ async function commitUniversalWorkspace(workspacePath, message, branch) {
879
+ const git = simpleGit.simpleGit(workspacePath).env({
880
+ ...process.env,
881
+ GCM_INTERACTIVE: "false",
882
+ GIT_TERMINAL_PROMPT: "0",
883
+ GIT_ASKPASS: "echo"
884
+ });
885
+ await git.add(["--sparse", "."]);
886
+ await git.commit(message);
887
+ try {
888
+ await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
889
+ } catch (err) {
890
+ const status = await git.status();
891
+ if (status.ahead === 0) {
892
+ console.warn("Universal Git push threw an error but branch is pushed.", err);
893
+ return;
894
+ }
895
+ throw err;
896
+ }
897
+ }
898
+ async function cleanupUniversalWorkspace(workspacePath) {
899
+ try {
900
+ await fs__namespace.rm(workspacePath, { recursive: true, force: true });
901
+ } catch (e) {
902
+ console.error("Failed to cleanup universal workspace", e);
903
+ }
904
+ }
905
+ async function executeRawCommand(sessionId, args) {
906
+ const { git } = getGitSession(sessionId);
907
+ return await git.raw(args);
908
+ }
909
+ const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
910
+ 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.
911
+ You will receive the Git Status and the Raw Error Message.
912
+
913
+ Your job is to:
914
+ 1. Provide a short internal status message in 'explanation' (e.g. "Aborting chaotic merge and pulling with strategy ours...").
915
+ 2. Provide the EXACT terminal git commands required to automatically resolve this issue.
916
+ 3. You must use standard git commands to force the issue to resolve so the user can successfully push.
917
+ - If there is a merge conflict, resolve it aggressively (e.g., 'git merge --abort', 'git config pull.rebase false', 'git pull --no-edit -X ours', 'git add .', 'git commit -m "Auto-resolve conflict"').
918
+ - If a push is rejected due to a diverged branch, pull the changes, merge them, and commit.
919
+ - DO NOT use interactive commands (like 'git rebase -i' or 'nano' or 'vim').
920
+ - DO NOT suggest editing files manually. You must use terminal commands to manipulate the git state directly.
921
+ Return the response strictly as a JSON object with this shape, and absolutely no markdown formatting outside of the JSON:
922
+ {
923
+ "explanation": "Internal status message...",
924
+ "commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
925
+ }`;
926
+ async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
927
+ if (!apiKeyInput) {
928
+ if (provider === "gemini") {
929
+ apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
930
+ } else {
931
+ apiKeyInput = "deepseek-hardcoded-key-if-applicable";
932
+ }
933
+ }
934
+ const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
935
+ const prompt = `
936
+ GIT STATUS:
937
+ ${JSON.stringify(gitStatus, null, 2)}
938
+
939
+ REPOSITORY PATH:
940
+ ${repoPath}
941
+
942
+ RAW ERROR MESSAGE:
943
+ ${errorMsg}
944
+ `;
945
+ let lastError = null;
946
+ if (githubToken) {
947
+ try {
948
+ const openai = new OpenAI({
949
+ apiKey: githubToken,
950
+ baseURL: "https://models.inference.ai.azure.com"
951
+ });
952
+ const response = await openai.chat.completions.create({
953
+ model: "gpt-4o-mini",
954
+ messages: [
955
+ { role: "system", content: SYSTEM_PROMPT },
956
+ { role: "user", content: prompt }
957
+ ],
958
+ response_format: { type: "json_object" }
959
+ });
960
+ let responseText = response.choices[0].message.content || "{}";
961
+ responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
962
+ const parsed = JSON.parse(responseText);
963
+ if (parsed.explanation && Array.isArray(parsed.commandsToRun)) {
964
+ console.log("[AI Service] Successfully used GitHub Copilot Models API!");
965
+ return parsed;
966
+ }
967
+ } catch (e) {
968
+ console.warn(`[AI Service] GitHub Copilot Models fallback failed: ${e.message}`);
969
+ lastError = e;
970
+ }
971
+ }
972
+ for (const apiKey of keys) {
973
+ let responseText = "";
974
+ try {
975
+ if (provider === "gemini") {
976
+ const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
977
+ const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
978
+ const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
979
+ responseText = result.response.text();
980
+ } else if (provider === "deepseek") {
981
+ const openai = new OpenAI({
982
+ apiKey,
983
+ baseURL: "https://router.huggingface.co/v1"
984
+ });
985
+ const response = await openai.chat.completions.create({
986
+ model: "deepseek-ai/DeepSeek-V4-Flash:novita",
987
+ messages: [
988
+ { role: "system", content: SYSTEM_PROMPT },
989
+ { role: "user", content: prompt }
990
+ ],
991
+ response_format: { type: "json_object" }
992
+ });
993
+ responseText = response.choices[0].message.content || "{}";
994
+ } else {
995
+ throw new Error(`Unsupported AI Provider: ${provider}`);
996
+ }
997
+ responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
998
+ const parsed = JSON.parse(responseText);
999
+ if (!parsed.explanation || !Array.isArray(parsed.commandsToRun)) {
1000
+ throw new Error("AI returned an invalid response format.");
1001
+ }
1002
+ return parsed;
1003
+ } catch (e) {
1004
+ console.warn(
1005
+ `[AI Service] Provider ${provider} failed with key ${apiKey.substring(0, 5)}...: ${e.message}. Trying next if available.`
1006
+ );
1007
+ lastError = e;
1008
+ }
1009
+ }
1010
+ throw new Error(
1011
+ `AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
1012
+ );
1013
+ }
1014
+ let currentWatcher = null;
1015
+ let syncTimeout = null;
1016
+ let isAutoSyncEnabled = false;
1017
+ const DEBOUNCE_MS = 3e3;
1018
+ const changedFiles = /* @__PURE__ */ new Set();
1019
+ function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
1020
+ isAutoSyncEnabled = enabled;
1021
+ if (currentWatcher) {
1022
+ currentWatcher.close();
1023
+ currentWatcher = null;
1024
+ }
1025
+ if (syncTimeout) {
1026
+ clearTimeout(syncTimeout);
1027
+ }
1028
+ changedFiles.clear();
1029
+ if (!enabled) {
1030
+ onLog("Auto-Sync disabled.");
1031
+ return;
1032
+ }
1033
+ onLog("⚡ Auto-Sync enabled. Watching for changes...");
1034
+ currentWatcher = chokidar.watch(repoPath, {
1035
+ ignored: [
1036
+ /(^|[\/\\])\../,
1037
+ // ignore dotfiles (.git, .DS_Store)
1038
+ /node_modules/,
1039
+ /dist/,
1040
+ /build/
1041
+ ],
1042
+ persistent: true,
1043
+ ignoreInitial: true
1044
+ });
1045
+ currentWatcher.on("all", (_event, filePath) => {
1046
+ if (!isAutoSyncEnabled) return;
1047
+ const relativePath = path__namespace.relative(repoPath, filePath).replace(/\\/g, "/");
1048
+ changedFiles.add(relativePath);
1049
+ if (syncTimeout) {
1050
+ clearTimeout(syncTimeout);
1051
+ }
1052
+ onLog(`Detected change in ${relativePath}. Waiting for 3 seconds...`);
1053
+ syncTimeout = setTimeout(() => {
1054
+ triggerSyncSequence(sessionId, repoPath, onLog);
1055
+ }, DEBOUNCE_MS);
1056
+ });
1057
+ }
1058
+ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
1059
+ if (changedFiles.size === 0) return;
1060
+ const filesToSync = Array.from(changedFiles);
1061
+ changedFiles.clear();
1062
+ try {
1063
+ onLog(`Staging ${filesToSync.length} file(s)...`);
1064
+ await stageFiles(sessionId, filesToSync);
1065
+ const status = await getStatus(sessionId);
1066
+ const branch = status.current || "main";
1067
+ const commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
1068
+ onLog(`Committing: "${commitMsg}"...`);
1069
+ await commitChanges(sessionId, commitMsg);
1070
+ onLog(`Pushing to ${branch}...`);
1071
+ await pushToRemote(sessionId, "origin", branch);
1072
+ onLog(`✅ Successfully pushed auto-sync commit!`);
1073
+ } catch (error) {
1074
+ onLog(`❌ Auto-sync failed: ${error.message}`);
1075
+ }
1076
+ }
1077
+ const icon = path.join(__dirname, "../../resources/icon.png");
1078
+ function createWindow() {
1079
+ const mainWindow = new electron.BrowserWindow({
1080
+ width: 1100,
1081
+ height: 800,
1082
+ minWidth: 850,
1083
+ minHeight: 600,
1084
+ show: false,
1085
+ autoHideMenuBar: true,
1086
+ title: "GIT ELECTRON",
1087
+ ...process.platform === "linux" ? { icon } : {},
1088
+ webPreferences: {
1089
+ preload: path.join(__dirname, "../preload/index.js"),
1090
+ sandbox: false
1091
+ }
1092
+ });
1093
+ const sessionId = mainWindow.webContents.id;
1094
+ initSession(sessionId);
1095
+ mainWindow.on("ready-to-show", () => {
1096
+ mainWindow.show();
1097
+ });
1098
+ mainWindow.on("closed", () => {
1099
+ cleanupSession(sessionId);
1100
+ cleanupGitSession(sessionId);
1101
+ });
1102
+ mainWindow.webContents.setWindowOpenHandler((details) => {
1103
+ electron.shell.openExternal(details.url);
1104
+ return { action: "deny" };
1105
+ });
1106
+ if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
1107
+ mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
1108
+ } else {
1109
+ mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
1110
+ }
1111
+ }
1112
+ const gotTheLock = electron.app.requestSingleInstanceLock();
1113
+ if (!gotTheLock) {
1114
+ electron.app.quit();
1115
+ process.exit(0);
1116
+ }
1117
+ let initialRepo = null;
1118
+ const cwdIndex = process.argv.indexOf("--cwd");
1119
+ if (cwdIndex !== -1 && process.argv.length > cwdIndex + 1) {
1120
+ initialRepo = process.argv[cwdIndex + 1];
1121
+ }
1122
+ electron.app.on("second-instance", (_event, commandLine, workingDirectory) => {
1123
+ const windows = electron.BrowserWindow.getAllWindows();
1124
+ if (windows.length > 0) {
1125
+ if (windows[0].isMinimized()) windows[0].restore();
1126
+ windows[0].focus();
1127
+ const idx = commandLine.indexOf("--cwd");
1128
+ let repoPath = null;
1129
+ if (idx !== -1 && commandLine.length > idx + 1) {
1130
+ repoPath = commandLine[idx + 1];
1131
+ } else if (workingDirectory) {
1132
+ repoPath = workingDirectory;
1133
+ }
1134
+ if (repoPath) {
1135
+ windows[0].webContents.send("set-cli-repo", repoPath);
1136
+ }
1137
+ }
1138
+ });
1139
+ if (process.defaultApp) {
1140
+ if (process.argv.length >= 2) {
1141
+ electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [
1142
+ require("path").resolve(process.argv[1])
1143
+ ]);
1144
+ }
1145
+ } else {
1146
+ electron.app.setAsDefaultProtocolClient("gitelectron");
1147
+ }
1148
+ electron.app.whenReady().then(async () => {
1149
+ utils.electronApp.setAppUserModelId("com.electron");
1150
+ await loadAccounts();
1151
+ electron.app.on("browser-window-created", (_, window) => {
1152
+ utils.optimizer.watchWindowShortcuts(window);
1153
+ });
1154
+ electron.ipcMain.on("ping", () => console.log("pong"));
1155
+ electron.ipcMain.handle("new-window", () => {
1156
+ createWindow();
1157
+ });
1158
+ electron.ipcMain.handle("get-accounts", async (event) => {
1159
+ return await getAccounts(event.sender.id);
1160
+ });
1161
+ electron.ipcMain.handle("switch-account", async (event, accountId) => {
1162
+ return await switchAccount(event.sender.id, accountId);
1163
+ });
1164
+ electron.ipcMain.handle("remove-account", async (_, accountId) => {
1165
+ return await removeAccount(accountId);
1166
+ });
1167
+ electron.ipcMain.handle("get-cli-repo", () => {
1168
+ return initialRepo;
1169
+ });
1170
+ electron.ipcMain.handle("select-directory", async (event) => {
1171
+ const sessionId = event.sender.id;
1172
+ const result = await electron.dialog.showOpenDialog({
1173
+ properties: ["openDirectory"]
1174
+ });
1175
+ if (result.canceled || result.filePaths.length === 0) {
1176
+ return null;
1177
+ }
1178
+ const path2 = result.filePaths[0];
1179
+ const isRepo = await isGitRepo(path2);
1180
+ if (isRepo) {
1181
+ initGit(sessionId, path2);
1182
+ }
1183
+ return { path: path2, isRepo };
1184
+ });
1185
+ electron.ipcMain.handle("set-repo-path", async (event, path2) => {
1186
+ const sessionId = event.sender.id;
1187
+ const isRepo = await isGitRepo(path2);
1188
+ if (isRepo) {
1189
+ initGit(sessionId, path2);
1190
+ }
1191
+ return { path: path2, isRepo };
1192
+ });
1193
+ electron.ipcMain.handle("init-repo", async (event, path2) => {
1194
+ return await initializeRepo(event.sender.id, path2);
1195
+ });
1196
+ electron.ipcMain.handle("get-status", async (event) => {
1197
+ return await getStatus(event.sender.id);
1198
+ });
1199
+ electron.ipcMain.handle("stage-files", async (event, files) => {
1200
+ return await stageFiles(event.sender.id, files);
1201
+ });
1202
+ electron.ipcMain.handle("unstage-files", async (event, files) => {
1203
+ return await unstageFiles(event.sender.id, files);
1204
+ });
1205
+ electron.ipcMain.handle("commit-changes", async (event, message) => {
1206
+ return await commitChanges(event.sender.id, message);
1207
+ });
1208
+ electron.ipcMain.handle("push-to-remote", async (event, remote, branch) => {
1209
+ return await pushToRemote(event.sender.id, remote, branch);
1210
+ });
1211
+ electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
1212
+ return await pullFromRemote(event.sender.id, remote, branch);
1213
+ });
1214
+ electron.ipcMain.handle("reset-to-remote", async (event, remote, branch) => {
1215
+ return await resetToRemote(event.sender.id, remote, branch);
1216
+ });
1217
+ electron.ipcMain.handle("fetch-remote", async (event) => {
1218
+ return await fetchRemote(event.sender.id);
1219
+ });
1220
+ electron.ipcMain.handle("get-branches", async (event) => {
1221
+ return await getBranches(event.sender.id);
1222
+ });
1223
+ electron.ipcMain.handle("checkout-branch", async (event, branch) => {
1224
+ return await checkoutBranch(event.sender.id, branch);
1225
+ });
1226
+ electron.ipcMain.handle("create-branch", async (event, branch, baseBranch) => {
1227
+ return await createBranch(event.sender.id, branch, baseBranch);
1228
+ });
1229
+ electron.ipcMain.handle("get-commit-history", async (event) => {
1230
+ return await getCommitHistory(event.sender.id);
1231
+ });
1232
+ electron.ipcMain.handle("get-deleted-history", async (event) => {
1233
+ return await getDeletedHistory(event.sender.id);
1234
+ });
1235
+ electron.ipcMain.handle("get-auth-status", async (event) => {
1236
+ return await getAuthStatus(event.sender.id);
1237
+ });
1238
+ electron.ipcMain.handle("start-device-flow", async (event) => {
1239
+ const data = await startDeviceFlow(event.sender.id);
1240
+ electron.shell.openExternal(data.verification_uri_complete);
1241
+ return data;
1242
+ });
1243
+ electron.ipcMain.handle("poll-device-flow", async (event, deviceCode, interval) => {
1244
+ return await pollDeviceFlow(event.sender.id, deviceCode, interval);
1245
+ });
1246
+ electron.ipcMain.handle("logout-github", async (event) => {
1247
+ return await logout(event.sender.id);
1248
+ });
1249
+ electron.ipcMain.handle("verify-push-permission", async (event, remoteUrl) => {
1250
+ return await verifyRepositoryPermission(event.sender.id, remoteUrl);
1251
+ });
1252
+ electron.ipcMain.handle("get-remotes", async (event) => {
1253
+ return await getRemotes(event.sender.id);
1254
+ });
1255
+ electron.ipcMain.handle("get-file-diff", async (event, file, staged) => {
1256
+ return await getFileDiff(event.sender.id, file, staged);
1257
+ });
1258
+ electron.ipcMain.handle("get-git-config", async (event, key, global) => {
1259
+ return await getGitConfig(event.sender.id, key, global);
1260
+ });
1261
+ electron.ipcMain.handle("set-git-config", async (event, key, value, global) => {
1262
+ return await setGitConfig(event.sender.id, key, value, global);
1263
+ });
1264
+ electron.ipcMain.handle("read-file", async (event, file) => {
1265
+ return await readFile(event.sender.id, file);
1266
+ });
1267
+ electron.ipcMain.handle("write-file", async (event, file, content) => {
1268
+ return await writeFile(event.sender.id, file, content);
1269
+ });
1270
+ electron.ipcMain.handle("get-user-repositories", async (event) => {
1271
+ return await getUserRepositories(event.sender.id);
1272
+ });
1273
+ electron.ipcMain.handle(
1274
+ "create-github-repository",
1275
+ async (event, name, description, isPrivate, autoInit, gitignoreTemplate, licenseTemplate) => {
1276
+ return await createGitHubRepository(
1277
+ event.sender.id,
1278
+ name,
1279
+ description,
1280
+ isPrivate,
1281
+ autoInit,
1282
+ gitignoreTemplate,
1283
+ licenseTemplate
1284
+ );
1285
+ }
1286
+ );
1287
+ electron.ipcMain.handle("add-remote", async (event, name, url) => {
1288
+ return await addRemote(event.sender.id, name, url);
1289
+ });
1290
+ electron.ipcMain.handle("push-all-branches", async (event, remote) => {
1291
+ return await pushAllBranches(event.sender.id, remote);
1292
+ });
1293
+ electron.ipcMain.handle("open-external", async (_, url) => {
1294
+ await electron.shell.openExternal(url);
1295
+ });
1296
+ electron.ipcMain.handle(
1297
+ "compare-external-file",
1298
+ async (event, sourcePath, repoRelativePath) => {
1299
+ return await compareExternalFile(event.sender.id, sourcePath, repoRelativePath);
1300
+ }
1301
+ );
1302
+ electron.ipcMain.handle("copy-external-files", async (event, files, destinationDir) => {
1303
+ return await copyExternalFiles(event.sender.id, files, destinationDir);
1304
+ });
1305
+ electron.ipcMain.handle("get-collaborators", async (event, remoteUrl) => {
1306
+ return await getCollaborators(event.sender.id, remoteUrl);
1307
+ });
1308
+ electron.ipcMain.handle("add-collaborator", async (event, remoteUrl, username) => {
1309
+ return await addCollaborator(event.sender.id, remoteUrl, username);
1310
+ });
1311
+ electron.ipcMain.handle(
1312
+ "remove-collaborator",
1313
+ async (event, remoteUrl, usernameOrId, isInvitation) => {
1314
+ return await removeCollaborator(
1315
+ event.sender.id,
1316
+ remoteUrl,
1317
+ usernameOrId,
1318
+ isInvitation
1319
+ );
1320
+ }
1321
+ );
1322
+ electron.ipcMain.handle("search-github-user", async (event, username) => {
1323
+ return await searchGitHubUser(event.sender.id, username);
1324
+ });
1325
+ electron.ipcMain.handle("get-user-invitations", async (event) => {
1326
+ return await getUserInvitations(event.sender.id);
1327
+ });
1328
+ electron.ipcMain.handle("accept-invitation", async (event, invitationId) => {
1329
+ return await acceptInvitation(event.sender.id, invitationId);
1330
+ });
1331
+ electron.ipcMain.handle("decline-invitation", async (event, invitationId) => {
1332
+ return await declineInvitation(event.sender.id, invitationId);
1333
+ });
1334
+ electron.ipcMain.handle("select-universal-files", async (_event, isDirectory) => {
1335
+ const properties = isDirectory ? ["openDirectory", "multiSelections"] : ["openFile", "multiSelections"];
1336
+ const result = await electron.dialog.showOpenDialog({ properties });
1337
+ return result.canceled ? null : result.filePaths;
1338
+ });
1339
+ electron.ipcMain.handle("get-repository-branches", async (event, owner, repo) => {
1340
+ return await getRepositoryBranches(event.sender.id, owner, repo);
1341
+ });
1342
+ electron.ipcMain.handle(
1343
+ "setup-universal-workspace",
1344
+ async (event, remoteUrl, branch, baseBranch, dest) => {
1345
+ return await setupUniversalWorkspace(
1346
+ event.sender.id,
1347
+ remoteUrl,
1348
+ branch,
1349
+ baseBranch,
1350
+ dest
1351
+ );
1352
+ }
1353
+ );
1354
+ electron.ipcMain.handle(
1355
+ "check-universal-collisions",
1356
+ async (_event, workspacePath, sourceFiles, dest) => {
1357
+ return await checkUniversalCollisions(workspacePath, sourceFiles, dest);
1358
+ }
1359
+ );
1360
+ electron.ipcMain.handle("get-universal-diff", async (_event, workspacePath, file) => {
1361
+ return await getUniversalDiff(workspacePath, file);
1362
+ });
1363
+ electron.ipcMain.handle(
1364
+ "commit-universal-workspace",
1365
+ async (_event, workspacePath, msg, branch) => {
1366
+ return await commitUniversalWorkspace(workspacePath, msg, branch);
1367
+ }
1368
+ );
1369
+ electron.ipcMain.handle("cleanup-universal-workspace", async (_event, workspacePath) => {
1370
+ return await cleanupUniversalWorkspace(workspacePath);
1371
+ });
1372
+ electron.ipcMain.handle(
1373
+ "ask-ai",
1374
+ async (event, provider, apiKey, errorMsg, gitStatus, repoPath) => {
1375
+ const githubToken = getToken(event.sender.id) || void 0;
1376
+ return await analyzeGitError(
1377
+ provider,
1378
+ apiKey,
1379
+ errorMsg,
1380
+ gitStatus,
1381
+ repoPath,
1382
+ githubToken
1383
+ );
1384
+ }
1385
+ );
1386
+ electron.ipcMain.handle("execute-raw-git", async (event, args) => {
1387
+ return await executeRawCommand(event.sender.id, args);
1388
+ });
1389
+ electron.ipcMain.on("toggle-auto-sync", (event, repoPath, enabled) => {
1390
+ toggleAutoSync(event.sender.id, repoPath, enabled, (msg) => {
1391
+ event.sender.send("auto-sync-log", msg);
1392
+ });
1393
+ });
1394
+ electron.ipcMain.handle("get-app-version", () => {
1395
+ return electron.app.getVersion();
1396
+ });
1397
+ electron.ipcMain.handle("check-updates", async () => {
1398
+ try {
1399
+ const https = require("https");
1400
+ return new Promise((resolve) => {
1401
+ https.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
1402
+ let data = "";
1403
+ res.on("data", (chunk) => {
1404
+ data += chunk;
1405
+ });
1406
+ res.on("end", () => {
1407
+ try {
1408
+ const json = JSON.parse(data);
1409
+ resolve(json.version || null);
1410
+ } catch (e) {
1411
+ resolve(null);
1412
+ }
1413
+ });
1414
+ }).on("error", () => resolve(null));
1415
+ });
1416
+ } catch (e) {
1417
+ return null;
1418
+ }
1419
+ });
1420
+ electron.ipcMain.handle("install-update", async () => {
1421
+ return new Promise((resolve) => {
1422
+ const { spawn } = require("child_process");
1423
+ const cmdArgs = [
1424
+ "/c",
1425
+ "start",
1426
+ "cmd.exe",
1427
+ "/c",
1428
+ "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"
1429
+ ];
1430
+ const child = spawn("cmd.exe", cmdArgs, {
1431
+ detached: true,
1432
+ stdio: "ignore"
1433
+ });
1434
+ child.unref();
1435
+ resolve(true);
1436
+ setTimeout(() => {
1437
+ electron.app.exit(0);
1438
+ }, 500);
1439
+ });
1440
+ });
1441
+ createWindow();
1442
+ electron.app.on("activate", function() {
1443
+ if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
1444
+ });
1445
+ });
1446
+ electron.app.on("window-all-closed", () => {
1447
+ if (process.platform !== "darwin") {
1448
+ electron.app.quit();
1449
+ }
1450
+ });