@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.
- package/dist/abletime-DDTV7WO3.js +36 -0
- package/dist/abletime-VTP4JR63.js +37 -0
- package/dist/{agents-LGJLZMM3.js → agents-O3AJMI2Y.js} +9 -8
- package/dist/{agents-CI6NGKRO.js → agents-RTQFF7PY.js} +9 -8
- package/dist/{app-settings-6RPALX4J.js → app-settings-U5RZKUXG.js} +11 -1
- package/dist/{app-settings-E7NEQY7D.js → app-settings-YQ7YX2CC.js} +11 -1
- package/dist/{chunk-MNL4FSKY.js → chunk-2ESCEK2Q.js} +4 -4
- package/dist/chunk-2XXAAIN3.js +463 -0
- package/dist/{chunk-HZPO3SSZ.js → chunk-57GIFU3X.js} +4 -3
- package/dist/chunk-6GN5WPYZ.js +31 -0
- package/dist/chunk-AT346U23.js +468 -0
- package/dist/{chunk-LLX3OMHU.js → chunk-ED4UPEJX.js} +1 -1
- package/dist/chunk-ELBYOGVA.js +32 -0
- package/dist/{chunk-HUYEU4GS.js → chunk-GJBHOJCZ.js} +83 -2
- package/dist/{chunk-ZND6XV6J.js → chunk-GKKHXZCT.js} +83 -2
- package/dist/{chunk-UDQI3N47.js → chunk-HQQNLDVC.js} +2 -2
- package/dist/{chunk-G6X6UFJO.js → chunk-J5IBSVB3.js} +42 -12
- package/dist/{chunk-RSFCB23A.js → chunk-K7EX47QG.js} +4 -3
- package/dist/{chunk-PK4PQUPU.js → chunk-KBBXNS2V.js} +6 -6
- package/dist/{chunk-SIPDL62B.js → chunk-KBWND62T.js} +6 -6
- package/dist/{chunk-VE22NWBD.js → chunk-LOKXPQ4U.js} +2 -2
- package/dist/{chunk-AQESMTCZ.js → chunk-M267JPEA.js} +3 -29
- package/dist/{chunk-MGYCQAZ6.js → chunk-N62K3KXX.js} +6 -6
- package/dist/{chunk-HLJUNJBF.js → chunk-PM3C2J6K.js} +42 -12
- package/dist/{chunk-HRSBZIXS.js → chunk-QAV3HGVS.js} +2 -2
- package/dist/{chunk-SYLTXDRF.js → chunk-RDULVW3E.js} +2 -2
- package/dist/{chunk-R7FGPBKL.js → chunk-S42XV45P.js} +3 -25
- package/dist/{chunk-VFXZTSAD.js → chunk-XIKEUCNC.js} +55 -31
- package/dist/{chunk-HULESWLI.js → chunk-XUEI4GCF.js} +4 -4
- package/dist/{chunk-SMWJAE74.js → chunk-Z5LYMW7M.js} +55 -31
- package/dist/{connected-teams-S6E3D5C7.js → connected-teams-4A2AXDNN.js} +3 -2
- package/dist/{connected-teams-3BWNJIRY.js → connected-teams-IJUJQ7R6.js} +3 -2
- package/dist/{coordinator-prompt-MEEUOEF2.js → coordinator-prompt-FYMWE33S.js} +4 -4
- package/dist/{coordinator-prompt-SPJSGAQS.js → coordinator-prompt-Y737IIFR.js} +4 -4
- package/dist/{global-workspace-KNESLWKG.js → global-workspace-6KH6BSKL.js} +5 -5
- package/dist/{global-workspace-BMWP7YZC.js → global-workspace-ZFKNLBZA.js} +5 -5
- package/dist/index.cjs +1150 -353
- package/dist/index.d.cts +173 -13
- package/dist/index.d.ts +173 -13
- package/dist/index.js +435 -240
- package/dist/mcp/run-stdio.cjs +1041 -300
- package/dist/mcp/run-stdio.js +339 -187
- package/dist/{oauth-FT7EL2PY.js → oauth-A7XXI2TM.js} +2 -1
- package/dist/{oauth-43NNH6C6.js → oauth-YJBAURIJ.js} +2 -1
- package/dist/{orchestrator-JA65XKB6.js → orchestrator-2BRAPJ47.js} +11 -10
- package/dist/{orchestrator-5TN2DJWO.js → orchestrator-3YDPPZHZ.js} +11 -10
- package/dist/{thread-store-5MLYY35S.js → thread-store-CRLQJ2HM.js} +3 -1
- package/dist/{thread-store-F6BCARQG.js → thread-store-FADXSMEJ.js} +3 -1
- package/dist/{workspaces-HSYRYJYV.js → workspaces-3RRF3LVF.js} +6 -6
- package/dist/{workspaces-O4QZOASB.js → workspaces-AYI5DHK4.js} +6 -6
- package/dist/{worktree-ERHC7Q4F.js → worktree-BC6XDMQK.js} +3 -3
- package/dist/{worktree-JVGSLSLJ.js → worktree-MX7XBX6Z.js} +3 -3
- 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
|
+
};
|
|
@@ -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,
|