@sideboard-ai/core 0.1.219 → 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.
@@ -44,13 +44,13 @@ import {
44
44
  resolveLoginCommand,
45
45
  resolveQuotaFallbackAgent,
46
46
  whichOnPath
47
- } from "./chunk-K642ORWF.js";
47
+ } from "./chunk-YG2X34EF.js";
48
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-IAP5NXQ4.js";
53
+ } from "./chunk-3Y4FIARC.js";
54
54
  import "./chunk-P2EAAM3H.js";
55
- import "./chunk-SVXIJ2IC.js";
56
- import "./chunk-Y4MIXGPA.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";
@@ -8,10 +8,10 @@ import {
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
  };
@@ -42,7 +42,7 @@ import {
42
42
  summarizeTurnStderr,
43
43
  toolDescription,
44
44
  turnFailChatText
45
- } from "./chunk-IAP5NXQ4.js";
45
+ } from "./chunk-3Y4FIARC.js";
46
46
  import {
47
47
  SLACK_REPLY_FORMATTING,
48
48
  addWorkspace,
@@ -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-5VVR7LOU.js");
421
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-P4KNHFHG.js");
422
422
  await getOrchestrator2().send(threadId, prompt);
423
423
  } catch {
424
424
  }
@@ -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-5VVR7LOU.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),
@@ -24,7 +24,7 @@ import {
24
24
  resolveQuotaFallbackAgent,
25
25
  stripBrightsyNdjsonNoise,
26
26
  toolDescription
27
- } from "./chunk-K642ORWF.js";
27
+ } from "./chunk-YG2X34EF.js";
28
28
  import {
29
29
  SLACK_REPLY_FORMATTING,
30
30
  addWorkspace,
@@ -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-LFC3V3AX.js");
485
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-Y5IDLJ7D.js");
486
486
  await getOrchestrator2().send(threadId, prompt);
487
487
  } catch {
488
488
  }
@@ -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-IFVGY77W.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) {
@@ -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-LFC3V3AX.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),
@@ -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
+ };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  brightsyAccessTokenNeedsRefresh,
3
3
  ensureBrightsyLocalConfigFresh,
4
+ ensureBrightsyOAuthClientId,
4
5
  loadBrightsyConfig,
5
6
  refreshBrightsyAccessToken,
6
7
  saveBrightsyConfig
7
- } from "./chunk-AN3THU7F.js";
8
+ } from "./chunk-ROPGQRPU.js";
8
9
  import {
9
10
  appDataDir
10
11
  } from "./chunk-WFUDY6PC.js";
@@ -18,7 +19,7 @@ import { join } from "path";
18
19
 
19
20
  // src/brightsy/accounts.ts
20
21
  async function loadConnectedTeams() {
21
- const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await import("./connected-teams-LX5DLL2I.js");
22
+ const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await import("./connected-teams-PRHZS6B4.js");
22
23
  return listConnectedBrightsyTeams2();
23
24
  }
24
25
  async function runBrightsyTeamsJson(args) {
@@ -46,15 +47,15 @@ async function listBrightsyAccounts() {
46
47
  }
47
48
  async function getBrightsySession() {
48
49
  try {
49
- const { ensureBrightsyLocalConfigFresh: ensureBrightsyLocalConfigFresh2 } = await import("./oauth-6ZQ4Q4XN.js");
50
- const { ensureConnectedBrightsyTeamTokens: ensureConnectedBrightsyTeamTokens2 } = await import("./connected-teams-LX5DLL2I.js");
50
+ const { ensureBrightsyLocalConfigFresh: ensureBrightsyLocalConfigFresh2 } = await import("./oauth-S3M7B475.js");
51
+ const { ensureConnectedBrightsyTeamTokens: ensureConnectedBrightsyTeamTokens2 } = await import("./connected-teams-PRHZS6B4.js");
51
52
  await ensureBrightsyLocalConfigFresh2();
52
53
  await ensureConnectedBrightsyTeamTokens2();
53
54
  loadBrightsyConfig();
54
55
  const data = await runBrightsyTeamsJson([]);
55
56
  const accounts = Array.isArray(data.teams) ? data.teams : [];
56
57
  const active = data.active ?? accounts.find((a) => a.active) ?? null;
57
- const { ensureCliTeamTracked: ensureCliTeamTracked2 } = await import("./connected-teams-LX5DLL2I.js");
58
+ const { ensureCliTeamTracked: ensureCliTeamTracked2 } = await import("./connected-teams-PRHZS6B4.js");
58
59
  const connectedTeams = ensureCliTeamTracked2(
59
60
  active ? { id: active.id, slug: active.slug, name: active.name } : void 0
60
61
  );
@@ -94,7 +95,7 @@ async function performBrightsyAccountSwitch(accountIdOrSlug) {
94
95
  return { cfg, target: data.active };
95
96
  }
96
97
  async function switchBrightsyAccount(accountIdOrSlug) {
97
- const { connectBrightsyTeam: connectBrightsyTeam2 } = await import("./connected-teams-LX5DLL2I.js");
98
+ const { connectBrightsyTeam: connectBrightsyTeam2 } = await import("./connected-teams-PRHZS6B4.js");
98
99
  await connectBrightsyTeam2(accountIdOrSlug);
99
100
  return getBrightsySession();
100
101
  }
@@ -147,10 +148,10 @@ function applyConnectedTeamToCli(team) {
147
148
  account_id: team.id,
148
149
  account_slug: team.slug,
149
150
  endpoint: team.endpoint ?? base.endpoint ?? "https://brightsy.ai",
150
- oauth_client_id: base.oauth_client_id
151
+ oauth_client_id: team.oauth_client_id ?? base.oauth_client_id
151
152
  });
152
153
  }
153
- async function refreshTeamToken(team) {
154
+ async function refreshTeamToken(team, opts) {
154
155
  if (!team.refresh_token) return team;
155
156
  const cfg = (() => {
156
157
  try {
@@ -159,21 +160,31 @@ async function refreshTeamToken(team) {
159
160
  return null;
160
161
  }
161
162
  })();
163
+ const endpoint = team.endpoint || "https://brightsy.ai";
164
+ const clientId = await ensureBrightsyOAuthClientId({
165
+ endpoint,
166
+ clientId: team.oauth_client_id || cfg?.oauth_client_id,
167
+ fetchImpl: opts?.fetchImpl
168
+ });
162
169
  const grant = await refreshBrightsyAccessToken({
163
- endpoint: team.endpoint || "https://brightsy.ai",
170
+ endpoint,
164
171
  refreshToken: team.refresh_token,
165
- clientId: cfg?.oauth_client_id
172
+ clientId,
173
+ fetchImpl: opts?.fetchImpl
166
174
  });
167
- if (!grant) return team;
175
+ if (!grant) return { ...team, oauth_client_id: clientId || team.oauth_client_id };
168
176
  return {
169
177
  ...team,
170
178
  access_token: grant.access_token,
171
179
  refresh_token: grant.refresh_token || team.refresh_token,
172
- expires_at: grant.expires_at ?? team.expires_at
180
+ expires_at: grant.expires_at ?? team.expires_at,
181
+ oauth_client_id: clientId
173
182
  };
174
183
  }
175
- async function ensureConnectedBrightsyTeamTokens() {
176
- await ensureBrightsyLocalConfigFresh().catch(() => null);
184
+ async function ensureConnectedBrightsyTeamTokens(opts) {
185
+ await ensureBrightsyLocalConfigFresh({ fetchImpl: opts?.fetchImpl }).catch(
186
+ () => null
187
+ );
177
188
  const teams = readStore();
178
189
  if (teams.length === 0) return [];
179
190
  const next = [];
@@ -183,8 +194,8 @@ async function ensureConnectedBrightsyTeamTokens() {
183
194
  next.push(team);
184
195
  continue;
185
196
  }
186
- const refreshed = await refreshTeamToken(team);
187
- if (refreshed.access_token !== team.access_token || refreshed.refresh_token !== team.refresh_token || refreshed.expires_at !== team.expires_at) {
197
+ const refreshed = await refreshTeamToken(team, opts);
198
+ if (refreshed.access_token !== team.access_token || refreshed.refresh_token !== team.refresh_token || refreshed.expires_at !== team.expires_at || refreshed.oauth_client_id !== team.oauth_client_id) {
188
199
  changed = true;
189
200
  }
190
201
  next.push(refreshed);
@@ -214,7 +225,8 @@ function ensureCliTeamTracked(meta) {
214
225
  access_token: cfg.access_token,
215
226
  refresh_token: cfg.refresh_token,
216
227
  expires_at: cfg.expires_at,
217
- endpoint: cfg.endpoint
228
+ endpoint: cfg.endpoint,
229
+ oauth_client_id: cfg.oauth_client_id
218
230
  };
219
231
  writeStore([...existing, team]);
220
232
  } catch {
@@ -241,7 +253,8 @@ async function connectBrightsyTeam(accountIdOrSlug) {
241
253
  access_token: minted.access_token,
242
254
  refresh_token: minted.refresh_token,
243
255
  expires_at: minted.expires_at,
244
- endpoint: minted.endpoint
256
+ endpoint: minted.endpoint,
257
+ oauth_client_id: minted.oauth_client_id
245
258
  };
246
259
  writeStore([...existing, team]);
247
260
  return listConnectedBrightsyTeams();
@@ -0,0 +1,178 @@
1
+ import {
2
+ formatFetchError
3
+ } from "./chunk-N7WZ6NIN.js";
4
+
5
+ // src/brightsy/config.ts
6
+ import { existsSync, readFileSync, writeFileSync } from "fs";
7
+ import { homedir } from "os";
8
+ import { join } from "path";
9
+ function brightsyConfigPath() {
10
+ const override = process.env.BRIGHTSY_CONFIG?.trim();
11
+ if (override) return override;
12
+ return join(homedir(), ".brightsy", "config.json");
13
+ }
14
+ function loadBrightsyConfig() {
15
+ const path = brightsyConfigPath();
16
+ if (!existsSync(path)) {
17
+ throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
18
+ }
19
+ const raw = JSON.parse(readFileSync(path, "utf8"));
20
+ if (!raw.access_token || !raw.account_id) {
21
+ throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
22
+ }
23
+ return raw;
24
+ }
25
+ function saveBrightsyConfig(cfg) {
26
+ writeFileSync(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
27
+ `, {
28
+ mode: 384
29
+ });
30
+ }
31
+
32
+ // src/brightsy/oauth.ts
33
+ var REFRESH_SKEW_MS = 6e4;
34
+ var BRIGHTSY_OAUTH_CLIENT_ID = "sideboard";
35
+ var BRIGHTSY_OAUTH_REDIRECT = "http://127.0.0.1:19850/callback";
36
+ function defaultFetch(fetchImpl) {
37
+ return fetchImpl ?? globalThis.fetch.bind(globalThis);
38
+ }
39
+ function normalizeBrightsyEndpoint(endpoint) {
40
+ return (endpoint || "https://brightsy.ai").replace(/\/$/, "");
41
+ }
42
+ function brightsyAccessTokenNeedsRefresh(expiresAt, now = Date.now()) {
43
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) {
44
+ return true;
45
+ }
46
+ return now >= expiresAt - REFRESH_SKEW_MS;
47
+ }
48
+ function applyGrant(current, grant) {
49
+ return {
50
+ access_token: grant.access_token,
51
+ refresh_token: grant.refresh_token || current.refresh_token,
52
+ expires_at: grant.expires_at ?? current.expires_at
53
+ };
54
+ }
55
+ async function registerBrightsyOAuthClient(opts) {
56
+ const endpoint = normalizeBrightsyEndpoint(opts.endpoint);
57
+ const url = `${endpoint}/oauth/register`;
58
+ const clientId = (opts.clientId || BRIGHTSY_OAUTH_CLIENT_ID).trim();
59
+ const redirectUri = opts.redirectUri || BRIGHTSY_OAUTH_REDIRECT;
60
+ const fetchImpl = defaultFetch(opts.fetchImpl);
61
+ let res;
62
+ try {
63
+ res = await fetchImpl(url, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify({
67
+ client_id: clientId,
68
+ client_name: "Sideboard",
69
+ description: "Sideboard desktop Brightsy MCP client",
70
+ redirect_uris: [redirectUri],
71
+ scope: "brightsy:api",
72
+ grant_types: ["authorization_code", "refresh_token"],
73
+ response_types: ["code"],
74
+ token_endpoint_auth_method: "none"
75
+ })
76
+ });
77
+ } catch (err) {
78
+ throw new Error(formatFetchError(err, url));
79
+ }
80
+ if (!res.ok) return null;
81
+ const data = await res.json();
82
+ if (!data.client_id?.trim()) return null;
83
+ return {
84
+ client_id: data.client_id.trim(),
85
+ client_secret: data.client_secret?.trim() || void 0
86
+ };
87
+ }
88
+ async function ensureBrightsyOAuthClientId(opts) {
89
+ const existing = opts.clientId?.trim();
90
+ if (existing) return existing;
91
+ const registered = await registerBrightsyOAuthClient({
92
+ endpoint: opts.endpoint,
93
+ fetchImpl: opts.fetchImpl
94
+ });
95
+ return registered?.client_id || BRIGHTSY_OAUTH_CLIENT_ID;
96
+ }
97
+ async function refreshBrightsyAccessToken(opts) {
98
+ const endpoint = normalizeBrightsyEndpoint(opts.endpoint);
99
+ const url = `${endpoint}/oauth/token`;
100
+ const fetchImpl = defaultFetch(opts.fetchImpl);
101
+ const clientId = opts.clientId?.trim() || BRIGHTSY_OAUTH_CLIENT_ID;
102
+ let res;
103
+ try {
104
+ res = await fetchImpl(url, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
107
+ body: new URLSearchParams({
108
+ grant_type: "refresh_token",
109
+ refresh_token: opts.refreshToken,
110
+ client_id: clientId
111
+ })
112
+ });
113
+ } catch (err) {
114
+ throw new Error(formatFetchError(err, url));
115
+ }
116
+ if (!res.ok) return null;
117
+ const data = await res.json();
118
+ if (!data.access_token) return null;
119
+ return {
120
+ access_token: data.access_token,
121
+ refresh_token: data.refresh_token,
122
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
123
+ };
124
+ }
125
+ var refreshInFlight = null;
126
+ async function ensureBrightsyLocalConfigFreshOnce(opts) {
127
+ let cfg;
128
+ try {
129
+ cfg = loadBrightsyConfig();
130
+ } catch {
131
+ return null;
132
+ }
133
+ const refreshToken = cfg.refresh_token;
134
+ if (!refreshToken || !brightsyAccessTokenNeedsRefresh(cfg.expires_at)) {
135
+ return cfg;
136
+ }
137
+ const endpoint = normalizeBrightsyEndpoint(cfg.endpoint);
138
+ const clientId = await ensureBrightsyOAuthClientId({
139
+ endpoint,
140
+ clientId: cfg.oauth_client_id,
141
+ fetchImpl: opts?.fetchImpl
142
+ });
143
+ if (clientId !== cfg.oauth_client_id) {
144
+ cfg = { ...cfg, oauth_client_id: clientId };
145
+ saveBrightsyConfig(cfg);
146
+ }
147
+ const grant = await refreshBrightsyAccessToken({
148
+ endpoint,
149
+ refreshToken,
150
+ clientId,
151
+ fetchImpl: opts?.fetchImpl
152
+ });
153
+ if (!grant) return cfg;
154
+ const next = { ...cfg, ...applyGrant(cfg, grant), oauth_client_id: clientId };
155
+ saveBrightsyConfig(next);
156
+ return next;
157
+ }
158
+ async function ensureBrightsyLocalConfigFresh(opts) {
159
+ if (refreshInFlight) return refreshInFlight;
160
+ refreshInFlight = ensureBrightsyLocalConfigFreshOnce(opts).finally(() => {
161
+ refreshInFlight = null;
162
+ });
163
+ return refreshInFlight;
164
+ }
165
+
166
+ export {
167
+ brightsyConfigPath,
168
+ loadBrightsyConfig,
169
+ saveBrightsyConfig,
170
+ BRIGHTSY_OAUTH_CLIENT_ID,
171
+ BRIGHTSY_OAUTH_REDIRECT,
172
+ normalizeBrightsyEndpoint,
173
+ brightsyAccessTokenNeedsRefresh,
174
+ registerBrightsyOAuthClient,
175
+ ensureBrightsyOAuthClientId,
176
+ refreshBrightsyAccessToken,
177
+ ensureBrightsyLocalConfigFresh
178
+ };