@sideboard-ai/core 0.1.132 → 0.1.135

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 (53) hide show
  1. package/dist/abletime-DDTV7WO3.js +36 -0
  2. package/dist/abletime-VTP4JR63.js +37 -0
  3. package/dist/{agents-LGJLZMM3.js → agents-O3AJMI2Y.js} +9 -8
  4. package/dist/{agents-CI6NGKRO.js → agents-RTQFF7PY.js} +9 -8
  5. package/dist/{app-settings-6RPALX4J.js → app-settings-U5RZKUXG.js} +11 -1
  6. package/dist/{app-settings-E7NEQY7D.js → app-settings-YQ7YX2CC.js} +11 -1
  7. package/dist/{chunk-MNL4FSKY.js → chunk-2ESCEK2Q.js} +4 -4
  8. package/dist/chunk-2XXAAIN3.js +463 -0
  9. package/dist/{chunk-HZPO3SSZ.js → chunk-57GIFU3X.js} +4 -3
  10. package/dist/chunk-6GN5WPYZ.js +31 -0
  11. package/dist/chunk-AT346U23.js +468 -0
  12. package/dist/{chunk-LLX3OMHU.js → chunk-ED4UPEJX.js} +1 -1
  13. package/dist/chunk-ELBYOGVA.js +32 -0
  14. package/dist/{chunk-HUYEU4GS.js → chunk-GJBHOJCZ.js} +83 -2
  15. package/dist/{chunk-ZND6XV6J.js → chunk-GKKHXZCT.js} +83 -2
  16. package/dist/{chunk-UDQI3N47.js → chunk-HQQNLDVC.js} +2 -2
  17. package/dist/{chunk-G6X6UFJO.js → chunk-J5IBSVB3.js} +42 -12
  18. package/dist/{chunk-RSFCB23A.js → chunk-K7EX47QG.js} +4 -3
  19. package/dist/{chunk-PK4PQUPU.js → chunk-KBBXNS2V.js} +6 -6
  20. package/dist/{chunk-SIPDL62B.js → chunk-KBWND62T.js} +6 -6
  21. package/dist/{chunk-VE22NWBD.js → chunk-LOKXPQ4U.js} +2 -2
  22. package/dist/{chunk-AQESMTCZ.js → chunk-M267JPEA.js} +3 -29
  23. package/dist/{chunk-MGYCQAZ6.js → chunk-N62K3KXX.js} +6 -6
  24. package/dist/{chunk-HLJUNJBF.js → chunk-PM3C2J6K.js} +42 -12
  25. package/dist/{chunk-HRSBZIXS.js → chunk-QAV3HGVS.js} +2 -2
  26. package/dist/{chunk-SYLTXDRF.js → chunk-RDULVW3E.js} +2 -2
  27. package/dist/{chunk-R7FGPBKL.js → chunk-S42XV45P.js} +3 -25
  28. package/dist/{chunk-VFXZTSAD.js → chunk-XIKEUCNC.js} +55 -31
  29. package/dist/{chunk-HULESWLI.js → chunk-XUEI4GCF.js} +4 -4
  30. package/dist/{chunk-SMWJAE74.js → chunk-Z5LYMW7M.js} +55 -31
  31. package/dist/{connected-teams-S6E3D5C7.js → connected-teams-4A2AXDNN.js} +3 -2
  32. package/dist/{connected-teams-3BWNJIRY.js → connected-teams-IJUJQ7R6.js} +3 -2
  33. package/dist/{coordinator-prompt-MEEUOEF2.js → coordinator-prompt-FYMWE33S.js} +4 -4
  34. package/dist/{coordinator-prompt-SPJSGAQS.js → coordinator-prompt-Y737IIFR.js} +4 -4
  35. package/dist/{global-workspace-KNESLWKG.js → global-workspace-6KH6BSKL.js} +5 -5
  36. package/dist/{global-workspace-BMWP7YZC.js → global-workspace-ZFKNLBZA.js} +5 -5
  37. package/dist/index.cjs +1150 -353
  38. package/dist/index.d.cts +173 -13
  39. package/dist/index.d.ts +173 -13
  40. package/dist/index.js +435 -240
  41. package/dist/mcp/run-stdio.cjs +1041 -300
  42. package/dist/mcp/run-stdio.js +339 -187
  43. package/dist/{oauth-FT7EL2PY.js → oauth-A7XXI2TM.js} +2 -1
  44. package/dist/{oauth-43NNH6C6.js → oauth-YJBAURIJ.js} +2 -1
  45. package/dist/{orchestrator-JA65XKB6.js → orchestrator-2BRAPJ47.js} +11 -10
  46. package/dist/{orchestrator-5TN2DJWO.js → orchestrator-3YDPPZHZ.js} +11 -10
  47. package/dist/{thread-store-5MLYY35S.js → thread-store-CRLQJ2HM.js} +3 -1
  48. package/dist/{thread-store-F6BCARQG.js → thread-store-FADXSMEJ.js} +3 -1
  49. package/dist/{workspaces-HSYRYJYV.js → workspaces-3RRF3LVF.js} +6 -6
  50. package/dist/{workspaces-O4QZOASB.js → workspaces-AYI5DHK4.js} +6 -6
  51. package/dist/{worktree-ERHC7Q4F.js → worktree-BC6XDMQK.js} +3 -3
  52. package/dist/{worktree-JVGSLSLJ.js → worktree-MX7XBX6Z.js} +3 -3
  53. package/package.json +1 -1
@@ -0,0 +1,468 @@
1
+ import {
2
+ httpFetch
3
+ } from "./chunk-ELBYOGVA.js";
4
+ import {
5
+ getAbleTimeAccessToken,
6
+ getAbleTimeHost,
7
+ isAbleTimeConnected,
8
+ saveAbleTimeConnection
9
+ } from "./chunk-GJBHOJCZ.js";
10
+
11
+ // src/integrations/abletime-mcp.ts
12
+ var DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
13
+ var ABLETIME_MCP_PATH = "/api/public/v2/mcp";
14
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
15
+ function normalizeAbleTimeHost(raw) {
16
+ const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
17
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
18
+ return withScheme.replace(/\/+$/, "");
19
+ }
20
+ function abletimeMcpUrl(host) {
21
+ return `${normalizeAbleTimeHost(host)}${ABLETIME_MCP_PATH}`;
22
+ }
23
+ function rewriteAbleTimeError(message) {
24
+ if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
25
+ return `${message} \u2014 Enable Agent access (MCP) in AbleTime Settings \u2192 Integrations \u2192 API Keys.`;
26
+ }
27
+ if (/INTEGRATION_PAT_REQUIRED|organization api key/i.test(message)) {
28
+ return `${message} \u2014 AbleTime MCP needs a personal access token (apt_\u2026), not an organization API key.`;
29
+ }
30
+ if (/INTEGRATION_PLAN_REQUIRED/i.test(message)) {
31
+ return `${message} \u2014 AbleTime plan does not include API access.`;
32
+ }
33
+ if (/INTEGRATION_KEY_INVALID|INTEGRATION_KEY_MISSING|401|unauthorized|invalid token/i.test(message)) {
34
+ return `${message} \u2014 Reconnect AbleTime from Account settings (Profile \u2192 API Access).`;
35
+ }
36
+ return message;
37
+ }
38
+ function asRecord(value) {
39
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
40
+ }
41
+ function parseSseJson(body) {
42
+ const blocks = body.split(/\n\n+/);
43
+ let last;
44
+ for (const block of blocks) {
45
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).filter((line) => line && line !== "[DONE]").join("\n");
46
+ if (!data) continue;
47
+ try {
48
+ last = JSON.parse(data);
49
+ } catch {
50
+ }
51
+ }
52
+ if (last !== void 0) return last;
53
+ try {
54
+ return JSON.parse(body);
55
+ } catch {
56
+ throw new Error(`AbleTime MCP returned a non-JSON response: ${body.slice(0, 200)}`);
57
+ }
58
+ }
59
+ function unwrapToolResult(result) {
60
+ const record = asRecord(result);
61
+ if (!record) return result;
62
+ const content = record.content;
63
+ if (Array.isArray(content)) {
64
+ const texts = content.map((part) => {
65
+ const item = asRecord(part);
66
+ if (!item) return "";
67
+ return typeof item.text === "string" ? item.text : "";
68
+ }).filter(Boolean);
69
+ if (texts.length === 1) {
70
+ const text = texts[0];
71
+ try {
72
+ return JSON.parse(text);
73
+ } catch {
74
+ return text;
75
+ }
76
+ }
77
+ if (texts.length > 1) return texts.join("\n");
78
+ }
79
+ if (record.structuredContent != null) return record.structuredContent;
80
+ if (record.isError === true) {
81
+ const message = typeof record.message === "string" ? record.message : textsFromUnknown(result) || "AbleTime tool failed";
82
+ throw new Error(message);
83
+ }
84
+ return result;
85
+ }
86
+ function textsFromUnknown(value) {
87
+ const record = asRecord(value);
88
+ if (!record) return "";
89
+ if (typeof record.message === "string") return record.message;
90
+ if (typeof record.error === "string") return record.error;
91
+ return "";
92
+ }
93
+ async function abletimeMcpRequest(method, params, opts) {
94
+ const token = (opts?.token ?? getAbleTimeAccessToken())?.trim();
95
+ if (!token) {
96
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
97
+ }
98
+ const url = abletimeMcpUrl(
99
+ opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
100
+ );
101
+ const res = await httpFetch(url, {
102
+ method: "POST",
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ Accept: "application/json, text/event-stream",
106
+ Authorization: `Bearer ${token}`,
107
+ "MCP-Protocol-Version": MCP_PROTOCOL_VERSION
108
+ },
109
+ body: JSON.stringify({
110
+ jsonrpc: "2.0",
111
+ id: 1,
112
+ method,
113
+ ...params === void 0 ? {} : { params }
114
+ })
115
+ });
116
+ const body = await res.text().catch(() => "");
117
+ if (!res.ok) {
118
+ throw new Error(
119
+ rewriteAbleTimeError(
120
+ `AbleTime MCP error ${res.status}${body ? `: ${body.slice(0, 240)}` : ""}`
121
+ )
122
+ );
123
+ }
124
+ const contentType = res.headers.get("content-type") ?? "";
125
+ const parsed = contentType.includes("text/event-stream") ? parseSseJson(body) : (() => {
126
+ try {
127
+ return JSON.parse(body);
128
+ } catch {
129
+ return parseSseJson(body);
130
+ }
131
+ })();
132
+ const rpc = asRecord(parsed);
133
+ if (rpc?.error) {
134
+ const detail = typeof rpc.error.message === "string" && rpc.error.message.trim() ? rpc.error.message : `AbleTime MCP error ${rpc.error.code ?? ""}`.trim();
135
+ throw new Error(rewriteAbleTimeError(detail));
136
+ }
137
+ if (rpc && "result" in rpc) return rpc.result;
138
+ return parsed;
139
+ }
140
+ async function initializeIfNeeded(opts) {
141
+ try {
142
+ await abletimeMcpRequest(
143
+ "initialize",
144
+ {
145
+ protocolVersion: MCP_PROTOCOL_VERSION,
146
+ capabilities: {},
147
+ clientInfo: { name: "sideboard", version: "1.0.0" }
148
+ },
149
+ opts
150
+ );
151
+ } catch (err) {
152
+ const message = err instanceof Error ? err.message : String(err);
153
+ if (/already initialized|not (required|supported)|method not found|-32601/i.test(message)) {
154
+ return;
155
+ }
156
+ throw err;
157
+ }
158
+ }
159
+ async function callAbleTimeTool(name, args = {}, opts) {
160
+ if (!opts?.token && !isAbleTimeConnected()) {
161
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
162
+ }
163
+ const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, opts).then((result) => {
164
+ return unwrapToolResult(result);
165
+ });
166
+ try {
167
+ return await call();
168
+ } catch (err) {
169
+ const message = err instanceof Error ? err.message : String(err);
170
+ if (/initialize|session|not initialized|-32000/i.test(message)) {
171
+ await initializeIfNeeded(opts);
172
+ return await call();
173
+ }
174
+ throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
175
+ }
176
+ }
177
+
178
+ // src/integrations/abletime.ts
179
+ var CLOSED_STATES = /* @__PURE__ */ new Set([
180
+ "done",
181
+ "completed",
182
+ "canceled",
183
+ "cancelled",
184
+ "archived",
185
+ "closed"
186
+ ]);
187
+ function asRecord2(value) {
188
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
189
+ }
190
+ function asString(value) {
191
+ return typeof value === "string" ? value.trim() : "";
192
+ }
193
+ function firstString(record, keys) {
194
+ if (!record) return "";
195
+ for (const key of keys) {
196
+ const value = asString(record[key]);
197
+ if (value) return value;
198
+ }
199
+ return "";
200
+ }
201
+ function firstRecord(record, keys) {
202
+ if (!record) return null;
203
+ for (const key of keys) {
204
+ const value = asRecord2(record[key]);
205
+ if (value) return value;
206
+ }
207
+ return null;
208
+ }
209
+ function asList(value) {
210
+ if (Array.isArray(value)) return value;
211
+ const record = asRecord2(value);
212
+ if (!record) return [];
213
+ for (const key of ["data", "tasks", "projects", "items", "results", "nodes"]) {
214
+ if (Array.isArray(record[key])) return record[key];
215
+ }
216
+ return [];
217
+ }
218
+ function labelsOf(record) {
219
+ const raw = record.tags ?? record.labels ?? record.tag_names;
220
+ if (!Array.isArray(raw)) return [];
221
+ return raw.map((item) => {
222
+ if (typeof item === "string") return item.trim();
223
+ const rec = asRecord2(item);
224
+ return rec ? firstString(rec, ["name", "title", "label"]) : "";
225
+ }).filter(Boolean);
226
+ }
227
+ function assigneeOf(record) {
228
+ const nested = firstRecord(record, ["assignee", "assigned_to", "user", "owner"]);
229
+ const name = firstString(nested, ["name", "display_name", "full_name", "email"]) || firstString(record, ["assignee_name", "assigneeName"]);
230
+ if (!name) return void 0;
231
+ const id = firstString(nested, ["id", "user_id"]);
232
+ return { id: id || void 0, name };
233
+ }
234
+ function taskUrl(task, host) {
235
+ const base = normalizeAbleTimeHost(host);
236
+ const slug = encodeURIComponent(task.identifier || task.id);
237
+ return `${base}/tasks/${slug}`;
238
+ }
239
+ function mapAbleTimeTask(raw, host) {
240
+ const record = asRecord2(raw);
241
+ if (!record) return null;
242
+ const nested = firstRecord(record, ["task", "data"]) ?? record;
243
+ const id = firstString(nested, ["id", "task_id", "taskId"]);
244
+ const identifier = firstString(nested, ["reference", "ref", "identifier", "key", "code", "number"]) || id;
245
+ const title = firstString(nested, ["title", "name"]);
246
+ if (!id && !identifier && !title) return null;
247
+ const resolvedId = id || identifier;
248
+ const resolvedIdentifier = identifier || id;
249
+ const url = firstString(nested, ["url", "permalink", "html_url"]) || taskUrl({ id: resolvedId, identifier: resolvedIdentifier }, host);
250
+ return {
251
+ id: resolvedId,
252
+ identifier: resolvedIdentifier,
253
+ title: title || resolvedIdentifier,
254
+ url,
255
+ description: firstString(nested, ["description", "body", "details"]) || void 0,
256
+ state: firstString(nested, ["state", "board_state", "boardState", "status"]) || void 0,
257
+ projectId: firstString(nested, ["project_id", "projectId"]) || firstString(asRecord2(nested.project), ["id"]) || void 0,
258
+ categoryId: firstString(nested, ["category_id", "categoryId"]) || firstString(asRecord2(nested.category), ["id"]) || void 0,
259
+ assignee: assigneeOf(nested),
260
+ labels: labelsOf(nested)
261
+ };
262
+ }
263
+ function toAbleTimeIssueInfo(task) {
264
+ return {
265
+ id: task.id || task.identifier,
266
+ identifier: task.identifier,
267
+ title: task.title,
268
+ url: task.url,
269
+ labels: task.labels,
270
+ provider: "abletime",
271
+ assignee: task.assignee?.name,
272
+ assignees: task.assignee?.name ? [task.assignee.name] : void 0
273
+ };
274
+ }
275
+ function issueAttachmentForAbleTimeTask(task) {
276
+ return {
277
+ id: `abletime-${task.id || task.identifier}`,
278
+ name: task.identifier || task.title,
279
+ kind: "issue",
280
+ content: [
281
+ `Linked issue: ${task.identifier} \u2014 ${task.title}`,
282
+ task.url ? `URL: ${task.url}` : null,
283
+ task.labels.length ? `Labels: ${task.labels.join(", ")}` : null
284
+ ].filter(Boolean).join("\n")
285
+ };
286
+ }
287
+ function mapProject(raw) {
288
+ const record = asRecord2(raw);
289
+ if (!record) return null;
290
+ const id = firstString(record, ["id", "project_id"]);
291
+ const name = firstString(record, ["name", "title"]);
292
+ if (!id && !name) return null;
293
+ const categories = asList(record.categories ?? record.category).map((item) => {
294
+ const rec = asRecord2(item);
295
+ if (!rec) return null;
296
+ const categoryId = firstString(rec, ["id", "category_id"]);
297
+ const categoryName = firstString(rec, ["name", "title"]);
298
+ if (!categoryId && !categoryName) return null;
299
+ return { id: categoryId || categoryName, name: categoryName || categoryId };
300
+ }).filter((item) => Boolean(item));
301
+ return { id: id || name, name: name || id, categories };
302
+ }
303
+ function isOpenTask(task) {
304
+ const state = (task.state ?? "").toLowerCase();
305
+ return !state || !CLOSED_STATES.has(state);
306
+ }
307
+ async function getAbleTimeOrientation(opts) {
308
+ const raw = await callAbleTimeTool("orientation", {}, opts);
309
+ const record = asRecord2(raw);
310
+ const viewerRec = firstRecord(record, ["viewer", "user", "actor", "me", "person"]) ?? record;
311
+ const viewer = {
312
+ id: firstString(viewerRec, ["id", "user_id"]) || void 0,
313
+ name: firstString(viewerRec, ["name", "display_name", "full_name"]) || firstString(record, ["name"]) || void 0
314
+ };
315
+ const projects = asList(
316
+ record?.projects ?? record?.current_projects ?? record?.currentProjects
317
+ ).map(mapProject).filter((item) => Boolean(item));
318
+ const tasks = asList(record?.tasks ?? record?.active_tasks ?? record?.activeTasks).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
319
+ return { viewer, projects, tasks, raw };
320
+ }
321
+ async function listAbleTimeProjects(opts) {
322
+ const raw = await callAbleTimeTool("list_projects", { include_categories: true }, opts);
323
+ return asList(raw).map(mapProject).filter((item) => Boolean(item));
324
+ }
325
+ async function listAbleTimeTasks(opts) {
326
+ const args = {};
327
+ if (opts?.projectId) {
328
+ args.project_id = opts.projectId;
329
+ args.project = opts.projectId;
330
+ }
331
+ if (opts?.query) args.query = opts.query;
332
+ const raw = await callAbleTimeTool("list_tasks", args, opts);
333
+ const tasks = asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item));
334
+ return opts?.includeClosed ? tasks : tasks.filter(isOpenTask);
335
+ }
336
+ async function searchAbleTimeTasks(query, opts) {
337
+ const q = query.trim();
338
+ if (!q) return [];
339
+ const raw = await callAbleTimeTool("search_tasks", { query: q, q }, opts);
340
+ return asList(raw).map((item) => mapAbleTimeTask(item, opts?.host)).filter((item) => Boolean(item)).filter(isOpenTask);
341
+ }
342
+ async function getAbleTimeTask(id, opts) {
343
+ const raw = await callAbleTimeTool("get_task", { id, task_id: id }, opts);
344
+ const task = mapAbleTimeTask(raw, opts?.host);
345
+ if (!task) throw new Error(`AbleTime task not found: ${id}`);
346
+ return task;
347
+ }
348
+ async function createAbleTimeTask(input, opts) {
349
+ const title = input.title.trim();
350
+ if (!title) throw new Error("AbleTime task title is required");
351
+ const project = await resolveAbleTimeProject(input.projectId, opts);
352
+ const category = resolveAbleTimeCategory(project, input.categoryId);
353
+ const raw = await callAbleTimeTool(
354
+ "create_task",
355
+ {
356
+ title,
357
+ description: input.description?.trim() || void 0,
358
+ state: input.state ?? "todo",
359
+ project: project.id,
360
+ project_id: project.id,
361
+ category: category?.id,
362
+ category_id: category?.id
363
+ },
364
+ opts
365
+ );
366
+ const task = mapAbleTimeTask(raw, opts?.host);
367
+ if (!task) throw new Error("AbleTime create_task returned no task");
368
+ return task;
369
+ }
370
+ async function resolveAbleTimeProject(projectId, opts) {
371
+ const wanted = projectId?.trim().toLowerCase();
372
+ const fromOrientation = (await getAbleTimeOrientation(opts).catch(() => null))?.projects ?? [];
373
+ const listed = fromOrientation.length > 0 ? fromOrientation : await listAbleTimeProjects(opts);
374
+ if (listed.length === 0) {
375
+ throw new Error(
376
+ rewriteAbleTimeError(
377
+ "AbleTime has no projects to create a task in \u2014 create a project in AbleTime first."
378
+ )
379
+ );
380
+ }
381
+ if (!wanted) return listed[0];
382
+ const found = listed.find(
383
+ (project) => project.id.toLowerCase() === wanted || project.name.toLowerCase() === wanted
384
+ );
385
+ if (!found) {
386
+ const names = listed.map((project) => project.name).join(", ") || "(none)";
387
+ throw new Error(`AbleTime project not found: ${projectId}. Known: ${names}`);
388
+ }
389
+ return found;
390
+ }
391
+ function resolveAbleTimeCategory(project, categoryId) {
392
+ if (project.categories.length === 0) return void 0;
393
+ const wanted = categoryId?.trim().toLowerCase();
394
+ if (!wanted) return project.categories[0];
395
+ return project.categories.find(
396
+ (category) => category.id.toLowerCase() === wanted || category.name.toLowerCase() === wanted
397
+ ) ?? project.categories[0];
398
+ }
399
+ function titlesMatch(a, b) {
400
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
401
+ }
402
+ async function ensureAbleTimeTask(input, opts) {
403
+ const title = input.title.trim();
404
+ if (!title) throw new Error("AbleTime task title is required");
405
+ const searched = await searchAbleTimeTasks(title, opts).catch(() => []);
406
+ const exact = searched.find((task) => titlesMatch(task.identifier, title)) || searched.find((task) => titlesMatch(task.title, title));
407
+ if (exact) return { ...exact, created: false };
408
+ const created = await createAbleTimeTask(
409
+ {
410
+ title,
411
+ description: input.description,
412
+ projectId: input.projectId,
413
+ categoryId: input.categoryId,
414
+ state: "todo"
415
+ },
416
+ opts
417
+ );
418
+ return { ...created, created: true };
419
+ }
420
+ async function listAbleTimeAssignedIssues(opts) {
421
+ if (!opts?.token && !isAbleTimeConnected()) {
422
+ throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
423
+ }
424
+ const [orientation, listed] = await Promise.all([
425
+ getAbleTimeOrientation(opts).catch(() => null),
426
+ listAbleTimeTasks(opts)
427
+ ]);
428
+ const issues = listed.map(toAbleTimeIssueInfo);
429
+ return {
430
+ viewer: orientation?.viewer ?? {},
431
+ issues
432
+ };
433
+ }
434
+ async function verifyAbleTimeConnection(input) {
435
+ const token = input.token.trim();
436
+ if (!token) throw new Error("AbleTime personal access token is required");
437
+ const host = input.host?.trim() || void 0;
438
+ const orientation = await getAbleTimeOrientation({ token, host });
439
+ saveAbleTimeConnection({
440
+ accessToken: token,
441
+ host,
442
+ viewerName: orientation.viewer.name
443
+ });
444
+ return orientation.viewer;
445
+ }
446
+
447
+ export {
448
+ DEFAULT_ABLETIME_HOST,
449
+ ABLETIME_MCP_PATH,
450
+ normalizeAbleTimeHost,
451
+ abletimeMcpUrl,
452
+ rewriteAbleTimeError,
453
+ abletimeMcpRequest,
454
+ callAbleTimeTool,
455
+ taskUrl,
456
+ mapAbleTimeTask,
457
+ toAbleTimeIssueInfo,
458
+ issueAttachmentForAbleTimeTask,
459
+ getAbleTimeOrientation,
460
+ listAbleTimeProjects,
461
+ listAbleTimeTasks,
462
+ searchAbleTimeTasks,
463
+ getAbleTimeTask,
464
+ createAbleTimeTask,
465
+ ensureAbleTimeTask,
466
+ listAbleTimeAssignedIssues,
467
+ verifyAbleTimeConnection
468
+ };
@@ -6,7 +6,7 @@ import {
6
6
  loadBrightsyConfig,
7
7
  refreshBrightsyAccessToken,
8
8
  saveBrightsyConfig
9
- } from "./chunk-R7FGPBKL.js";
9
+ } from "./chunk-S42XV45P.js";
10
10
  import {
11
11
  run
12
12
  } from "./chunk-KPIYENTF.js";
@@ -0,0 +1,32 @@
1
+ // src/http/fetch.ts
2
+ var injected = null;
3
+ function setHttpFetchImpl(fetchImpl) {
4
+ injected = fetchImpl;
5
+ }
6
+ function formatFetchError(err, url) {
7
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
8
+ const cause = err.cause;
9
+ let detail = "";
10
+ if (cause instanceof Error) {
11
+ const code = typeof cause.code === "string" ? cause.code : void 0;
12
+ detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
13
+ } else if (cause != null) {
14
+ detail = ` [${String(cause)}]`;
15
+ }
16
+ return `${err.message}${detail} (${url})`;
17
+ }
18
+ async function httpFetch(input, init) {
19
+ const url = typeof input === "string" ? input : input.href;
20
+ const fn = injected ?? globalThis.fetch.bind(globalThis);
21
+ try {
22
+ return await fn(url, init);
23
+ } catch (err) {
24
+ throw new Error(formatFetchError(err, url));
25
+ }
26
+ }
27
+
28
+ export {
29
+ setHttpFetchImpl,
30
+ formatFetchError,
31
+ httpFetch
32
+ };
@@ -193,6 +193,9 @@ function normalizeVault(raw) {
193
193
  if (typeof raw.githubPat === "string" && raw.githubPat.trim()) {
194
194
  out.githubPat = raw.githubPat.trim();
195
195
  }
196
+ if (typeof raw.abletimeAccessToken === "string" && raw.abletimeAccessToken.trim()) {
197
+ out.abletimeAccessToken = raw.abletimeAccessToken.trim();
198
+ }
196
199
  if (raw.environment && typeof raw.environment === "object") {
197
200
  const environment = {};
198
201
  for (const [key, value] of Object.entries(raw.environment)) {
@@ -252,6 +255,7 @@ function toPublicAppSettings(settings) {
252
255
  const slackClientSecret = integrations.slackClientSecret;
253
256
  const slackAppToken = integrations.slackAppToken;
254
257
  const githubPat = integrations.githubPat;
258
+ const abletimeAccessToken = integrations.abletimeAccessToken;
255
259
  delete integrations.linearApiKey;
256
260
  delete integrations.linearAccessToken;
257
261
  delete integrations.linearRefreshToken;
@@ -259,6 +263,7 @@ function toPublicAppSettings(settings) {
259
263
  delete integrations.slackClientSecret;
260
264
  delete integrations.slackAppToken;
261
265
  delete integrations.githubPat;
266
+ delete integrations.abletimeAccessToken;
262
267
  return {
263
268
  ...settings,
264
269
  environment,
@@ -268,7 +273,8 @@ function toPublicAppSettings(settings) {
268
273
  hasLinearOAuth: hasLinearOAuth(settings.integrations),
269
274
  hasSlackClientSecret: Boolean(slackClientSecret?.trim()),
270
275
  hasSlackAppToken: Boolean(slackAppToken?.trim()),
271
- hasGithubPat: Boolean(githubPat?.trim())
276
+ hasGithubPat: Boolean(githubPat?.trim()),
277
+ hasAbleTimeToken: Boolean(abletimeAccessToken?.trim())
272
278
  }
273
279
  };
274
280
  }
@@ -399,6 +405,18 @@ function normalizeIntegrations(raw) {
399
405
  const pat = source.githubPat.trim();
400
406
  if (pat) out.githubPat = pat;
401
407
  }
408
+ if (typeof source.abletimeAccessToken === "string") {
409
+ const token = source.abletimeAccessToken.trim();
410
+ if (token) out.abletimeAccessToken = token;
411
+ }
412
+ if (typeof source.abletimeHost === "string") {
413
+ const host = source.abletimeHost.trim();
414
+ if (host) out.abletimeHost = host;
415
+ }
416
+ if (typeof source.abletimeViewerName === "string") {
417
+ const name = source.abletimeViewerName.trim().slice(0, 120);
418
+ if (name) out.abletimeViewerName = name;
419
+ }
402
420
  return out;
403
421
  }
404
422
  function normalizeDefaults(raw) {
@@ -557,7 +575,7 @@ function readSettingsFile() {
557
575
  }
558
576
  function diskHoldsSecrets(disk) {
559
577
  return Boolean(
560
- disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || Object.keys(disk.environment).length > 0
578
+ disk.integrations.linearApiKey || disk.integrations.linearAccessToken || disk.integrations.linearRefreshToken || disk.integrations.linearClientSecret || disk.integrations.slackClientSecret || disk.integrations.slackAppToken || disk.integrations.githubPat || disk.integrations.abletimeAccessToken || Object.keys(disk.environment).length > 0
561
579
  );
562
580
  }
563
581
  function mergeVault(disk) {
@@ -585,6 +603,9 @@ function mergeVault(disk) {
585
603
  if (vault.githubPat && !integrations.githubPat) {
586
604
  integrations.githubPat = vault.githubPat;
587
605
  }
606
+ if (vault.abletimeAccessToken && !integrations.abletimeAccessToken) {
607
+ integrations.abletimeAccessToken = vault.abletimeAccessToken;
608
+ }
588
609
  return { ...disk, environment, integrations };
589
610
  }
590
611
  function persistSplit(settings) {
@@ -596,6 +617,7 @@ function persistSplit(settings) {
596
617
  const slackClientSecret = integrations.slackClientSecret;
597
618
  const slackAppToken = integrations.slackAppToken;
598
619
  const githubPat = integrations.githubPat;
620
+ const abletimeAccessToken = integrations.abletimeAccessToken;
599
621
  delete integrations.linearApiKey;
600
622
  delete integrations.linearAccessToken;
601
623
  delete integrations.linearRefreshToken;
@@ -603,6 +625,7 @@ function persistSplit(settings) {
603
625
  delete integrations.slackClientSecret;
604
626
  delete integrations.slackAppToken;
605
627
  delete integrations.githubPat;
628
+ delete integrations.abletimeAccessToken;
606
629
  writePrivateFile(
607
630
  appSettingsPath(),
608
631
  `${JSON.stringify({ ...settings, environment: {}, integrations }, null, 2)}
@@ -616,6 +639,7 @@ function persistSplit(settings) {
616
639
  slackClientSecret,
617
640
  slackAppToken,
618
641
  githubPat,
642
+ abletimeAccessToken,
619
643
  environment: settings.environment
620
644
  });
621
645
  }
@@ -795,6 +819,28 @@ function updateIntegrationsSettings(patch) {
795
819
  integrations.githubPat = patch.githubPat.trim();
796
820
  }
797
821
  }
822
+ if ("abletimeAccessToken" in patch) {
823
+ if (patch.abletimeAccessToken == null || patch.abletimeAccessToken.trim() === "") {
824
+ delete integrations.abletimeAccessToken;
825
+ delete integrations.abletimeViewerName;
826
+ } else {
827
+ integrations.abletimeAccessToken = patch.abletimeAccessToken.trim();
828
+ }
829
+ }
830
+ if ("abletimeHost" in patch) {
831
+ if (patch.abletimeHost == null || patch.abletimeHost.trim() === "") {
832
+ delete integrations.abletimeHost;
833
+ } else {
834
+ integrations.abletimeHost = patch.abletimeHost.trim();
835
+ }
836
+ }
837
+ if ("abletimeViewerName" in patch) {
838
+ if (patch.abletimeViewerName == null || patch.abletimeViewerName.trim() === "") {
839
+ delete integrations.abletimeViewerName;
840
+ } else {
841
+ integrations.abletimeViewerName = patch.abletimeViewerName.trim().slice(0, 120);
842
+ }
843
+ }
798
844
  return saveAppSettings({ ...current, integrations });
799
845
  }
800
846
  function updateDefaultsSettings(patch) {
@@ -899,6 +945,35 @@ function disconnectLinearConnection() {
899
945
  delete integrations.linearOrganizationName;
900
946
  return saveAppSettings({ ...current, integrations });
901
947
  }
948
+ function isAbleTimeConnected(settings = loadAppSettings()) {
949
+ return Boolean(settings.integrations.abletimeAccessToken?.trim());
950
+ }
951
+ function getAbleTimeAccessToken(settings = loadAppSettings()) {
952
+ const token = settings.integrations.abletimeAccessToken?.trim();
953
+ return token || null;
954
+ }
955
+ function getAbleTimeHost(settings = loadAppSettings()) {
956
+ return settings.integrations.abletimeHost?.trim() || "https://track.abletime.com";
957
+ }
958
+ function saveAbleTimeConnection(input) {
959
+ const current = loadAppSettings();
960
+ const integrations = { ...current.integrations };
961
+ integrations.abletimeAccessToken = input.accessToken.trim();
962
+ if (input.host?.trim()) {
963
+ integrations.abletimeHost = input.host.trim();
964
+ }
965
+ if (input.viewerName?.trim()) {
966
+ integrations.abletimeViewerName = input.viewerName.trim().slice(0, 120);
967
+ }
968
+ return saveAppSettings({ ...current, integrations });
969
+ }
970
+ function disconnectAbleTimeConnection() {
971
+ const current = loadAppSettings();
972
+ const integrations = { ...current.integrations };
973
+ delete integrations.abletimeAccessToken;
974
+ delete integrations.abletimeViewerName;
975
+ return saveAppSettings({ ...current, integrations });
976
+ }
902
977
  function getIssueSource(settings = loadAppSettings()) {
903
978
  return settings.integrations.issueSource ?? "github";
904
979
  }
@@ -912,6 +987,7 @@ function getGithubPat(settings = loadAppSettings()) {
912
987
  function isIssueSourceConnected(source, settings = loadAppSettings()) {
913
988
  if (source === "github") return true;
914
989
  if (source === "linear") return isLinearConnected(settings);
990
+ if (source === "abletime") return isAbleTimeConnected(settings);
915
991
  return false;
916
992
  }
917
993
  function resolveEffectiveIssueSource(settings = loadAppSettings()) {
@@ -1173,6 +1249,11 @@ export {
1173
1249
  isLinearConnected,
1174
1250
  saveLinearOAuth,
1175
1251
  disconnectLinearConnection,
1252
+ isAbleTimeConnected,
1253
+ getAbleTimeAccessToken,
1254
+ getAbleTimeHost,
1255
+ saveAbleTimeConnection,
1256
+ disconnectAbleTimeConnection,
1176
1257
  getIssueSource,
1177
1258
  getGithubGitAuthMode,
1178
1259
  getGithubPat,