@sideboard-ai/core 0.1.218 → 0.1.220

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 (35) hide show
  1. package/dist/{agents-YABJUV3V.js → agents-6UOWJLXX.js} +4 -4
  2. package/dist/{agents-NWJ7DFJD.js → agents-DYSZ2JFY.js} +4 -4
  3. package/dist/{chunk-FSIJGLSV.js → chunk-3Y4FIARC.js} +39 -11
  4. package/dist/{chunk-JWBPSD7Q.js → chunk-ALDVGAIO.js} +8 -8
  5. package/dist/{chunk-SRFFIFW6.js → chunk-B73KKJQB.js} +9 -9
  6. package/dist/{chunk-OPYBLX4V.js → chunk-N2VST3NL.js} +49 -2
  7. package/dist/chunk-P2BSMNHB.js +179 -0
  8. package/dist/{chunk-MW57SKKR.js → chunk-P2EAAM3H.js} +49 -2
  9. package/dist/{chunk-IILG7DPX.js → chunk-QILV2BRW.js} +31 -18
  10. package/dist/chunk-ROPGQRPU.js +178 -0
  11. package/dist/{chunk-SVXIJ2IC.js → chunk-UNMMKZ4Q.js} +26 -13
  12. package/dist/{chunk-XRHTRXZN.js → chunk-YG2X34EF.js} +39 -11
  13. package/dist/{connected-teams-LX5DLL2I.js → connected-teams-PRHZS6B4.js} +2 -2
  14. package/dist/{connected-teams-PQETXUWV.js → connected-teams-YOIB42RB.js} +2 -2
  15. package/dist/{coordinator-prompt-5V7GEPBG.js → coordinator-prompt-JLMQVORO.js} +1 -1
  16. package/dist/{coordinator-prompt-J4QSNTKE.js → coordinator-prompt-VZ7OSRLD.js} +1 -1
  17. package/dist/{global-workspace-4TEMLESD.js → global-workspace-3FDAUNN2.js} +1 -1
  18. package/dist/{global-workspace-XPTFHMKL.js → global-workspace-QPSJEXGR.js} +1 -1
  19. package/dist/index.cjs +216 -43
  20. package/dist/index.d.cts +40 -2
  21. package/dist/index.d.ts +40 -2
  22. package/dist/index.js +17 -5
  23. package/dist/mcp/run-stdio.cjs +196 -41
  24. package/dist/mcp/run-stdio.js +5 -5
  25. package/dist/oauth-K6CWZEQO.js +23 -0
  26. package/dist/oauth-S3M7B475.js +21 -0
  27. package/dist/{orchestrator-SWRTYFHX.js → orchestrator-P4KNHFHG.js} +5 -5
  28. package/dist/{orchestrator-HKW3VLGX.js → orchestrator-Y5IDLJ7D.js} +5 -5
  29. package/dist/{workspaces-BCBBMMXB.js → workspaces-76W7IEKR.js} +3 -1
  30. package/dist/{workspaces-2HQP654Q.js → workspaces-TDBA64II.js} +3 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-AN3THU7F.js +0 -103
  33. package/dist/chunk-Y4MIXGPA.js +0 -104
  34. package/dist/oauth-6ZQ4Q4XN.js +0 -11
  35. package/dist/oauth-O7UGLZOY.js +0 -13
@@ -44,13 +44,13 @@ import {
44
44
  resolveLoginCommand,
45
45
  resolveQuotaFallbackAgent,
46
46
  whichOnPath
47
- } from "./chunk-XRHTRXZN.js";
48
- import "./chunk-OPYBLX4V.js";
47
+ } from "./chunk-YG2X34EF.js";
48
+ import "./chunk-N2VST3NL.js";
49
49
  import "./chunk-KTZESHYH.js";
50
50
  import "./chunk-6K64FJDD.js";
51
51
  import "./chunk-PO4M4JAJ.js";
52
- import "./chunk-IILG7DPX.js";
53
- import "./chunk-AN3THU7F.js";
52
+ import "./chunk-QILV2BRW.js";
53
+ import "./chunk-ROPGQRPU.js";
54
54
  import "./chunk-N7WZ6NIN.js";
55
55
  import {
56
56
  cursorSdkMessageToEvents,
@@ -50,10 +50,10 @@ import {
50
50
  resolveQuotaFallbackAgent,
51
51
  turnCostUsdFromCursorUsage,
52
52
  whichOnPath
53
- } from "./chunk-FSIJGLSV.js";
54
- import "./chunk-MW57SKKR.js";
55
- import "./chunk-SVXIJ2IC.js";
56
- import "./chunk-Y4MIXGPA.js";
53
+ } from "./chunk-3Y4FIARC.js";
54
+ import "./chunk-P2EAAM3H.js";
55
+ import "./chunk-UNMMKZ4Q.js";
56
+ import "./chunk-P2BSMNHB.js";
57
57
  import "./chunk-VKQGAWHY.js";
58
58
  import "./chunk-NFWPDEN4.js";
59
59
  import "./chunk-D6JWYJ4P.js";
@@ -2,16 +2,16 @@
2
2
 
3
3
  import {
4
4
  isOrchestratorThread
5
- } from "./chunk-MW57SKKR.js";
5
+ } from "./chunk-P2EAAM3H.js";
6
6
  import {
7
7
  applyConnectedTeamToCli,
8
8
  brightsyMcpServerName,
9
9
  ensureCliTeamTracked,
10
10
  ensureConnectedBrightsyTeamTokens
11
- } from "./chunk-SVXIJ2IC.js";
11
+ } from "./chunk-UNMMKZ4Q.js";
12
12
  import {
13
13
  loadBrightsyConfig
14
- } from "./chunk-Y4MIXGPA.js";
14
+ } from "./chunk-P2BSMNHB.js";
15
15
  import {
16
16
  httpFetch
17
17
  } from "./chunk-VKQGAWHY.js";
@@ -1313,6 +1313,7 @@ function teamEnv(team) {
1313
1313
  return {
1314
1314
  BRIGHTSY_API_TOKEN: team.access_token,
1315
1315
  ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
1316
+ ...team.oauth_client_id ? { BRIGHTSY_OAUTH_CLIENT_ID: team.oauth_client_id } : {},
1316
1317
  BRIGHTSY_ACCOUNT_ID: team.id,
1317
1318
  BRIGHTSY_API_URL: endpoint
1318
1319
  };
@@ -1496,8 +1497,16 @@ function toOpencodeMcpConfigContent(servers, opts) {
1496
1497
  }
1497
1498
  return JSON.stringify({ mcp });
1498
1499
  }
1499
- function writeMcpServersConfig(servers) {
1500
- if (servers.length === 0) return null;
1500
+ function stripElectronFromUserMcpEntry(entry) {
1501
+ const next = { ...entry };
1502
+ const env = next.env;
1503
+ if (!env || typeof env !== "object" || Array.isArray(env)) return next;
1504
+ const cleaned = { ...env };
1505
+ delete cleaned.ELECTRON_RUN_AS_NODE;
1506
+ next.env = cleaned;
1507
+ return next;
1508
+ }
1509
+ function writeMcpServersConfig(servers, extraServers) {
1501
1510
  const mcpServers = {};
1502
1511
  for (const s of servers) {
1503
1512
  const env = mcpSpawnEnv(s.env);
@@ -1507,6 +1516,14 @@ function writeMcpServersConfig(servers) {
1507
1516
  ...env ? { env } : {}
1508
1517
  };
1509
1518
  }
1519
+ if (extraServers) {
1520
+ for (const [rawName, entry] of Object.entries(extraServers)) {
1521
+ const name = rawName.trim();
1522
+ if (!name || mcpServers[name] || !entry) continue;
1523
+ mcpServers[name] = stripElectronFromUserMcpEntry(entry);
1524
+ }
1525
+ }
1526
+ if (Object.keys(mcpServers).length === 0) return null;
1510
1527
  const dir = mkdtempSync(join4(tmpdir(), "sideboard-mcp-"));
1511
1528
  const cfgPath = join4(dir, "mcp.json");
1512
1529
  writeFileSync2(cfgPath, JSON.stringify({ mcpServers }, null, 2));
@@ -1593,10 +1610,20 @@ function listUserOpencodeMcpNames(home = homedir3()) {
1593
1610
  }
1594
1611
  return names;
1595
1612
  }
1596
- function listUserClaudeMcpNames(home = homedir3()) {
1613
+ function userClaudeMcpConfigObject(home = homedir3()) {
1597
1614
  const claudePath = home === homedir3() ? userClaudeMcpConfigPath() : join6(home, ".claude.json");
1598
- if (!existsSync7(claudePath)) return [];
1599
- return listMcpNamesFromJsonMap(readJsonObject(claudePath), "mcpServers");
1615
+ if (!existsSync7(claudePath)) return {};
1616
+ return asObject(asObject(readJsonObject(claudePath)).mcpServers);
1617
+ }
1618
+ function listUserClaudeMcpServerEntries(home = homedir3()) {
1619
+ const out = {};
1620
+ for (const [rawName, value] of Object.entries(userClaudeMcpConfigObject(home))) {
1621
+ const name = rawName.trim();
1622
+ if (!name || isInjectedOrchMcpName(name)) continue;
1623
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
1624
+ out[name] = value;
1625
+ }
1626
+ return out;
1600
1627
  }
1601
1628
  function toCodexDisableUserMcpArgs(names) {
1602
1629
  const args = [];
@@ -2511,7 +2538,7 @@ var claudeAdapter = {
2511
2538
  );
2512
2539
  }
2513
2540
  const mode = permissionMode(thread);
2514
- const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-XPTFHMKL.js");
2541
+ const { isOrchestratorThread: isOrchestratorThread2 } = await import("./global-workspace-QPSJEXGR.js");
2515
2542
  const isOrchestrator = isOrchestratorThread2(thread);
2516
2543
  const injectedServers = await buildInjectedMcpServers({
2517
2544
  includeSideboard: true,
@@ -2522,6 +2549,7 @@ var claudeAdapter = {
2522
2549
  });
2523
2550
  const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
2524
2551
  const chromeOn = claudeChromeEnabled();
2552
+ const userMcpEntries = isOrchestrator ? {} : listUserClaudeMcpServerEntries();
2525
2553
  let allowedTools;
2526
2554
  if (isOrchestrator) {
2527
2555
  allowedTools = [
@@ -2532,7 +2560,7 @@ var claudeAdapter = {
2532
2560
  } else {
2533
2561
  allowedTools = [
2534
2562
  ...BASE_ALLOWED_TOOLS,
2535
- ...mcpAllowToolsFromNames(userMcpNamesToDisable({ names: listUserClaudeMcpNames() })),
2563
+ ...mcpAllowToolsFromNames(Object.keys(userMcpEntries)),
2536
2564
  ...sideboardWorktreeAllowedTools({
2537
2565
  github: true,
2538
2566
  linear: isLinearConnected(),
@@ -2557,7 +2585,7 @@ var claudeAdapter = {
2557
2585
  if (systemPrompt) {
2558
2586
  args.push("--append-system-prompt", systemPrompt);
2559
2587
  }
2560
- const mcpConfigPath = writeMcpServersConfig(injectedServers);
2588
+ const mcpConfigPath = writeMcpServersConfig(injectedServers, userMcpEntries);
2561
2589
  if (mcpConfigPath) {
2562
2590
  args.push("--mcp-config", mcpConfigPath);
2563
2591
  args.push("--strict-mcp-config");
@@ -42,7 +42,7 @@ import {
42
42
  summarizeTurnStderr,
43
43
  toolDescription,
44
44
  turnFailChatText
45
- } from "./chunk-FSIJGLSV.js";
45
+ } from "./chunk-3Y4FIARC.js";
46
46
  import {
47
47
  SLACK_REPLY_FORMATTING,
48
48
  addWorkspace,
@@ -65,7 +65,7 @@ import {
65
65
  stageAbsolutePathsAsAttachments,
66
66
  stageBuffersAsAttachments,
67
67
  syncWorkspacesFromThreads
68
- } from "./chunk-MW57SKKR.js";
68
+ } from "./chunk-P2EAAM3H.js";
69
69
  import {
70
70
  addPrStackLayer,
71
71
  allocateTeamName,
@@ -418,7 +418,7 @@ async function continueSourceThread(threadId, prompt) {
418
418
  await continueOnReply(threadId, prompt);
419
419
  return;
420
420
  }
421
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-SWRTYFHX.js");
421
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-P4KNHFHG.js");
422
422
  await getOrchestrator2().send(threadId, prompt);
423
423
  } catch {
424
424
  }
@@ -895,9 +895,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
895
895
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
896
896
  );
897
897
  }
898
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-XPTFHMKL.js");
898
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-QPSJEXGR.js");
899
899
  if (isGlobalThread2(thread)) {
900
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-J4QSNTKE.js");
900
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-VZ7OSRLD.js");
901
901
  ensureGlobalCoordinatorCwd2(
902
902
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
903
903
  );
@@ -4482,7 +4482,7 @@ async function adoptThread(input) {
4482
4482
  messages: input.messages ?? []
4483
4483
  });
4484
4484
  writeThread(thread);
4485
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-2HQP654Q.js");
4485
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TDBA64II.js");
4486
4486
  await ensureWorkspace2(repoPath);
4487
4487
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-K7QK4RI5.js");
4488
4488
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -6957,7 +6957,7 @@ function formatScheduledPrompt(name, prompt) {
6957
6957
  ${prompt}`;
6958
6958
  }
6959
6959
  async function defaultDeps() {
6960
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-SWRTYFHX.js");
6960
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-P4KNHFHG.js");
6961
6961
  const orch = getOrchestrator2();
6962
6962
  return {
6963
6963
  findThread: (id) => findThreadByRef(id),
@@ -9231,7 +9231,7 @@ var Orchestrator = class {
9231
9231
  this.enforceHistoryRetention();
9232
9232
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
9233
9233
  try {
9234
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-2HQP654Q.js");
9234
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TDBA64II.js");
9235
9235
  await ensureWorkspace2(thread.repoPath);
9236
9236
  } catch {
9237
9237
  }
@@ -24,7 +24,7 @@ import {
24
24
  resolveQuotaFallbackAgent,
25
25
  stripBrightsyNdjsonNoise,
26
26
  toolDescription
27
- } from "./chunk-XRHTRXZN.js";
27
+ } from "./chunk-YG2X34EF.js";
28
28
  import {
29
29
  SLACK_REPLY_FORMATTING,
30
30
  addWorkspace,
@@ -47,7 +47,7 @@ import {
47
47
  stageAbsolutePathsAsAttachments,
48
48
  stageBuffersAsAttachments,
49
49
  syncWorkspacesFromThreads
50
- } from "./chunk-OPYBLX4V.js";
50
+ } from "./chunk-N2VST3NL.js";
51
51
  import {
52
52
  addPrStackLayer,
53
53
  allocateTeamName,
@@ -482,7 +482,7 @@ async function continueSourceThread(threadId, prompt) {
482
482
  await continueOnReply(threadId, prompt);
483
483
  return;
484
484
  }
485
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-HKW3VLGX.js");
485
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-Y5IDLJ7D.js");
486
486
  await getOrchestrator2().send(threadId, prompt);
487
487
  } catch {
488
488
  }
@@ -900,9 +900,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
900
900
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
901
901
  );
902
902
  }
903
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-4TEMLESD.js");
903
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-3FDAUNN2.js");
904
904
  if (isGlobalThread2(thread)) {
905
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-5V7GEPBG.js");
905
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-JLMQVORO.js");
906
906
  ensureGlobalCoordinatorCwd2(
907
907
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
908
908
  );
@@ -4178,7 +4178,7 @@ async function createThread(input, _onSetupLine) {
4178
4178
  return readThread(thread.id) ?? thread;
4179
4179
  }
4180
4180
  async function listLinearIssues(agent, repoPath) {
4181
- const { getAdapter: getAdapter2 } = await import("./agents-YABJUV3V.js");
4181
+ const { getAdapter: getAdapter2 } = await import("./agents-6UOWJLXX.js");
4182
4182
  await requireAgent(agent, { requireLinear: true });
4183
4183
  const adapter = getAdapter2(agent);
4184
4184
  if (!adapter.listLinearIssues) {
@@ -4651,7 +4651,7 @@ async function adoptThread(input) {
4651
4651
  messages: input.messages ?? []
4652
4652
  });
4653
4653
  writeThread(thread);
4654
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BCBBMMXB.js");
4654
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-76W7IEKR.js");
4655
4655
  await ensureWorkspace2(repoPath);
4656
4656
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-L32WEBX6.js");
4657
4657
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -7332,7 +7332,7 @@ function formatScheduledPrompt(name, prompt) {
7332
7332
  ${prompt}`;
7333
7333
  }
7334
7334
  async function defaultDeps() {
7335
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-HKW3VLGX.js");
7335
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-Y5IDLJ7D.js");
7336
7336
  const orch = getOrchestrator2();
7337
7337
  return {
7338
7338
  findThread: (id) => findThreadByRef(id),
@@ -9606,7 +9606,7 @@ var Orchestrator = class {
9606
9606
  this.enforceHistoryRetention();
9607
9607
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
9608
9608
  try {
9609
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-BCBBMMXB.js");
9609
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-76W7IEKR.js");
9610
9610
  await ensureWorkspace2(thread.repoPath);
9611
9611
  } catch {
9612
9612
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  allocateTeamName,
3
+ canonicalizeRepoPath,
3
4
  ensureGhPreferOrigin,
4
5
  resolveGithubRepoSlug,
5
6
  resolveRepoRoot,
@@ -853,6 +854,29 @@ function isLinkedWorktreeOf(path, mainRepo) {
853
854
  return false;
854
855
  }
855
856
  }
857
+ function primaryCheckoutFromLinkedWorktree(path) {
858
+ if (!path) return null;
859
+ const git = join3(path, ".git");
860
+ try {
861
+ if (!existsSync2(git) || statSync2(git).isDirectory()) return null;
862
+ const text = readFileSync3(git, "utf8");
863
+ const match = /^\s*gitdir:\s*(.+)$/m.exec(text);
864
+ if (!match) return null;
865
+ const gitdir = match[1].trim().replace(/\/+$/, "");
866
+ const worktrees = gitdir.lastIndexOf("/.git/worktrees/");
867
+ const root = worktrees > 0 ? gitdir.slice(0, worktrees) : gitdir.endsWith("/.git") ? gitdir.slice(0, -5) : null;
868
+ if (!root) return null;
869
+ return canonicalizeRepoPath(root);
870
+ } catch {
871
+ return null;
872
+ }
873
+ }
874
+ function resolveSyncWorkspacePath(path) {
875
+ if (!path || path === "/") return null;
876
+ const primary = primaryCheckoutFromLinkedWorktree(path);
877
+ if (primary) return primary;
878
+ return canonicalizeRepoPath(path);
879
+ }
856
880
  async function addWorkspace(repoPath) {
857
881
  const root = await resolveRepoRoot(repoPath);
858
882
  if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
@@ -881,9 +905,24 @@ async function ensureWorkspace(repoPath) {
881
905
  function syncWorkspacesFromThreads(repoPaths) {
882
906
  const current = readAll();
883
907
  const removed = readRemoved();
884
- const byPath = new Map(current.map((w) => [w.path, w]));
908
+ const byPath = /* @__PURE__ */ new Map();
885
909
  let dirty = false;
886
- for (const path of repoPaths) {
910
+ for (const w of current) {
911
+ const resolved = resolveSyncWorkspacePath(w.path) ?? w.path;
912
+ if (resolved !== w.path) dirty = true;
913
+ if (!resolved || resolved === "/" || isGlobalRepoPath(resolved) || removed.has(resolved)) {
914
+ dirty = true;
915
+ continue;
916
+ }
917
+ if (!byPath.has(resolved)) {
918
+ byPath.set(
919
+ resolved,
920
+ resolved === w.path ? w : { ...w, path: resolved, name: basename2(resolved) }
921
+ );
922
+ }
923
+ }
924
+ for (const raw of repoPaths) {
925
+ const path = resolveSyncWorkspacePath(raw);
887
926
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
888
927
  continue;
889
928
  }
@@ -896,6 +935,13 @@ function syncWorkspacesFromThreads(repoPaths) {
896
935
  byPath.set(path, ws);
897
936
  dirty = true;
898
937
  }
938
+ for (const [path] of byPath) {
939
+ const mains = [...byPath.keys()];
940
+ if (mains.some((main) => isLinkedWorktreeOf(path, main))) {
941
+ byPath.delete(path);
942
+ dirty = true;
943
+ }
944
+ }
899
945
  const next = [...byPath.values()];
900
946
  if (dirty) writeAll(next);
901
947
  return next.sort((a, b) => a.name.localeCompare(b.name));
@@ -916,6 +962,7 @@ export {
916
962
  persistPendingFileAttachments,
917
963
  attachmentsFromWorktreePaths,
918
964
  listWorkspaces,
965
+ primaryCheckoutFromLinkedWorktree,
919
966
  addWorkspace,
920
967
  removeWorkspace,
921
968
  ensureWorkspace,
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ formatFetchError
5
+ } from "./chunk-VKQGAWHY.js";
6
+
7
+ // src/brightsy/config.ts
8
+ import { existsSync, readFileSync, writeFileSync } from "fs";
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ function brightsyConfigPath() {
12
+ const override = process.env.BRIGHTSY_CONFIG?.trim();
13
+ if (override) return override;
14
+ return join(homedir(), ".brightsy", "config.json");
15
+ }
16
+ function loadBrightsyConfig() {
17
+ const path = brightsyConfigPath();
18
+ if (!existsSync(path)) {
19
+ throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
20
+ }
21
+ const raw = JSON.parse(readFileSync(path, "utf8"));
22
+ if (!raw.access_token || !raw.account_id) {
23
+ throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
24
+ }
25
+ return raw;
26
+ }
27
+ function saveBrightsyConfig(cfg) {
28
+ writeFileSync(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
29
+ `, {
30
+ mode: 384
31
+ });
32
+ }
33
+
34
+ // src/brightsy/oauth.ts
35
+ var REFRESH_SKEW_MS = 6e4;
36
+ var BRIGHTSY_OAUTH_CLIENT_ID = "sideboard";
37
+ var BRIGHTSY_OAUTH_REDIRECT = "http://127.0.0.1:19850/callback";
38
+ function defaultFetch(fetchImpl) {
39
+ return fetchImpl ?? globalThis.fetch.bind(globalThis);
40
+ }
41
+ function normalizeBrightsyEndpoint(endpoint) {
42
+ return (endpoint || "https://brightsy.ai").replace(/\/$/, "");
43
+ }
44
+ function brightsyAccessTokenNeedsRefresh(expiresAt, now = Date.now()) {
45
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) {
46
+ return true;
47
+ }
48
+ return now >= expiresAt - REFRESH_SKEW_MS;
49
+ }
50
+ function applyGrant(current, grant) {
51
+ return {
52
+ access_token: grant.access_token,
53
+ refresh_token: grant.refresh_token || current.refresh_token,
54
+ expires_at: grant.expires_at ?? current.expires_at
55
+ };
56
+ }
57
+ async function registerBrightsyOAuthClient(opts) {
58
+ const endpoint = normalizeBrightsyEndpoint(opts.endpoint);
59
+ const url = `${endpoint}/oauth/register`;
60
+ const clientId = (opts.clientId || BRIGHTSY_OAUTH_CLIENT_ID).trim();
61
+ const redirectUri = opts.redirectUri || BRIGHTSY_OAUTH_REDIRECT;
62
+ const fetchImpl = defaultFetch(opts.fetchImpl);
63
+ let res;
64
+ try {
65
+ res = await fetchImpl(url, {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/json" },
68
+ body: JSON.stringify({
69
+ client_id: clientId,
70
+ client_name: "Sideboard",
71
+ description: "Sideboard desktop Brightsy MCP client",
72
+ redirect_uris: [redirectUri],
73
+ scope: "brightsy:api",
74
+ grant_types: ["authorization_code", "refresh_token"],
75
+ response_types: ["code"],
76
+ token_endpoint_auth_method: "none"
77
+ })
78
+ });
79
+ } catch (err) {
80
+ throw new Error(formatFetchError(err, url));
81
+ }
82
+ if (!res.ok) return null;
83
+ const data = await res.json();
84
+ if (!data.client_id?.trim()) return null;
85
+ return {
86
+ client_id: data.client_id.trim(),
87
+ client_secret: data.client_secret?.trim() || void 0
88
+ };
89
+ }
90
+ async function ensureBrightsyOAuthClientId(opts) {
91
+ const existing = opts.clientId?.trim();
92
+ if (existing) return existing;
93
+ const registered = await registerBrightsyOAuthClient({
94
+ endpoint: opts.endpoint,
95
+ fetchImpl: opts.fetchImpl
96
+ });
97
+ return registered?.client_id || BRIGHTSY_OAUTH_CLIENT_ID;
98
+ }
99
+ async function refreshBrightsyAccessToken(opts) {
100
+ const endpoint = normalizeBrightsyEndpoint(opts.endpoint);
101
+ const url = `${endpoint}/oauth/token`;
102
+ const fetchImpl = defaultFetch(opts.fetchImpl);
103
+ const clientId = opts.clientId?.trim() || BRIGHTSY_OAUTH_CLIENT_ID;
104
+ let res;
105
+ try {
106
+ res = await fetchImpl(url, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
109
+ body: new URLSearchParams({
110
+ grant_type: "refresh_token",
111
+ refresh_token: opts.refreshToken,
112
+ client_id: clientId
113
+ })
114
+ });
115
+ } catch (err) {
116
+ throw new Error(formatFetchError(err, url));
117
+ }
118
+ if (!res.ok) return null;
119
+ const data = await res.json();
120
+ if (!data.access_token) return null;
121
+ return {
122
+ access_token: data.access_token,
123
+ refresh_token: data.refresh_token,
124
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
125
+ };
126
+ }
127
+ var refreshInFlight = null;
128
+ async function ensureBrightsyLocalConfigFreshOnce(opts) {
129
+ let cfg;
130
+ try {
131
+ cfg = loadBrightsyConfig();
132
+ } catch {
133
+ return null;
134
+ }
135
+ const refreshToken = cfg.refresh_token;
136
+ if (!refreshToken || !brightsyAccessTokenNeedsRefresh(cfg.expires_at)) {
137
+ return cfg;
138
+ }
139
+ const endpoint = normalizeBrightsyEndpoint(cfg.endpoint);
140
+ const clientId = await ensureBrightsyOAuthClientId({
141
+ endpoint,
142
+ clientId: cfg.oauth_client_id,
143
+ fetchImpl: opts?.fetchImpl
144
+ });
145
+ if (clientId !== cfg.oauth_client_id) {
146
+ cfg = { ...cfg, oauth_client_id: clientId };
147
+ saveBrightsyConfig(cfg);
148
+ }
149
+ const grant = await refreshBrightsyAccessToken({
150
+ endpoint,
151
+ refreshToken,
152
+ clientId,
153
+ fetchImpl: opts?.fetchImpl
154
+ });
155
+ if (!grant) return cfg;
156
+ const next = { ...cfg, ...applyGrant(cfg, grant), oauth_client_id: clientId };
157
+ saveBrightsyConfig(next);
158
+ return next;
159
+ }
160
+ async function ensureBrightsyLocalConfigFresh(opts) {
161
+ if (refreshInFlight) return refreshInFlight;
162
+ refreshInFlight = ensureBrightsyLocalConfigFreshOnce(opts).finally(() => {
163
+ refreshInFlight = null;
164
+ });
165
+ return refreshInFlight;
166
+ }
167
+
168
+ export {
169
+ loadBrightsyConfig,
170
+ saveBrightsyConfig,
171
+ BRIGHTSY_OAUTH_CLIENT_ID,
172
+ BRIGHTSY_OAUTH_REDIRECT,
173
+ normalizeBrightsyEndpoint,
174
+ brightsyAccessTokenNeedsRefresh,
175
+ registerBrightsyOAuthClient,
176
+ ensureBrightsyOAuthClientId,
177
+ refreshBrightsyAccessToken,
178
+ ensureBrightsyLocalConfigFresh
179
+ };
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  allocateTeamName,
5
+ canonicalizeRepoPath,
5
6
  ensureGhPreferOrigin,
6
7
  resolveGithubRepoSlug,
7
8
  resolveRepoRoot,
@@ -808,6 +809,29 @@ function isLinkedWorktreeOf(path, mainRepo) {
808
809
  return false;
809
810
  }
810
811
  }
812
+ function primaryCheckoutFromLinkedWorktree(path) {
813
+ if (!path) return null;
814
+ const git = join3(path, ".git");
815
+ try {
816
+ if (!existsSync2(git) || statSync2(git).isDirectory()) return null;
817
+ const text = readFileSync3(git, "utf8");
818
+ const match = /^\s*gitdir:\s*(.+)$/m.exec(text);
819
+ if (!match) return null;
820
+ const gitdir = match[1].trim().replace(/\/+$/, "");
821
+ const worktrees = gitdir.lastIndexOf("/.git/worktrees/");
822
+ const root = worktrees > 0 ? gitdir.slice(0, worktrees) : gitdir.endsWith("/.git") ? gitdir.slice(0, -5) : null;
823
+ if (!root) return null;
824
+ return canonicalizeRepoPath(root);
825
+ } catch {
826
+ return null;
827
+ }
828
+ }
829
+ function resolveSyncWorkspacePath(path) {
830
+ if (!path || path === "/") return null;
831
+ const primary = primaryCheckoutFromLinkedWorktree(path);
832
+ if (primary) return primary;
833
+ return canonicalizeRepoPath(path);
834
+ }
811
835
  async function addWorkspace(repoPath) {
812
836
  const root = await resolveRepoRoot(repoPath);
813
837
  if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
@@ -836,9 +860,24 @@ async function ensureWorkspace(repoPath) {
836
860
  function syncWorkspacesFromThreads(repoPaths) {
837
861
  const current = readAll();
838
862
  const removed = readRemoved();
839
- const byPath = new Map(current.map((w) => [w.path, w]));
863
+ const byPath = /* @__PURE__ */ new Map();
840
864
  let dirty = false;
841
- for (const path of repoPaths) {
865
+ for (const w of current) {
866
+ const resolved = resolveSyncWorkspacePath(w.path) ?? w.path;
867
+ if (resolved !== w.path) dirty = true;
868
+ if (!resolved || resolved === "/" || isGlobalRepoPath(resolved) || removed.has(resolved)) {
869
+ dirty = true;
870
+ continue;
871
+ }
872
+ if (!byPath.has(resolved)) {
873
+ byPath.set(
874
+ resolved,
875
+ resolved === w.path ? w : { ...w, path: resolved, name: basename2(resolved) }
876
+ );
877
+ }
878
+ }
879
+ for (const raw of repoPaths) {
880
+ const path = resolveSyncWorkspacePath(raw);
842
881
  if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
843
882
  continue;
844
883
  }
@@ -851,6 +890,13 @@ function syncWorkspacesFromThreads(repoPaths) {
851
890
  byPath.set(path, ws);
852
891
  dirty = true;
853
892
  }
893
+ for (const [path] of byPath) {
894
+ const mains = [...byPath.keys()];
895
+ if (mains.some((main) => isLinkedWorktreeOf(path, main))) {
896
+ byPath.delete(path);
897
+ dirty = true;
898
+ }
899
+ }
854
900
  const next = [...byPath.values()];
855
901
  if (dirty) writeAll(next);
856
902
  return next.sort((a, b) => a.name.localeCompare(b.name));
@@ -862,6 +908,7 @@ export {
862
908
  persistPendingFileAttachments,
863
909
  attachmentsFromWorktreePaths,
864
910
  listWorkspaces,
911
+ primaryCheckoutFromLinkedWorktree,
865
912
  addWorkspace,
866
913
  removeWorkspace,
867
914
  ensureWorkspace,