claude-maestro 0.1.21 → 0.1.22

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.
Files changed (28) hide show
  1. package/out/main/index.js +313 -29
  2. package/out/preload/index.js +8 -1
  3. package/out/renderer/assets/{index-BDxfkk76.js → index-5VwvlSBH.js} +2 -2
  4. package/out/renderer/assets/{index-kJ0KF5bI.js → index-8y8_VEXe.js} +2 -2
  5. package/out/renderer/assets/{index-CIAp39oC.js → index-B3PqNdfC.js} +2 -2
  6. package/out/renderer/assets/{index-C2wfbMG1.js → index-BAbAYJ5Y.js} +2 -2
  7. package/out/renderer/assets/{index-DroXAl3A.js → index-BZcI6lAQ.js} +1 -1
  8. package/out/renderer/assets/{index-Cfvyl_8T.js → index-B_KGykK5.js} +3 -3
  9. package/out/renderer/assets/{index-Uh6FxvAQ.js → index-BlF1OgRv.js} +329 -53
  10. package/out/renderer/assets/{index-Bw6wDwNB.js → index-CCNlPk-t.js} +2 -2
  11. package/out/renderer/assets/{index-Ck4WZgFA.js → index-CVHp4WAz.js} +2 -2
  12. package/out/renderer/assets/{index-BPiKmKfX.js → index-CeEj811j.js} +5 -5
  13. package/out/renderer/assets/{index-CTyPr1hG.css → index-D62W3fL9.css} +123 -0
  14. package/out/renderer/assets/{index-C3_9l5Zo.js → index-D8JIi1r1.js} +2 -2
  15. package/out/renderer/assets/{index-DD6EoqPp.js → index-DBWvOvJX.js} +2 -2
  16. package/out/renderer/assets/{index-DGNONcNh.js → index-DC-oZjAl.js} +3 -3
  17. package/out/renderer/assets/{index-CdG7xnB7.js → index-DCdVxGT4.js} +2 -2
  18. package/out/renderer/assets/{index-BNIPHMhg.js → index-DTVTUfRt.js} +2 -2
  19. package/out/renderer/assets/{index-CZmL3oq-.js → index-DmASR2LM.js} +5 -5
  20. package/out/renderer/assets/{index-DzjUOrFM.js → index-KxHDPFk3.js} +5 -5
  21. package/out/renderer/assets/{index-CYo0nynQ.js → index-Tn8iVXu1.js} +4 -4
  22. package/out/renderer/assets/{index-Bf87-cF0.js → index-UF6qHegH.js} +5 -5
  23. package/out/renderer/assets/{index-D9lxbtli.js → index-Xg74Ilp9.js} +5 -5
  24. package/out/renderer/assets/{index-iIulTEbp.js → index-XkfWJ3Sj.js} +5 -5
  25. package/out/renderer/assets/{index-BDJGybQo.js → index-xFr-MERG.js} +2 -2
  26. package/out/renderer/assets/{index-Bm6BMufC.js → index-xKQyIF3m.js} +2 -2
  27. package/out/renderer/index.html +2 -2
  28. package/package.json +1 -1
package/out/main/index.js CHANGED
@@ -621,7 +621,7 @@ async function startMergeLeaveConflicts(baseFolder, branch, baseBranch) {
621
621
  const merging = await git(baseFolder, ["rev-parse", "--verify", "--quiet", "MERGE_HEAD"]);
622
622
  return { ok: false, conflict: merging.code === 0, output: res.output };
623
623
  }
624
- async function pushBranch(folder, branch) {
624
+ async function pushBranch(folder, branch, env) {
625
625
  const up = await git(folder, [
626
626
  "rev-parse",
627
627
  "--abbrev-ref",
@@ -631,15 +631,20 @@ async function pushBranch(folder, branch) {
631
631
  if (up.code !== 0) return null;
632
632
  const remote = up.stdout.trim().split("/")[0];
633
633
  if (!remote) return null;
634
- const res = await git(folder, ["push", remote, branch]);
634
+ const res = await git(folder, ["push", remote, branch], env);
635
635
  return { ok: res.code === 0, output: res.output };
636
636
  }
637
- function gh(cwd, args) {
637
+ function gh$1(cwd, args, env) {
638
638
  return new Promise((resolve2, reject) => {
639
639
  child_process.execFile(
640
640
  "gh",
641
641
  args,
642
- { cwd, windowsHide: true, maxBuffer: 8 * 1024 * 1024 },
642
+ {
643
+ cwd,
644
+ windowsHide: true,
645
+ maxBuffer: 8 * 1024 * 1024,
646
+ ...env ? { env: { ...process.env, ...env } } : {}
647
+ },
643
648
  (err, stdout, stderr) => {
644
649
  if (err && err.code === "ENOENT") {
645
650
  reject(
@@ -663,7 +668,7 @@ function extractPrUrl(text) {
663
668
  const m = text.match(/https:\/\/github\.com\/\S+\/pull\/\d+/);
664
669
  return m ? m[0] : "";
665
670
  }
666
- async function createPullRequest(folder, branch, baseBranch, title, body) {
671
+ async function createPullRequest(folder, branch, baseBranch, title, body, env) {
667
672
  const remote = await defaultRemote(folder);
668
673
  if (!remote) {
669
674
  return {
@@ -671,28 +676,21 @@ async function createPullRequest(folder, branch, baseBranch, title, body) {
671
676
  output: "This repository has no git remote, so there is nowhere to open a pull request. Add a GitHub remote (e.g. `git remote add origin <url>`) first."
672
677
  };
673
678
  }
674
- const push = await git(folder, ["push", "-u", remote, branch]);
679
+ const push = await git(folder, ["push", "-u", remote, branch], env);
675
680
  if (push.code !== 0) {
676
681
  return { ok: false, output: `Pushing "${branch}" to ${remote} failed:
677
682
  ${push.output}` };
678
683
  }
679
- const res = await gh(folder, [
680
- "pr",
681
- "create",
682
- "--base",
683
- baseBranch,
684
- "--head",
685
- branch,
686
- "--title",
687
- title,
688
- "--body",
689
- body
690
- ]);
684
+ const res = await gh$1(
685
+ folder,
686
+ ["pr", "create", "--base", baseBranch, "--head", branch, "--title", title, "--body", body],
687
+ env
688
+ );
691
689
  if (res.code === 0) {
692
690
  return { ok: true, url: extractPrUrl(res.stdout) || lastLine(res.stdout), output: res.output };
693
691
  }
694
692
  if (/already exists/i.test(res.output)) {
695
- const view = await gh(folder, ["pr", "view", branch, "--json", "url", "--jq", ".url"]);
693
+ const view = await gh$1(folder, ["pr", "view", branch, "--json", "url", "--jq", ".url"], env);
696
694
  const url = view.code === 0 ? extractPrUrl(view.stdout) || lastLine(view.stdout) : "";
697
695
  return {
698
696
  ok: true,
@@ -712,10 +710,18 @@ async function defaultRemote(folder) {
712
710
  if (remotes.length === 0) return null;
713
711
  return remotes.includes("origin") ? "origin" : remotes[0];
714
712
  }
715
- async function publishBranch(folder, branch) {
713
+ async function remoteUrl(folder) {
716
714
  const remote = await defaultRemote(folder);
717
715
  if (!remote) return null;
718
- const res = await git(folder, ["push", "-u", remote, branch]);
716
+ const res = await git(folder, ["remote", "get-url", remote]);
717
+ if (res.code !== 0) return null;
718
+ const url = res.stdout.trim();
719
+ return url || null;
720
+ }
721
+ async function publishBranch(folder, branch, env) {
722
+ const remote = await defaultRemote(folder);
723
+ if (!remote) return null;
724
+ const res = await git(folder, ["push", "-u", remote, branch], env);
719
725
  return { ok: res.code === 0, output: res.output };
720
726
  }
721
727
  async function dirtyCount(folder) {
@@ -751,6 +757,39 @@ async function branchExists(repoRoot, branch) {
751
757
  const res = await git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
752
758
  return res.code === 0;
753
759
  }
760
+ async function isValidBranchName(name) {
761
+ const n = name.trim();
762
+ if (!n) return false;
763
+ const res = await git(process.cwd(), ["check-ref-format", "--branch", n]);
764
+ return res.code === 0;
765
+ }
766
+ async function checkoutBranch(folder, branch) {
767
+ return git(folder, ["checkout", branch]);
768
+ }
769
+ async function createBranch(folder, branch, from) {
770
+ const args = ["checkout", "-b", branch, ...[]];
771
+ return git(folder, args);
772
+ }
773
+ async function worktreeBranches(repoRoot) {
774
+ const res = await git(repoRoot, ["worktree", "list", "--porcelain"]);
775
+ if (res.code !== 0) return [];
776
+ const out = [];
777
+ let cur = null;
778
+ for (const line of res.stdout.split(/\r?\n/)) {
779
+ if (line.startsWith("worktree ")) {
780
+ if (cur) out.push(cur);
781
+ cur = { path: line.slice("worktree ".length).trim().replace(/\\/g, "/"), branch: null };
782
+ } else if (line.startsWith("branch ") && cur) {
783
+ cur.branch = line.slice("branch ".length).trim().replace(/^refs\/heads\//, "");
784
+ }
785
+ }
786
+ if (cur) out.push(cur);
787
+ return out;
788
+ }
789
+ async function branchCheckedOutAt(repoRoot, branch) {
790
+ const wts = await worktreeBranches(repoRoot);
791
+ return wts.find((w) => w.branch === branch)?.path ?? null;
792
+ }
754
793
  async function listWorktreePaths(repoRoot) {
755
794
  const res = await git(repoRoot, ["worktree", "list", "--porcelain"]);
756
795
  if (res.code !== 0) return [];
@@ -975,6 +1014,68 @@ function excludeFilePathSync(folder) {
975
1014
  return null;
976
1015
  }
977
1016
  }
1017
+ function gh(args) {
1018
+ return new Promise((resolve) => {
1019
+ child_process.execFile("gh", args, { windowsHide: true, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
1020
+ const code = err ? err.code ?? 1 : 0;
1021
+ resolve({ code, stdout, stderr });
1022
+ });
1023
+ });
1024
+ }
1025
+ function stripAnsi$1(text) {
1026
+ const esc = String.fromCharCode(27);
1027
+ return text.replace(new RegExp(esc + "\\[[0-9;]*m", "g"), "");
1028
+ }
1029
+ async function listAccounts() {
1030
+ const res = await gh(["auth", "status"]);
1031
+ const text = stripAnsi$1(`${res.stdout}
1032
+ ${res.stderr}`);
1033
+ const accounts = [];
1034
+ let last = null;
1035
+ for (const line of text.split(/\r?\n/)) {
1036
+ const logged = line.match(/Logged in to (\S+) account (\S+)/);
1037
+ if (logged) {
1038
+ last = { host: logged[1], login: logged[2] };
1039
+ accounts.push(last);
1040
+ continue;
1041
+ }
1042
+ const activeMatch = line.match(/Active account:\s*(true|false)/i);
1043
+ if (activeMatch && last) {
1044
+ last.active = activeMatch[1].toLowerCase() === "true";
1045
+ }
1046
+ }
1047
+ return accounts;
1048
+ }
1049
+ async function tokenFor(host, login) {
1050
+ const res = await gh(["auth", "token", "--hostname", host, "--user", login]);
1051
+ if (res.code !== 0) return null;
1052
+ const token = res.stdout.trim();
1053
+ return token || null;
1054
+ }
1055
+ async function repoSlug(folder) {
1056
+ const url = await remoteUrl(folder);
1057
+ if (!url) return null;
1058
+ let rest = url.trim();
1059
+ const scp = rest.match(/^[^@]+@([^:]+):(.+)$/);
1060
+ if (scp) {
1061
+ rest = `${scp[1]}/${scp[2]}`;
1062
+ } else {
1063
+ rest = rest.replace(/^[a-z]+:\/\//i, "").replace(/^[^@/]+@/, "");
1064
+ }
1065
+ rest = rest.replace(/\.git$/i, "").replace(/\/+$/, "");
1066
+ if (!rest.includes("/")) return null;
1067
+ return rest.toLowerCase();
1068
+ }
1069
+ async function accountEnvForRepo(folder, mapping) {
1070
+ if (!mapping) return void 0;
1071
+ const slug = await repoSlug(folder);
1072
+ if (!slug) return void 0;
1073
+ const entry = mapping[slug];
1074
+ if (!entry) return void 0;
1075
+ const token = await tokenFor(entry.host, entry.login);
1076
+ if (!token) return void 0;
1077
+ return { GH_TOKEN: token };
1078
+ }
978
1079
  const BEL = "\x07";
979
1080
  const CSI_RE = new RegExp(
980
1081
  "[\\x1b\\x9b][\\[\\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]",
@@ -1450,7 +1551,11 @@ class AutoExpandService {
1450
1551
  if (!info.isRepo || !info.repoRoot) return;
1451
1552
  await ensureBranch(info.repoRoot, cfg.branch);
1452
1553
  try {
1453
- await publishBranch(info.repoRoot, cfg.branch);
1554
+ const env = await accountEnvForRepo(
1555
+ info.repoRoot,
1556
+ this.persistence.state.settings.githubRepoAccounts
1557
+ );
1558
+ await publishBranch(info.repoRoot, cfg.branch, env);
1454
1559
  } catch {
1455
1560
  }
1456
1561
  }
@@ -1530,7 +1635,11 @@ class AutoExpandService {
1530
1635
  }
1531
1636
  await ensureBranch(info.repoRoot, cfg.branch);
1532
1637
  try {
1533
- await publishBranch(info.repoRoot, cfg.branch);
1638
+ const env = await accountEnvForRepo(
1639
+ info.repoRoot,
1640
+ this.persistence.state.settings.githubRepoAccounts
1641
+ );
1642
+ await publishBranch(info.repoRoot, cfg.branch, env);
1534
1643
  } catch {
1535
1644
  }
1536
1645
  const existingTitles = this.features.list(session.id).map((f) => f.title);
@@ -4828,6 +4937,22 @@ function registerIpc(sessions, fs2, persistence2, sentinels, features, autoExpan
4828
4937
  );
4829
4938
  electron.ipcMain.handle("git:branches", (_e, sessionId) => sessions.listBranches(sessionId));
4830
4939
  electron.ipcMain.handle("git:branchDiff", (_e, sessionId) => sessions.getBranchDiff(sessionId));
4940
+ electron.ipcMain.handle(
4941
+ "git:checkout",
4942
+ (_e, sessionId, branch) => sessions.checkoutSessionBranch(sessionId, branch)
4943
+ );
4944
+ electron.ipcMain.handle(
4945
+ "git:createBranch",
4946
+ (_e, sessionId, branch) => sessions.createSessionBranch(sessionId, branch)
4947
+ );
4948
+ electron.ipcMain.handle(
4949
+ "git:deleteBranch",
4950
+ (_e, sessionId, branch) => sessions.deleteSessionBranch(sessionId, branch)
4951
+ );
4952
+ electron.ipcMain.handle(
4953
+ "git:createBranchPr",
4954
+ (_e, sessionId) => sessions.createBranchPr(sessionId)
4955
+ );
4831
4956
  electron.ipcMain.handle(
4832
4957
  "checkpoint:create",
4833
4958
  (_e, sessionId, label) => sessions.createCheckpoint(sessionId, label)
@@ -5155,6 +5280,23 @@ function registerIpc(sessions, fs2, persistence2, sentinels, features, autoExpan
5155
5280
  getWin2()?.webContents.send("session:changed");
5156
5281
  if (patch.agentRegistryPath !== void 0) agents.refresh();
5157
5282
  });
5283
+ electron.ipcMain.handle("github:listAccounts", () => listAccounts());
5284
+ electron.ipcMain.handle("github:repoInfo", async (_e, sessionId) => {
5285
+ const slug = await repoSlug(rootOf(sessionId));
5286
+ const accounts = await listAccounts();
5287
+ const current = slug ? persistence2.state.settings.githubRepoAccounts[slug] ?? null : null;
5288
+ return { slug, current, accounts };
5289
+ });
5290
+ electron.ipcMain.handle(
5291
+ "github:setRepoAccount",
5292
+ (_e, slug, account) => {
5293
+ const map = persistence2.state.settings.githubRepoAccounts;
5294
+ if (account) map[slug] = account;
5295
+ else delete map[slug];
5296
+ persistence2.scheduleSave();
5297
+ getWin2()?.webContents.send("session:changed");
5298
+ }
5299
+ );
5158
5300
  }
5159
5301
  const DEFAULT_TOKEN_EFFICIENCY = {
5160
5302
  enabled: false,
@@ -5189,7 +5331,8 @@ const DEFAULT_SETTINGS = {
5189
5331
  gamificationSound: false,
5190
5332
  dailyBudgetUSD: null,
5191
5333
  monthlyBudgetUSD: null,
5192
- budgetAlertThreshold: 80
5334
+ budgetAlertThreshold: 80,
5335
+ githubRepoAccounts: {}
5193
5336
  };
5194
5337
  const DEFAULT_CATEGORIES = [
5195
5338
  {
@@ -6000,6 +6143,118 @@ class SessionManager {
6000
6143
  if (!config) return { branches: [], current: null, defaultBranch: null };
6001
6144
  return listBranches(config.folder);
6002
6145
  }
6146
+ /**
6147
+ * Switch a session's folder to an existing branch. Warn-and-block: refuses on
6148
+ * any uncommitted change so the user's work is never disturbed (they commit,
6149
+ * stash, or checkpoint first). git's own message is surfaced when the branch
6150
+ * is busy in another worktree.
6151
+ */
6152
+ async checkoutSessionBranch(sessionId, branch) {
6153
+ const config = this.getConfig(sessionId);
6154
+ if (!config) return { ok: false, reason: "error", output: "Unknown session." };
6155
+ const dirty = await dirtyCount(config.folder) ?? 0;
6156
+ if (dirty > 0) {
6157
+ return {
6158
+ ok: false,
6159
+ reason: "dirty",
6160
+ output: `This folder has ${dirty} uncommitted file(s). Commit, stash, or take a checkpoint before switching branches, so nothing is lost.`
6161
+ };
6162
+ }
6163
+ const res = await checkoutBranch(config.folder, branch);
6164
+ if (res.code !== 0) {
6165
+ const busy = /already (checked out|used by worktree)/i.test(res.output);
6166
+ return { ok: false, reason: busy ? "busy" : "error", output: res.output };
6167
+ }
6168
+ this.notifyChanged();
6169
+ return { ok: true, output: res.output };
6170
+ }
6171
+ /**
6172
+ * Create a new branch off the session folder's current HEAD and switch to it.
6173
+ * Same warn-and-block dirty guard as checkout; validates the branch name and
6174
+ * reports a name clash distinctly so the UI can say "already exists".
6175
+ */
6176
+ async createSessionBranch(sessionId, branch) {
6177
+ const config = this.getConfig(sessionId);
6178
+ if (!config) return { ok: false, reason: "error", output: "Unknown session." };
6179
+ const name = branch.trim();
6180
+ if (!await isValidBranchName(name)) {
6181
+ return { ok: false, reason: "invalid", output: `"${name}" is not a valid branch name.` };
6182
+ }
6183
+ if (await branchExists(config.folder, name)) {
6184
+ return { ok: false, reason: "exists", output: `Branch "${name}" already exists.` };
6185
+ }
6186
+ const dirty = await dirtyCount(config.folder) ?? 0;
6187
+ if (dirty > 0) {
6188
+ return {
6189
+ ok: false,
6190
+ reason: "dirty",
6191
+ output: `This folder has ${dirty} uncommitted file(s). Commit, stash, or take a checkpoint before creating a branch (it switches the working tree).`
6192
+ };
6193
+ }
6194
+ const res = await createBranch(config.folder, name);
6195
+ if (res.code !== 0) return { ok: false, reason: "error", output: res.output };
6196
+ this.notifyChanged();
6197
+ return { ok: true, output: res.output };
6198
+ }
6199
+ /**
6200
+ * Delete a local branch in a session's repo. Refuses to delete the branch the
6201
+ * folder currently has checked out, or one checked out in another worktree.
6202
+ */
6203
+ async deleteSessionBranch(sessionId, branch) {
6204
+ const config = this.getConfig(sessionId);
6205
+ if (!config) return { ok: false, reason: "error", output: "Unknown session." };
6206
+ const listing = await listBranches(config.folder);
6207
+ if (listing.current === branch) {
6208
+ return {
6209
+ ok: false,
6210
+ reason: "busy",
6211
+ output: `"${branch}" is the current branch — switch away before deleting it.`
6212
+ };
6213
+ }
6214
+ const busyAt = await branchCheckedOutAt(config.folder, branch);
6215
+ if (busyAt) {
6216
+ return {
6217
+ ok: false,
6218
+ reason: "busy",
6219
+ output: `"${branch}" is checked out in another worktree (${busyAt}) — close it first.`
6220
+ };
6221
+ }
6222
+ await deleteBranch(config.folder, branch);
6223
+ this.notifyChanged();
6224
+ return { ok: true, output: `Deleted branch "${branch}".` };
6225
+ }
6226
+ /**
6227
+ * Open a pull request for a NON-worktree session's current branch against the
6228
+ * repo's default branch. Pushes the branch (via the per-repo GitHub account),
6229
+ * then `gh pr create`. The worktree equivalent is createWorktreePr.
6230
+ */
6231
+ async createBranchPr(sessionId) {
6232
+ const config = this.getConfig(sessionId);
6233
+ if (!config) return { ok: false, output: "Unknown session." };
6234
+ const listing = await listBranches(config.folder);
6235
+ const branch = listing.current;
6236
+ if (!branch) {
6237
+ return { ok: false, output: "No branch is checked out (detached HEAD) — cannot open a PR." };
6238
+ }
6239
+ const baseBranch = listing.defaultBranch && listing.defaultBranch !== branch ? listing.defaultBranch : null;
6240
+ if (!baseBranch) {
6241
+ return {
6242
+ ok: false,
6243
+ output: `The current branch "${branch}" is the default branch — there is nothing to open a PR against. Switch to a feature branch first.`
6244
+ };
6245
+ }
6246
+ const title = branch;
6247
+ const body = `Opened by Maestro for branch \`${branch}\`.
6248
+
6249
+ Merges \`${branch}\` into \`${baseBranch}\`.`;
6250
+ const env = await accountEnvForRepo(
6251
+ config.folder,
6252
+ this.state.settings.githubRepoAccounts
6253
+ );
6254
+ const result = await createPullRequest(config.folder, branch, baseBranch, title, body, env);
6255
+ if (result.ok) this.emitGame({ type: "worktree.pr" });
6256
+ return result;
6257
+ }
6003
6258
  /**
6004
6259
  * Cumulative diff of a worktree task's branch against its base branch (their
6005
6260
  * merge-base), for the read-only "Review changes" tab. Returns an empty diff
@@ -6160,7 +6415,14 @@ class SessionManager {
6160
6415
  async getWorktreeTaskState(sessionId) {
6161
6416
  const config = this.getConfig(sessionId);
6162
6417
  if (!config?.worktree) {
6163
- return { folderExists: false, dirty: -1, ahead: -1, conflictFiles: null };
6418
+ return {
6419
+ folderExists: false,
6420
+ dirty: -1,
6421
+ ahead: -1,
6422
+ conflictFiles: null,
6423
+ baseDirty: -1,
6424
+ baseBranchBusyPath: null
6425
+ };
6164
6426
  }
6165
6427
  const folderExists = fs.existsSync(config.folder);
6166
6428
  const dirty = folderExists ? await dirtyCount(config.folder) : null;
@@ -6169,6 +6431,13 @@ class SessionManager {
6169
6431
  config.worktree.baseBranch,
6170
6432
  config.worktree.branch
6171
6433
  );
6434
+ const baseDirty = await dirtyCount(config.worktree.baseFolder);
6435
+ const busyAt = await branchCheckedOutAt(
6436
+ config.worktree.baseFolder,
6437
+ config.worktree.baseBranch
6438
+ );
6439
+ const baseNorm = config.worktree.baseFolder.replace(/\\/g, "/").replace(/\/+$/, "");
6440
+ const baseBranchBusyPath = busyAt && busyAt.replace(/\/+$/, "") !== baseNorm ? busyAt : null;
6172
6441
  let conflictFiles = null;
6173
6442
  if (ahead !== null && ahead > 0) {
6174
6443
  try {
@@ -6183,7 +6452,14 @@ class SessionManager {
6183
6452
  } else if (ahead === 0) {
6184
6453
  conflictFiles = [];
6185
6454
  }
6186
- return { folderExists, dirty: dirty ?? -1, ahead: ahead ?? -1, conflictFiles };
6455
+ return {
6456
+ folderExists,
6457
+ dirty: dirty ?? -1,
6458
+ ahead: ahead ?? -1,
6459
+ conflictFiles,
6460
+ baseDirty: baseDirty ?? -1,
6461
+ baseBranchBusyPath
6462
+ };
6187
6463
  }
6188
6464
  /**
6189
6465
  * Merge a worktree task's branch back into its base branch (runs in the base
@@ -6257,9 +6533,13 @@ ${commit.output}` };
6257
6533
  * and push to GitHub", so if it has no upstream yet we publish it (push -u).
6258
6534
  */
6259
6535
  async pushAfterMerge(merge, baseFolder, baseBranch) {
6260
- let push = await pushBranch(baseFolder, baseBranch);
6536
+ const env = await accountEnvForRepo(
6537
+ baseFolder,
6538
+ this.state.settings.githubRepoAccounts
6539
+ );
6540
+ let push = await pushBranch(baseFolder, baseBranch, env);
6261
6541
  if (!push && this.isAutoExpandBranch(baseBranch)) {
6262
- push = await publishBranch(baseFolder, baseBranch);
6542
+ push = await publishBranch(baseFolder, baseBranch, env);
6263
6543
  }
6264
6544
  if (!push) return merge;
6265
6545
  if (push.ok) return { ...merge, pushed: true };
@@ -6334,7 +6614,11 @@ ${commit.output}` };
6334
6614
  }
6335
6615
  const title = prTitle(config.name, branch);
6336
6616
  const body = prBody(config.name, branch, baseBranch);
6337
- const result = await createPullRequest(config.folder, branch, baseBranch, title, body);
6617
+ const env = await accountEnvForRepo(
6618
+ config.folder,
6619
+ this.state.settings.githubRepoAccounts
6620
+ );
6621
+ const result = await createPullRequest(config.folder, branch, baseBranch, title, body, env);
6338
6622
  if (result.ok) this.emitGame({ type: "worktree.pr" });
6339
6623
  return { ...result, autoCommitted };
6340
6624
  }
@@ -32,6 +32,10 @@ const api = {
32
32
  gitFileDiff: (sessionId, path) => electron.ipcRenderer.invoke("git:fileDiff", sessionId, path),
33
33
  gitBranches: (sessionId) => electron.ipcRenderer.invoke("git:branches", sessionId),
34
34
  gitBranchDiff: (sessionId) => electron.ipcRenderer.invoke("git:branchDiff", sessionId),
35
+ gitCheckout: (sessionId, branch) => electron.ipcRenderer.invoke("git:checkout", sessionId, branch),
36
+ gitCreateBranch: (sessionId, branch) => electron.ipcRenderer.invoke("git:createBranch", sessionId, branch),
37
+ gitDeleteBranch: (sessionId, branch) => electron.ipcRenderer.invoke("git:deleteBranch", sessionId, branch),
38
+ gitCreateBranchPr: (sessionId) => electron.ipcRenderer.invoke("git:createBranchPr", sessionId),
35
39
  createCheckpoint: (sessionId, label) => electron.ipcRenderer.invoke("checkpoint:create", sessionId, label),
36
40
  listCheckpoints: (sessionId) => electron.ipcRenderer.invoke("checkpoint:list", sessionId),
37
41
  checkpointDiff: (sessionId, id) => electron.ipcRenderer.invoke("checkpoint:diff", sessionId, id),
@@ -161,6 +165,9 @@ const api = {
161
165
  clipboardRead: () => electron.ipcRenderer.invoke("clipboard:read"),
162
166
  clipboardWrite: (text) => electron.ipcRenderer.send("clipboard:write", text),
163
167
  getSettings: () => electron.ipcRenderer.invoke("settings:get"),
164
- setSettings: (patch) => electron.ipcRenderer.invoke("settings:set", patch)
168
+ setSettings: (patch) => electron.ipcRenderer.invoke("settings:set", patch),
169
+ githubListAccounts: () => electron.ipcRenderer.invoke("github:listAccounts"),
170
+ githubRepoInfo: (sessionId) => electron.ipcRenderer.invoke("github:repoInfo", sessionId),
171
+ githubSetRepoAccount: (slug, account) => electron.ipcRenderer.invoke("github:setRepoAccount", slug, account)
165
172
  };
166
173
  electron.contextBridge.exposeInMainWorld("api", api);
@@ -1,5 +1,5 @@
1
- import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup, C as ContextTracker } from "./index-DroXAl3A.js";
2
- import { s as styleTags, t as tags, E as EditorView, e as syntaxTree, l as EditorSelection, a as LanguageSupport, b as ifNotIn, d as completeFromList, L as LRLanguage, i as indentNodeProp, g as flatIndent, c as continuedIndent, h as delimitedIndent, f as foldNodeProp, j as foldInside, J as sublanguageProp, k as snippetCompletion, z as defineLanguageFacet, N as NodeWeakMap, I as IterMode } from "./index-Uh6FxvAQ.js";
1
+ import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup, C as ContextTracker } from "./index-BZcI6lAQ.js";
2
+ import { s as styleTags, t as tags, E as EditorView, e as syntaxTree, l as EditorSelection, a as LanguageSupport, b as ifNotIn, d as completeFromList, L as LRLanguage, i as indentNodeProp, g as flatIndent, c as continuedIndent, h as delimitedIndent, f as foldNodeProp, j as foldInside, J as sublanguageProp, k as snippetCompletion, z as defineLanguageFacet, N as NodeWeakMap, I as IterMode } from "./index-BlF1OgRv.js";
3
3
  const noSemi = 316, noSemiType = 317, incdec = 1, incdecPrefix = 2, questionDot = 3, JSXStartTag = 4, insertSemi = 318, spaces = 320, newline = 321, LineComment = 5, BlockComment = 6, Dialect_jsx = 0;
4
4
  const space = [
5
5
  9,
@@ -1,5 +1,5 @@
1
- import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup } from "./index-DroXAl3A.js";
2
- import { s as styleTags, t as tags$1, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, c as continuedIndent, f as foldNodeProp, j as foldInside, e as syntaxTree, N as NodeWeakMap, I as IterMode } from "./index-Uh6FxvAQ.js";
1
+ import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup } from "./index-BZcI6lAQ.js";
2
+ import { s as styleTags, t as tags$1, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, c as continuedIndent, f as foldNodeProp, j as foldInside, e as syntaxTree, N as NodeWeakMap, I as IterMode } from "./index-BlF1OgRv.js";
3
3
  const descendantOp = 135, Unit = 1, identifier$1 = 136, callee = 137, VariableName = 2, queryIdentifier = 138, queryVariableName = 3, QueryCallee = 4;
4
4
  const space = [
5
5
  9,
@@ -1,5 +1,5 @@
1
- import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-DroXAl3A.js";
2
- import { s as styleTags, t as tags, E as EditorView, e as syntaxTree, l as EditorSelection, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, f as foldNodeProp, G as bracketMatchingHandle } from "./index-Uh6FxvAQ.js";
1
+ import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-BZcI6lAQ.js";
2
+ import { s as styleTags, t as tags, E as EditorView, e as syntaxTree, l as EditorSelection, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, f as foldNodeProp, G as bracketMatchingHandle } from "./index-BlF1OgRv.js";
3
3
  const StartTag = 1, StartCloseTag = 2, MissingCloseTag = 3, mismatchedStartCloseTag = 4, incompleteStartCloseTag = 5, commentContent$1 = 36, piContent$1 = 37, cdataContent$1 = 38, Element$1 = 11, OpenTag = 13;
4
4
  function nameChar(ch) {
5
5
  return ch == 45 || ch == 46 || ch == 58 || ch >= 65 && ch <= 90 || ch == 95 || ch >= 97 && ch <= 122 || ch >= 161;
@@ -1,5 +1,5 @@
1
- import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup, C as ContextTracker } from "./index-DroXAl3A.js";
2
- import { s as styleTags, t as tags, a as LanguageSupport, b as ifNotIn, d as completeFromList, L as LRLanguage, i as indentNodeProp, g as flatIndent, c as continuedIndent, h as delimitedIndent, f as foldNodeProp, j as foldInside, e as syntaxTree, k as snippetCompletion, N as NodeWeakMap, I as IterMode } from "./index-Uh6FxvAQ.js";
1
+ import { L as LRParser, E as ExternalTokenizer, a as LocalTokenGroup, C as ContextTracker } from "./index-BZcI6lAQ.js";
2
+ import { s as styleTags, t as tags, a as LanguageSupport, b as ifNotIn, d as completeFromList, L as LRLanguage, i as indentNodeProp, g as flatIndent, c as continuedIndent, h as delimitedIndent, f as foldNodeProp, j as foldInside, e as syntaxTree, k as snippetCompletion, N as NodeWeakMap, I as IterMode } from "./index-BlF1OgRv.js";
3
3
  const insertedSemi = 177, space$1 = 179, identifier = 184, String = 12, closeParen$1 = 13, Number = 17, Rune = 20, closeBrace$1 = 25, closeBracket = 53, IncDecOp = 95, _return = 142, _break = 144, _continue = 145, fallthrough = 148;
4
4
  const newline = 10, carriageReturn = 13, space = 32, tab = 9, slash = 47, closeParen = 41, closeBrace = 125;
5
5
  const semicolon = new ExternalTokenizer((input, stack) => {
@@ -1,4 +1,4 @@
1
- import { P as Parser, o as NodeSet, m as NodeType, H as DefaultBufferLength, n as NodeProp, q as Tree, I as IterMode } from "./index-Uh6FxvAQ.js";
1
+ import { P as Parser, o as NodeSet, m as NodeType, H as DefaultBufferLength, n as NodeProp, q as Tree, I as IterMode } from "./index-BlF1OgRv.js";
2
2
  var define_process_env_default = {};
3
3
  class Stack {
4
4
  /**
@@ -1,6 +1,6 @@
1
- import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-DroXAl3A.js";
2
- import { s as styleTags, t as tags, a as LanguageSupport, L as LRLanguage, f as foldNodeProp, j as foldInside, i as indentNodeProp, c as continuedIndent } from "./index-Uh6FxvAQ.js";
3
- import { defineCSSCompletionSource } from "./index-kJ0KF5bI.js";
1
+ import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-BZcI6lAQ.js";
2
+ import { s as styleTags, t as tags, a as LanguageSupport, L as LRLanguage, f as foldNodeProp, j as foldInside, i as indentNodeProp, c as continuedIndent } from "./index-BlF1OgRv.js";
3
+ import { defineCSSCompletionSource } from "./index-8y8_VEXe.js";
4
4
  const indent = 168, dedent = 169, descendantOp = 170, InterpolationEnd = 1, InterpolationContinue = 2, Unit = 3, callee = 171, identifier = 172, VariableName = 4, queryIdentifier = 173, InterpolationStart = 5, newline = 174, blankLineStart = 175, eof = 176, whitespace = 177, LineComment = 6, Comment = 7, IndentedMixin = 8, IndentedInclude = 9, Dialect_indented = 0;
5
5
  const space = [
6
6
  9,