@sideboard-ai/core 0.1.9

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,154 @@
1
+ import {
2
+ threadFilePath,
3
+ threadLockPath,
4
+ threadsDir
5
+ } from "./chunk-M37RITA6.js";
6
+
7
+ // src/store/thread-store.ts
8
+ import { randomUUID } from "crypto";
9
+ import {
10
+ existsSync,
11
+ readFileSync,
12
+ renameSync,
13
+ unlinkSync,
14
+ writeFileSync,
15
+ readdirSync
16
+ } from "fs";
17
+ import lockfile from "proper-lockfile";
18
+ function nowIso() {
19
+ return (/* @__PURE__ */ new Date()).toISOString();
20
+ }
21
+ function normalizeThread(raw) {
22
+ return {
23
+ ...raw,
24
+ model: raw.model ?? null,
25
+ fast: Boolean(raw.fast),
26
+ planMode: Boolean(raw.planMode),
27
+ autonomy: raw.autonomy ?? "default",
28
+ lastError: raw.lastError ?? null,
29
+ attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
30
+ prTitle: raw.prTitle ?? null,
31
+ userSetTitle: Boolean(raw.userSetTitle),
32
+ activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : []
33
+ };
34
+ }
35
+ function createEmptyThread(partial) {
36
+ const ts = nowIso();
37
+ return {
38
+ id: randomUUID(),
39
+ sessionId: partial.sessionId ?? null,
40
+ autonomy: partial.autonomy ?? "default",
41
+ model: partial.model ?? null,
42
+ fast: partial.fast ?? false,
43
+ planMode: partial.planMode ?? false,
44
+ sourceIsFork: partial.sourceIsFork ?? false,
45
+ status: partial.status ?? "idle",
46
+ queue: partial.queue ?? [],
47
+ parentThreadId: partial.parentThreadId ?? null,
48
+ devPort: partial.devPort ?? null,
49
+ activeRuns: partial.activeRuns ?? [],
50
+ prUrl: partial.prUrl ?? null,
51
+ prTitle: partial.prTitle ?? null,
52
+ userSetTitle: partial.userSetTitle ?? false,
53
+ messages: partial.messages ?? [],
54
+ attachments: partial.attachments ?? [],
55
+ createdAt: ts,
56
+ updatedAt: ts,
57
+ title: partial.title,
58
+ sourceType: partial.sourceType,
59
+ sourceRef: partial.sourceRef,
60
+ branchName: partial.branchName,
61
+ worktreePath: partial.worktreePath,
62
+ repoPath: partial.repoPath,
63
+ agent: partial.agent,
64
+ lastError: null
65
+ };
66
+ }
67
+ async function withThreadLock(id, fn) {
68
+ const lockPath = threadLockPath(id);
69
+ writeFileSync(lockPath, "", { flag: "a" });
70
+ let release;
71
+ try {
72
+ release = await lockfile.lock(lockPath, {
73
+ retries: { retries: 10, minTimeout: 50, maxTimeout: 200 },
74
+ stale: 6e4
75
+ });
76
+ return await fn();
77
+ } finally {
78
+ if (release) await release();
79
+ }
80
+ }
81
+ function readThread(id) {
82
+ const path = threadFilePath(id);
83
+ if (!existsSync(path)) return null;
84
+ const raw = readFileSync(path, "utf8");
85
+ return normalizeThread(JSON.parse(raw));
86
+ }
87
+ function writeThread(thread) {
88
+ const path = threadFilePath(idPath(thread.id));
89
+ const tmp = `${path}.${process.pid}.tmp`;
90
+ const next = { ...thread, updatedAt: nowIso() };
91
+ writeFileSync(tmp, JSON.stringify(next, null, 2), "utf8");
92
+ renameSync(tmp, path);
93
+ }
94
+ function idPath(id) {
95
+ return id;
96
+ }
97
+ function listThreads(opts) {
98
+ const files = readdirSync(threadsDir()).filter((f) => f.endsWith(".json"));
99
+ const threads = files.map((f) => {
100
+ try {
101
+ return normalizeThread(
102
+ JSON.parse(readFileSync(threadFilePath(f.replace(/\.json$/, "")), "utf8"))
103
+ );
104
+ } catch {
105
+ return null;
106
+ }
107
+ }).filter((t) => t !== null).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
108
+ if (opts?.includeArchived) return threads;
109
+ return threads.filter((t) => t.status !== "archived");
110
+ }
111
+ function deleteThreadRecord(id) {
112
+ const path = threadFilePath(id);
113
+ if (existsSync(path)) unlinkSync(path);
114
+ const lock = threadLockPath(id);
115
+ if (existsSync(lock)) {
116
+ try {
117
+ unlinkSync(lock);
118
+ } catch {
119
+ }
120
+ }
121
+ }
122
+ function updateThread(id, patch) {
123
+ const current = readThread(id);
124
+ if (!current) throw new Error(`Thread not found: ${id}`);
125
+ const next = { ...current, ...patch, id: current.id, updatedAt: nowIso() };
126
+ writeThread(next);
127
+ return next;
128
+ }
129
+ function appendMessage(id, message) {
130
+ const current = readThread(id);
131
+ if (!current) throw new Error(`Thread not found: ${id}`);
132
+ return updateThread(id, { messages: [...current.messages, message] });
133
+ }
134
+ function setStatus(id, status, lastError) {
135
+ return updateThread(id, { status, lastError: lastError ?? null });
136
+ }
137
+ function findThreadByRef(ref) {
138
+ const all = listThreads({ includeArchived: true });
139
+ return all.find((t) => t.id === ref || t.id.startsWith(ref) || t.branchName === ref || t.title === ref) ?? null;
140
+ }
141
+
142
+ export {
143
+ normalizeThread,
144
+ createEmptyThread,
145
+ withThreadLock,
146
+ readThread,
147
+ writeThread,
148
+ listThreads,
149
+ deleteThreadRecord,
150
+ updateThread,
151
+ appendMessage,
152
+ setStatus,
153
+ findThreadByRef
154
+ };
@@ -0,0 +1,311 @@
1
+ import {
2
+ appDataDir
3
+ } from "./chunk-M37RITA6.js";
4
+ import {
5
+ run
6
+ } from "./chunk-AJ6ROGD7.js";
7
+
8
+ // src/brightsy/connected-teams.ts
9
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
10
+ import { join as join2 } from "path";
11
+
12
+ // src/brightsy/config.ts
13
+ import { existsSync, readFileSync, writeFileSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ function brightsyConfigPath() {
17
+ return join(homedir(), ".brightsy", "config.json");
18
+ }
19
+ function loadBrightsyConfig() {
20
+ const path = brightsyConfigPath();
21
+ if (!existsSync(path)) {
22
+ throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
23
+ }
24
+ const raw = JSON.parse(readFileSync(path, "utf8"));
25
+ if (!raw.access_token || !raw.account_id) {
26
+ throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
27
+ }
28
+ return raw;
29
+ }
30
+ function saveBrightsyConfig(cfg) {
31
+ writeFileSync(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
32
+ `, {
33
+ mode: 384
34
+ });
35
+ }
36
+
37
+ // src/brightsy/accounts.ts
38
+ async function loadConnectedTeams() {
39
+ const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await import("./connected-teams-GF52Q7LB.js");
40
+ return listConnectedBrightsyTeams2();
41
+ }
42
+ async function runBrightsyTeamsJson(args) {
43
+ const listed = await run("brightsy", ["teams", ...args, "--json"], {
44
+ reject: false
45
+ });
46
+ if (listed.exitCode !== 0) {
47
+ throw new Error(
48
+ listed.stderr.trim() || listed.stdout.trim() || "brightsy teams failed \u2014 is `@brightsy/cli` installed and logged in?"
49
+ );
50
+ }
51
+ const raw = listed.stdout.trim();
52
+ if (!raw) {
53
+ throw new Error("brightsy teams returned empty output");
54
+ }
55
+ try {
56
+ return JSON.parse(raw);
57
+ } catch {
58
+ throw new Error("brightsy teams returned invalid JSON");
59
+ }
60
+ }
61
+ async function listBrightsyAccounts() {
62
+ const data = await runBrightsyTeamsJson([]);
63
+ return Array.isArray(data.teams) ? data.teams : [];
64
+ }
65
+ async function getBrightsySession() {
66
+ try {
67
+ loadBrightsyConfig();
68
+ const data = await runBrightsyTeamsJson([]);
69
+ const accounts = Array.isArray(data.teams) ? data.teams : [];
70
+ const active = data.active ?? accounts.find((a) => a.active) ?? null;
71
+ const { ensureCliTeamTracked: ensureCliTeamTracked2 } = await import("./connected-teams-GF52Q7LB.js");
72
+ const connectedTeams = ensureCliTeamTracked2(
73
+ active ? { id: active.id, slug: active.slug, name: active.name } : void 0
74
+ );
75
+ return {
76
+ connected: true,
77
+ endpoint: (data.endpoint || "https://brightsy.ai").replace(/\/$/, ""),
78
+ accountId: active?.id ?? null,
79
+ accountSlug: active?.slug ?? null,
80
+ accounts,
81
+ connectedTeams
82
+ };
83
+ } catch (err) {
84
+ return {
85
+ connected: false,
86
+ endpoint: "https://brightsy.ai",
87
+ accountId: null,
88
+ accountSlug: null,
89
+ accounts: [],
90
+ connectedTeams: await loadConnectedTeams(),
91
+ reason: err instanceof Error ? err.message : String(err)
92
+ };
93
+ }
94
+ }
95
+ async function performBrightsyAccountSwitch(accountIdOrSlug) {
96
+ const data = await runBrightsyTeamsJson([
97
+ "switch",
98
+ accountIdOrSlug
99
+ ]);
100
+ if (!data.active?.id) {
101
+ throw new Error(`Team switch failed for: ${accountIdOrSlug}`);
102
+ }
103
+ const cfg = loadBrightsyConfig();
104
+ if (data.active.slug && data.active.slug !== cfg.account_slug) {
105
+ cfg.account_slug = data.active.slug;
106
+ saveBrightsyConfig(cfg);
107
+ }
108
+ return { cfg, target: data.active };
109
+ }
110
+ async function switchBrightsyAccount(accountIdOrSlug) {
111
+ const { connectBrightsyTeam: connectBrightsyTeam2 } = await import("./connected-teams-GF52Q7LB.js");
112
+ await connectBrightsyTeam2(accountIdOrSlug);
113
+ return getBrightsySession();
114
+ }
115
+
116
+ // src/brightsy/connected-teams.ts
117
+ function storePath() {
118
+ return join2(appDataDir(), "brightsy-teams.json");
119
+ }
120
+ function readStore() {
121
+ const path = storePath();
122
+ if (!existsSync2(path)) return [];
123
+ try {
124
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
125
+ return Array.isArray(parsed.teams) ? parsed.teams : [];
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+ function writeStore(teams) {
131
+ mkdirSync(appDataDir(), { recursive: true });
132
+ const path = storePath();
133
+ writeFileSync2(path, `${JSON.stringify({ teams }, null, 2)}
134
+ `, {
135
+ mode: 384
136
+ });
137
+ return teams;
138
+ }
139
+ function listConnectedBrightsyTeams() {
140
+ return readStore().map(({ id, slug, name, expires_at }) => ({
141
+ id,
142
+ slug,
143
+ name,
144
+ expires_at
145
+ }));
146
+ }
147
+ function getConnectedBrightsyTeamsRaw() {
148
+ return readStore();
149
+ }
150
+ function applyConnectedTeamToCli(team) {
151
+ let base = {};
152
+ try {
153
+ base = loadBrightsyConfig();
154
+ } catch {
155
+ }
156
+ saveBrightsyConfig({
157
+ ...base,
158
+ access_token: team.access_token,
159
+ refresh_token: team.refresh_token ?? base.refresh_token,
160
+ expires_at: team.expires_at ?? base.expires_at,
161
+ account_id: team.id,
162
+ account_slug: team.slug,
163
+ endpoint: team.endpoint ?? base.endpoint ?? "https://brightsy.ai",
164
+ oauth_client_id: base.oauth_client_id
165
+ });
166
+ }
167
+ async function refreshTeamToken(team) {
168
+ if (!team.refresh_token) return team;
169
+ const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
170
+ const cfg = (() => {
171
+ try {
172
+ return loadBrightsyConfig();
173
+ } catch {
174
+ return null;
175
+ }
176
+ })();
177
+ const clientId = cfg?.oauth_client_id || "brightsy-cli";
178
+ const res = await fetch(`${endpoint}/oauth/token`, {
179
+ method: "POST",
180
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
181
+ body: new URLSearchParams({
182
+ grant_type: "refresh_token",
183
+ refresh_token: team.refresh_token,
184
+ client_id: clientId
185
+ })
186
+ });
187
+ if (!res.ok) return team;
188
+ const data = await res.json();
189
+ if (!data.access_token) return team;
190
+ return {
191
+ ...team,
192
+ access_token: data.access_token,
193
+ refresh_token: data.refresh_token || team.refresh_token,
194
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : team.expires_at
195
+ };
196
+ }
197
+ async function ensureConnectedBrightsyTeamTokens() {
198
+ const teams = readStore();
199
+ if (teams.length === 0) return [];
200
+ const next = [];
201
+ let changed = false;
202
+ for (const team of teams) {
203
+ const expired = typeof team.expires_at === "number" && Date.now() >= team.expires_at - 6e4;
204
+ if (!expired) {
205
+ next.push(team);
206
+ continue;
207
+ }
208
+ const refreshed = await refreshTeamToken(team);
209
+ if (refreshed.access_token !== team.access_token) changed = true;
210
+ next.push(refreshed);
211
+ }
212
+ if (changed) writeStore(next);
213
+ try {
214
+ const cfg = loadBrightsyConfig();
215
+ const active = next.find((t) => t.id === cfg.account_id);
216
+ if (active && active.access_token !== cfg.access_token) {
217
+ applyConnectedTeamToCli(active);
218
+ }
219
+ } catch {
220
+ }
221
+ return next;
222
+ }
223
+ function ensureCliTeamTracked(meta) {
224
+ try {
225
+ const cfg = loadBrightsyConfig();
226
+ const existing = readStore();
227
+ if (existing.some((t) => t.id === cfg.account_id)) {
228
+ return listConnectedBrightsyTeams();
229
+ }
230
+ const team = {
231
+ id: cfg.account_id,
232
+ slug: meta?.slug || cfg.account_slug || cfg.account_id,
233
+ name: meta?.name || cfg.account_slug || cfg.account_id,
234
+ access_token: cfg.access_token,
235
+ refresh_token: cfg.refresh_token,
236
+ expires_at: cfg.expires_at,
237
+ endpoint: cfg.endpoint
238
+ };
239
+ writeStore([...existing, team]);
240
+ } catch {
241
+ }
242
+ return listConnectedBrightsyTeams();
243
+ }
244
+ async function connectBrightsyTeam(accountIdOrSlug) {
245
+ const accounts = await listBrightsyAccounts();
246
+ const target = accounts.find((a) => a.id === accountIdOrSlug) ?? accounts.find((a) => a.slug === accountIdOrSlug);
247
+ if (!target) {
248
+ throw new Error(`Brightsy team not found: ${accountIdOrSlug}`);
249
+ }
250
+ const existing = readStore();
251
+ const already = existing.find((t) => t.id === target.id);
252
+ if (already) {
253
+ applyConnectedTeamToCli(already);
254
+ return listConnectedBrightsyTeams();
255
+ }
256
+ const { cfg: minted } = await performBrightsyAccountSwitch(target.id);
257
+ const team = {
258
+ id: target.id,
259
+ slug: target.slug,
260
+ name: target.name,
261
+ access_token: minted.access_token,
262
+ refresh_token: minted.refresh_token,
263
+ expires_at: minted.expires_at,
264
+ endpoint: minted.endpoint
265
+ };
266
+ writeStore([...existing, team]);
267
+ return listConnectedBrightsyTeams();
268
+ }
269
+ async function disconnectBrightsyTeam(accountIdOrSlug) {
270
+ const before = readStore();
271
+ const removed = before.find(
272
+ (t) => t.id === accountIdOrSlug || t.slug === accountIdOrSlug
273
+ );
274
+ const teams = before.filter(
275
+ (t) => t.id !== accountIdOrSlug && t.slug !== accountIdOrSlug
276
+ );
277
+ writeStore(teams);
278
+ if (removed) {
279
+ try {
280
+ const cfg = loadBrightsyConfig();
281
+ if (cfg.account_id === removed.id) {
282
+ if (teams[0]) {
283
+ applyConnectedTeamToCli(teams[0]);
284
+ }
285
+ }
286
+ } catch {
287
+ }
288
+ }
289
+ return listConnectedBrightsyTeams();
290
+ }
291
+ function brightsyMcpServerName(slug) {
292
+ const cleaned = slug.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
293
+ return `brightsy_${cleaned || "team"}`;
294
+ }
295
+
296
+ export {
297
+ brightsyConfigPath,
298
+ loadBrightsyConfig,
299
+ saveBrightsyConfig,
300
+ listBrightsyAccounts,
301
+ getBrightsySession,
302
+ switchBrightsyAccount,
303
+ listConnectedBrightsyTeams,
304
+ getConnectedBrightsyTeamsRaw,
305
+ applyConnectedTeamToCli,
306
+ ensureConnectedBrightsyTeamTokens,
307
+ ensureCliTeamTracked,
308
+ connectBrightsyTeam,
309
+ disconnectBrightsyTeam,
310
+ brightsyMcpServerName
311
+ };