@sideboard-ai/core 0.1.324 → 0.1.326
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-CU2UPMJF.js → abletime-RDHYSZL2.js} +5 -4
- package/dist/{abletime-5ZY3ZUUO.js → abletime-RYYFGKMU.js} +6 -5
- package/dist/{abletime-oauth-7EOSBEFM.js → abletime-oauth-OYGS2DAU.js} +3 -3
- package/dist/{abletime-oauth-4TTCR3CT.js → abletime-oauth-QR2QDVMK.js} +3 -3
- package/dist/{abletime-rest-KOKE7V4B.js → abletime-rest-BKYKNPTC.js} +3 -3
- package/dist/{abletime-rest-3TG4G7LW.js → abletime-rest-QDPTWNS4.js} +3 -3
- package/dist/agents/claude-runner.cjs +59 -22
- package/dist/agents/claude-runner.js +39 -16
- package/dist/agents/cursor-runner.cjs +97 -32
- package/dist/agents/cursor-runner.js +74 -24
- package/dist/{agents-YK65UDLT.js → agents-HANWZNI6.js} +9 -8
- package/dist/{agents-LUSTCMDN.js → agents-QO7GKBYT.js} +12 -11
- package/dist/{app-settings-XIF4EC4K.js → app-settings-RXBH7KY4.js} +1 -1
- package/dist/{app-settings-I5HM5RMP.js → app-settings-TUVLZNDE.js} +1 -1
- package/dist/{chunk-7MQSYPAY.js → chunk-2EOLWHXH.js} +206 -188
- package/dist/{chunk-QOS4465Y.js → chunk-3Q4XNYHY.js} +1 -1
- package/dist/{chunk-RR4WCGNB.js → chunk-46FECFZU.js} +18 -15
- package/dist/{chunk-5GDJSFCT.js → chunk-4H5MWZ6J.js} +2 -2
- package/dist/{chunk-EQ52JR3Q.js → chunk-5MXLDDYF.js} +1 -1
- package/dist/{chunk-NCHOTN3C.js → chunk-5YCKATG6.js} +4 -3
- package/dist/{chunk-ROOZDDFA.js → chunk-75RPUXJS.js} +7 -8
- package/dist/{chunk-INBUV7HY.js → chunk-B244TJWC.js} +2 -2
- package/dist/{chunk-JH5SMVM5.js → chunk-BBNWKL4I.js} +268 -235
- package/dist/{chunk-ODH3XHMI.js → chunk-C7HUCBX7.js} +5 -5
- package/dist/{chunk-OIHRC2ZJ.js → chunk-D744HOI5.js} +25 -3
- package/dist/chunk-DBKHL7NC.js +214 -0
- package/dist/chunk-F6GE7V3Q.js +216 -0
- package/dist/{chunk-JE3JLHMO.js → chunk-FWVUGLBG.js} +3 -3
- package/dist/{chunk-DF5BM5OY.js → chunk-GP3RLZ6A.js} +207 -186
- package/dist/{chunk-FR3SAYUL.js → chunk-JM2TGOHP.js} +5 -5
- package/dist/{chunk-Y3WREIPQ.js → chunk-K7M7MIWL.js} +12 -210
- package/dist/{chunk-FUFNMUNR.js → chunk-KECHK3ZT.js} +7 -8
- package/dist/{chunk-EKU7FQVG.js → chunk-KXRF6RNL.js} +2 -2
- package/dist/chunk-LRHUBGEM.js +1259 -0
- package/dist/{chunk-MZIZTSR2.js → chunk-MAW7DIG7.js} +2 -2
- package/dist/{chunk-ZHGMRBSC.js → chunk-NURZOXGN.js} +4 -4
- package/dist/{chunk-QIC2JXHW.js → chunk-OOXRMTU6.js} +1 -1
- package/dist/{chunk-5XNT6BCG.js → chunk-OPXEJJBM.js} +1 -1
- package/dist/chunk-OYRN356W.js +80 -0
- package/dist/{chunk-7PVT73H6.js → chunk-PB6HW7SA.js} +12 -210
- package/dist/{chunk-B4CDSSRN.js → chunk-Q3FHTGIW.js} +2 -0
- package/dist/{chunk-7D5QIU3N.js → chunk-QG4CYRA3.js} +301 -268
- package/dist/{chunk-EJ66LEKH.js → chunk-QPSKQP6E.js} +24 -3
- package/dist/{chunk-Q3YJZPQI.js → chunk-RZVOX2MT.js} +3 -3
- package/dist/chunk-SO7G6O24.js +1445 -0
- package/dist/{chunk-YDUHLWI5.js → chunk-SUZOPJZC.js} +21 -7
- package/dist/chunk-TMCK5TLA.js +25 -0
- package/dist/chunk-W3ZLE4PO.js +77 -0
- package/dist/chunk-WUCAVIIP.js +28 -0
- package/dist/{chunk-BYUCNIXK.js → chunk-XLVLMQKN.js} +13 -9
- package/dist/{chunk-ZIRQZUZY.js → chunk-YGVGXQA7.js} +3 -3
- package/dist/{chunk-NXUN4U5P.js → chunk-YWQF7WAG.js} +2 -2
- package/dist/{chunk-IFWGG2M2.js → chunk-ZSHS46V5.js} +2 -0
- package/dist/{chunk-74OPNF7U.js → chunk-ZUAYA57W.js} +2 -2
- package/dist/{conductor-SPZOTC7W.js → conductor-4GVTHUUB.js} +7 -7
- package/dist/{conductor-JFABMXDA.js → conductor-TGWI6ETZ.js} +6 -6
- package/dist/{coordinator-prompt-K66RGNWK.js → coordinator-prompt-3E6SIDTM.js} +4 -4
- package/dist/{coordinator-prompt-Z6UFHFDH.js → coordinator-prompt-WEYJREV6.js} +4 -4
- package/dist/{cursor-recover-TEBFKAZ4.js → cursor-recover-7F46PAJ5.js} +1 -1
- package/dist/{cursor-recover-OWPQUHFH.js → cursor-recover-BDIAZIE3.js} +1 -1
- package/dist/{global-workspace-A6UO6PJV.js → global-workspace-FPDZKUPF.js} +4 -4
- package/dist/{global-workspace-RHEO55B4.js → global-workspace-UIEXAG2B.js} +4 -4
- package/dist/index.cjs +21094 -20840
- package/dist/index.d.cts +36 -17
- package/dist/index.d.ts +36 -17
- package/dist/index.js +343 -1678
- package/dist/linear-6S7MK5YK.js +69 -0
- package/dist/linear-Z2T4KUAQ.js +67 -0
- package/dist/linear-list-A37WLUBO.js +6 -0
- package/dist/linear-list-Q4M7AZO2.js +8 -0
- package/dist/mcp/run-stdio.cjs +15903 -15640
- package/dist/mcp/run-stdio.js +67 -1247
- package/dist/{optional-cli-RMMS5L6F.js → optional-cli-AANBL4U5.js} +3 -3
- package/dist/{optional-cli-Y3Z3GIPZ.js → optional-cli-CA4LL322.js} +4 -4
- package/dist/{orchestrator-7VTAUY24.js → orchestrator-7FVDBE7Y.js} +15 -13
- package/dist/{orchestrator-2O6OJQLX.js → orchestrator-RQXRZW7Z.js} +14 -12
- package/dist/{thread-store-3F7GR64A.js → thread-store-4ZN3DS2W.js} +1 -1
- package/dist/{thread-store-JRSL4HQY.js → thread-store-IC4BQXDW.js} +1 -1
- package/dist/{workspaces-NRKEXOYR.js → workspaces-LEW4UP56.js} +4 -4
- package/dist/{workspaces-42E6AGXD.js → workspaces-QONNLHNR.js} +4 -4
- package/dist/{worktree-ZDA2UJ7C.js → worktree-6GIONDQO.js} +3 -3
- package/dist/{worktree-4NGXCCUO.js → worktree-KFEJAQ2H.js} +3 -3
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
getAbleTimeHost,
|
|
6
6
|
isAbleTimeConnected
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QPSKQP6E.js";
|
|
8
8
|
|
|
9
9
|
// src/integrations/abletime-mcp.ts
|
|
10
10
|
var DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
|
|
@@ -153,7 +153,7 @@ function textsFromUnknown(value) {
|
|
|
153
153
|
return "";
|
|
154
154
|
}
|
|
155
155
|
async function abletimeMcpRequest(method, params, opts) {
|
|
156
|
-
const rawToken = opts?.token ?? await import("./abletime-oauth-
|
|
156
|
+
const rawToken = opts?.token ?? await import("./abletime-oauth-OYGS2DAU.js").then((m) => m.ensureAbleTimeAccessToken());
|
|
157
157
|
if (!rawToken?.trim()) {
|
|
158
158
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
159
159
|
}
|
|
@@ -224,9 +224,9 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
224
224
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
225
225
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
226
226
|
}
|
|
227
|
-
const rawToken = opts?.token ?? await import("./abletime-oauth-
|
|
227
|
+
const rawToken = opts?.token ?? await import("./abletime-oauth-OYGS2DAU.js").then((m) => m.ensureAbleTimeAccessToken());
|
|
228
228
|
if (rawToken && ableTimeUsesRest(rawToken)) {
|
|
229
|
-
const { callAbleTimeRestTool } = await import("./abletime-rest-
|
|
229
|
+
const { callAbleTimeRestTool } = await import("./abletime-rest-BKYKNPTC.js");
|
|
230
230
|
return callAbleTimeRestTool(name, args, { ...opts, token: rawToken });
|
|
231
231
|
}
|
|
232
232
|
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
@@ -242,7 +242,7 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
242
242
|
return await call();
|
|
243
243
|
}
|
|
244
244
|
if (rawToken && ableTimeUsesRest(rawToken) && isAbleTimeRejectedCredential(message)) {
|
|
245
|
-
const { callAbleTimeRestTool } = await import("./abletime-rest-
|
|
245
|
+
const { callAbleTimeRestTool } = await import("./abletime-rest-BKYKNPTC.js");
|
|
246
246
|
return callAbleTimeRestTool(name, args, { ...opts, token: rawToken });
|
|
247
247
|
}
|
|
248
248
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
@@ -471,6 +471,20 @@ function fromInclusiveInputUsage(opts) {
|
|
|
471
471
|
function requestOccupancy(u) {
|
|
472
472
|
return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
|
|
473
473
|
}
|
|
474
|
+
function subtractUsage(total, part) {
|
|
475
|
+
if (!total) return null;
|
|
476
|
+
const cacheRead = Math.max(0, (total.cacheReadTokens ?? 0) - (part.cacheReadTokens ?? 0));
|
|
477
|
+
const cacheWrite = Math.max(0, (total.cacheWriteTokens ?? 0) - (part.cacheWriteTokens ?? 0));
|
|
478
|
+
return {
|
|
479
|
+
inputTokens: Math.max(0, total.inputTokens - part.inputTokens),
|
|
480
|
+
outputTokens: Math.max(0, total.outputTokens - part.outputTokens),
|
|
481
|
+
cacheReadTokens: cacheRead || void 0,
|
|
482
|
+
cacheWriteTokens: cacheWrite || void 0,
|
|
483
|
+
costUsd: total.costUsd != null && part.costUsd != null ? Math.max(0, total.costUsd - part.costUsd) : total.costUsd,
|
|
484
|
+
sessionCostUsd: total.sessionCostUsd,
|
|
485
|
+
lastRequestTokens: total.lastRequestTokens
|
|
486
|
+
};
|
|
487
|
+
}
|
|
474
488
|
function mergeUsage(a, b) {
|
|
475
489
|
return {
|
|
476
490
|
inputTokens: (a?.inputTokens ?? 0) + b.inputTokens,
|
|
@@ -517,10 +531,11 @@ function applyTurnUsage(current, incoming, scope = "request") {
|
|
|
517
531
|
...incoming.sessionCostUsd != null ? { sessionCostUsd: incoming.sessionCostUsd } : {}
|
|
518
532
|
};
|
|
519
533
|
}
|
|
534
|
+
const explicit = incoming.lastRequestTokens != null && incoming.lastRequestTokens > 0 ? incoming.lastRequestTokens : void 0;
|
|
520
535
|
return {
|
|
521
536
|
...incoming,
|
|
522
537
|
costUsd: incoming.costUsd ?? current?.costUsd,
|
|
523
|
-
lastRequestTokens: current?.lastRequestTokens ??
|
|
538
|
+
lastRequestTokens: current?.lastRequestTokens ?? explicit,
|
|
524
539
|
...incoming.sessionCostUsd == null && current?.sessionCostUsd != null ? { sessionCostUsd: current.sessionCostUsd } : {}
|
|
525
540
|
};
|
|
526
541
|
}
|
|
@@ -562,7 +577,8 @@ function previousCostBaseline(messages) {
|
|
|
562
577
|
}
|
|
563
578
|
return {
|
|
564
579
|
sessionCostUsd: lastSession ?? summed,
|
|
565
|
-
lastCostUsd: lastCost
|
|
580
|
+
lastCostUsd: lastCost,
|
|
581
|
+
hasSessionSnapshot: lastSession != null
|
|
566
582
|
};
|
|
567
583
|
}
|
|
568
584
|
function turnCostUsdFromProviderSnapshot(opts) {
|
|
@@ -576,6 +592,9 @@ function turnCostUsdFromProviderSnapshot(opts) {
|
|
|
576
592
|
if (session + USD_EPS >= previous) {
|
|
577
593
|
return roundUsd(Math.max(0, session - previous));
|
|
578
594
|
}
|
|
595
|
+
if (opts.previousHasSessionSnapshot) {
|
|
596
|
+
return roundUsd(session);
|
|
597
|
+
}
|
|
579
598
|
if (last != null && session + USD_EPS >= last) {
|
|
580
599
|
return roundUsd(Math.max(0, session - last));
|
|
581
600
|
}
|
|
@@ -590,7 +609,8 @@ function withTurnScopedCost(usage, previousMessages) {
|
|
|
590
609
|
reportedTurnCostUsd: usage.costUsd,
|
|
591
610
|
sessionCostUsd: usage.sessionCostUsd,
|
|
592
611
|
previousSessionCostUsd: baseline.sessionCostUsd,
|
|
593
|
-
previousLastCostUsd: baseline.lastCostUsd
|
|
612
|
+
previousLastCostUsd: baseline.lastCostUsd,
|
|
613
|
+
previousHasSessionSnapshot: baseline.hasSessionSnapshot
|
|
594
614
|
});
|
|
595
615
|
if (costUsd == null) {
|
|
596
616
|
if (usage.costUsd == null) return usage;
|
|
@@ -2371,6 +2391,8 @@ export {
|
|
|
2371
2391
|
formatViewerContextDirective,
|
|
2372
2392
|
formatViewerContextReminder,
|
|
2373
2393
|
fromInclusiveInputUsage,
|
|
2394
|
+
requestOccupancy,
|
|
2395
|
+
subtractUsage,
|
|
2374
2396
|
sumUsageList,
|
|
2375
2397
|
applyTurnUsage,
|
|
2376
2398
|
contextTokens,
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
httpFetch
|
|
3
|
+
} from "./chunk-JAT3JFOS.js";
|
|
4
|
+
import {
|
|
5
|
+
ATTACHMENTS_DIR,
|
|
6
|
+
attachmentsGitignoreBody
|
|
7
|
+
} from "./chunk-6K64FJDD.js";
|
|
8
|
+
|
|
9
|
+
// src/integrations/issue-attachments.ts
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
11
|
+
import { basename, extname, join } from "path";
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
var ISSUE_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;
|
|
14
|
+
var MARKDOWN_IMAGE_RE = /!\[[^\]]*]\(\s*<?(https?:\/\/[^)\s>]+)>?/gi;
|
|
15
|
+
var MARKDOWN_LINK_RE = /(?<!!)\[[^\]]*]\(\s*<?(https?:\/\/[^)\s>]+)>?/gi;
|
|
16
|
+
var HTML_SRC_RE = /\b(?:src|href)\s*=\s*["'](https?:\/\/[^"']+)["']/gi;
|
|
17
|
+
var BARE_URL_RE = /https?:\/\/[^\s<>"'`)]+/gi;
|
|
18
|
+
var EXT_BY_MIME = {
|
|
19
|
+
"image/png": ".png",
|
|
20
|
+
"image/jpeg": ".jpg",
|
|
21
|
+
"image/jpg": ".jpg",
|
|
22
|
+
"image/gif": ".gif",
|
|
23
|
+
"image/webp": ".webp",
|
|
24
|
+
"image/svg+xml": ".svg",
|
|
25
|
+
"application/pdf": ".pdf",
|
|
26
|
+
"text/plain": ".txt",
|
|
27
|
+
"text/markdown": ".md",
|
|
28
|
+
"application/json": ".json",
|
|
29
|
+
"application/zip": ".zip"
|
|
30
|
+
};
|
|
31
|
+
function issueAttachmentWorktreePath(repoPath) {
|
|
32
|
+
const raw = (repoPath ?? "").trim();
|
|
33
|
+
return raw || process.cwd();
|
|
34
|
+
}
|
|
35
|
+
function ensureIssueAttachmentsDir(worktreePath) {
|
|
36
|
+
const dir = join(worktreePath, ATTACHMENTS_DIR);
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
const gi = join(dir, ".gitignore");
|
|
39
|
+
if (!existsSync(gi)) {
|
|
40
|
+
writeFileSync(gi, attachmentsGitignoreBody(), "utf8");
|
|
41
|
+
}
|
|
42
|
+
return dir;
|
|
43
|
+
}
|
|
44
|
+
function hostnameOf(url) {
|
|
45
|
+
try {
|
|
46
|
+
return new URL(url).hostname.toLowerCase();
|
|
47
|
+
} catch {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function hostMatches(url, suffixes) {
|
|
52
|
+
const host = hostnameOf(url);
|
|
53
|
+
if (!host) return false;
|
|
54
|
+
return suffixes.some((suffix) => host === suffix || host.endsWith(`.${suffix}`));
|
|
55
|
+
}
|
|
56
|
+
function looksLikeHttpUrl(value) {
|
|
57
|
+
return /^https?:\/\//i.test(value.trim());
|
|
58
|
+
}
|
|
59
|
+
function looksLikeFileUrl(url) {
|
|
60
|
+
try {
|
|
61
|
+
const path = new URL(url).pathname;
|
|
62
|
+
return /\.[a-z0-9]{2,8}$/i.test(path);
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function filenameFromUrl(url, fallback = "attachment") {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = new URL(url);
|
|
70
|
+
const last = decodeURIComponent(basename(parsed.pathname)).replace(/[/\\]/g, "_");
|
|
71
|
+
if (last && last !== "/" && last !== ".") return last;
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
function filenameFromContentDisposition(header) {
|
|
77
|
+
if (!header) return void 0;
|
|
78
|
+
const star = header.match(/filename\*\s*=\s*(?:UTF-8''|utf-8'')([^;]+)/i);
|
|
79
|
+
if (star?.[1]) {
|
|
80
|
+
try {
|
|
81
|
+
return decodeURIComponent(star[1].trim().replace(/^["']|["']$/g, ""));
|
|
82
|
+
} catch {
|
|
83
|
+
return star[1].trim().replace(/^["']|["']$/g, "");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const plain = header.match(/filename\s*=\s*("(?:\\.|[^"])*"|[^;]+)/i);
|
|
87
|
+
if (!plain?.[1]) return void 0;
|
|
88
|
+
return plain[1].trim().replace(/^["']|["']$/g, "").replace(/\\"/g, '"');
|
|
89
|
+
}
|
|
90
|
+
function uniqueAttachmentFileName(dir, originalName) {
|
|
91
|
+
const safe = originalName.replace(/[/\\]/g, "_").replace(/^\.+/, "") || "file";
|
|
92
|
+
if (!existsSync(join(dir, safe))) return safe;
|
|
93
|
+
const ext = extname(safe);
|
|
94
|
+
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
95
|
+
for (let i = 1; i < 1e4; i++) {
|
|
96
|
+
const candidate = `${stem}-${i}${ext}`;
|
|
97
|
+
if (!existsSync(join(dir, candidate))) return candidate;
|
|
98
|
+
}
|
|
99
|
+
return `${stem}-${randomUUID()}${ext}`;
|
|
100
|
+
}
|
|
101
|
+
function extractHttpUrls(markdown) {
|
|
102
|
+
const text = markdown ?? "";
|
|
103
|
+
if (!text.trim()) return [];
|
|
104
|
+
const found = [];
|
|
105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
106
|
+
const push = (raw) => {
|
|
107
|
+
const url = raw.replace(/[).,;]+$/, "");
|
|
108
|
+
if (!looksLikeHttpUrl(url) || seen.has(url)) return;
|
|
109
|
+
seen.add(url);
|
|
110
|
+
found.push(url);
|
|
111
|
+
};
|
|
112
|
+
for (const re of [MARKDOWN_IMAGE_RE, MARKDOWN_LINK_RE, HTML_SRC_RE, BARE_URL_RE]) {
|
|
113
|
+
re.lastIndex = 0;
|
|
114
|
+
let match;
|
|
115
|
+
while (match = re.exec(text)) {
|
|
116
|
+
if (match[1]) push(match[1]);
|
|
117
|
+
else if (re === BARE_URL_RE) push(match[0]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return found;
|
|
121
|
+
}
|
|
122
|
+
function mergeIssueAttachments(items) {
|
|
123
|
+
const out = [];
|
|
124
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125
|
+
for (const item of items) {
|
|
126
|
+
if (!item) continue;
|
|
127
|
+
const url = item.url.trim();
|
|
128
|
+
const id = item.id.trim() || url;
|
|
129
|
+
if (!id && !url) continue;
|
|
130
|
+
const key = url || id;
|
|
131
|
+
if (seen.has(key) || seen.has(id)) continue;
|
|
132
|
+
seen.add(key);
|
|
133
|
+
if (id !== key) seen.add(id);
|
|
134
|
+
out.push({
|
|
135
|
+
id: id || url,
|
|
136
|
+
name: item.name.trim() || filenameFromUrl(url),
|
|
137
|
+
url,
|
|
138
|
+
...item.contentType ? { contentType: item.contentType } : {},
|
|
139
|
+
...item.sourceType ? { sourceType: item.sourceType } : {}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function extensionForContentType(contentType, name) {
|
|
145
|
+
if (extname(name)) return name;
|
|
146
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
147
|
+
const ext = EXT_BY_MIME[mime];
|
|
148
|
+
return ext ? `${name}${ext}` : name;
|
|
149
|
+
}
|
|
150
|
+
function assertNotHtml(contentType, url) {
|
|
151
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
152
|
+
if (mime === "text/html" || mime === "application/xhtml+xml") {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Attachment at ${url} returned HTML, not a file. Use the listed file URL (uploads / user-attachments), not a ticket or PR page.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function downloadAuthenticatedFile(opts) {
|
|
159
|
+
const url = opts.url.trim();
|
|
160
|
+
if (!looksLikeHttpUrl(url)) {
|
|
161
|
+
throw new Error(`Attachment URL must be http(s) (got ${url || "empty"})`);
|
|
162
|
+
}
|
|
163
|
+
const res = await httpFetch(url, {
|
|
164
|
+
method: "GET",
|
|
165
|
+
redirect: "follow",
|
|
166
|
+
headers: {
|
|
167
|
+
Accept: "*/*",
|
|
168
|
+
...opts.headers ?? {}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
const detail = await res.text().catch(() => "");
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Attachment download failed ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""} (${url})`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const contentType = res.headers.get("content-type") ?? void 0;
|
|
178
|
+
assertNotHtml(contentType, url);
|
|
179
|
+
const lengthHeader = res.headers.get("content-length");
|
|
180
|
+
if (lengthHeader) {
|
|
181
|
+
const length = Number(lengthHeader);
|
|
182
|
+
if (Number.isFinite(length) && length > ISSUE_ATTACHMENT_MAX_BYTES) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Attachment is ${length} bytes; max is ${ISSUE_ATTACHMENT_MAX_BYTES} (${url})`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
189
|
+
if (buf.length > ISSUE_ATTACHMENT_MAX_BYTES) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Attachment is ${buf.length} bytes; max is ${ISSUE_ATTACHMENT_MAX_BYTES} (${url})`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const dir = ensureIssueAttachmentsDir(opts.worktreePath);
|
|
195
|
+
const hinted = opts.name?.trim() || filenameFromContentDisposition(res.headers.get("content-disposition")) || filenameFromUrl(url);
|
|
196
|
+
const name = uniqueAttachmentFileName(dir, extensionForContentType(contentType, hinted));
|
|
197
|
+
writeFileSync(join(dir, name), buf);
|
|
198
|
+
return {
|
|
199
|
+
name,
|
|
200
|
+
path: `${ATTACHMENTS_DIR}/${name}`,
|
|
201
|
+
bytes: buf.length,
|
|
202
|
+
...contentType ? { contentType } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
issueAttachmentWorktreePath,
|
|
208
|
+
hostMatches,
|
|
209
|
+
looksLikeHttpUrl,
|
|
210
|
+
looksLikeFileUrl,
|
|
211
|
+
extractHttpUrls,
|
|
212
|
+
mergeIssueAttachments,
|
|
213
|
+
downloadAuthenticatedFile
|
|
214
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ATTACHMENTS_DIR,
|
|
5
|
+
attachmentsGitignoreBody
|
|
6
|
+
} from "./chunk-N2WW62LF.js";
|
|
7
|
+
import {
|
|
8
|
+
httpFetch
|
|
9
|
+
} from "./chunk-2GPQZZLY.js";
|
|
10
|
+
|
|
11
|
+
// src/integrations/issue-attachments.ts
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
13
|
+
import { basename, extname, join } from "path";
|
|
14
|
+
import { randomUUID } from "crypto";
|
|
15
|
+
var ISSUE_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;
|
|
16
|
+
var MARKDOWN_IMAGE_RE = /!\[[^\]]*]\(\s*<?(https?:\/\/[^)\s>]+)>?/gi;
|
|
17
|
+
var MARKDOWN_LINK_RE = /(?<!!)\[[^\]]*]\(\s*<?(https?:\/\/[^)\s>]+)>?/gi;
|
|
18
|
+
var HTML_SRC_RE = /\b(?:src|href)\s*=\s*["'](https?:\/\/[^"']+)["']/gi;
|
|
19
|
+
var BARE_URL_RE = /https?:\/\/[^\s<>"'`)]+/gi;
|
|
20
|
+
var EXT_BY_MIME = {
|
|
21
|
+
"image/png": ".png",
|
|
22
|
+
"image/jpeg": ".jpg",
|
|
23
|
+
"image/jpg": ".jpg",
|
|
24
|
+
"image/gif": ".gif",
|
|
25
|
+
"image/webp": ".webp",
|
|
26
|
+
"image/svg+xml": ".svg",
|
|
27
|
+
"application/pdf": ".pdf",
|
|
28
|
+
"text/plain": ".txt",
|
|
29
|
+
"text/markdown": ".md",
|
|
30
|
+
"application/json": ".json",
|
|
31
|
+
"application/zip": ".zip"
|
|
32
|
+
};
|
|
33
|
+
function issueAttachmentWorktreePath(repoPath) {
|
|
34
|
+
const raw = (repoPath ?? "").trim();
|
|
35
|
+
return raw || process.cwd();
|
|
36
|
+
}
|
|
37
|
+
function ensureIssueAttachmentsDir(worktreePath) {
|
|
38
|
+
const dir = join(worktreePath, ATTACHMENTS_DIR);
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
const gi = join(dir, ".gitignore");
|
|
41
|
+
if (!existsSync(gi)) {
|
|
42
|
+
writeFileSync(gi, attachmentsGitignoreBody(), "utf8");
|
|
43
|
+
}
|
|
44
|
+
return dir;
|
|
45
|
+
}
|
|
46
|
+
function hostnameOf(url) {
|
|
47
|
+
try {
|
|
48
|
+
return new URL(url).hostname.toLowerCase();
|
|
49
|
+
} catch {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function hostMatches(url, suffixes) {
|
|
54
|
+
const host = hostnameOf(url);
|
|
55
|
+
if (!host) return false;
|
|
56
|
+
return suffixes.some((suffix) => host === suffix || host.endsWith(`.${suffix}`));
|
|
57
|
+
}
|
|
58
|
+
function looksLikeHttpUrl(value) {
|
|
59
|
+
return /^https?:\/\//i.test(value.trim());
|
|
60
|
+
}
|
|
61
|
+
function looksLikeFileUrl(url) {
|
|
62
|
+
try {
|
|
63
|
+
const path = new URL(url).pathname;
|
|
64
|
+
return /\.[a-z0-9]{2,8}$/i.test(path);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function filenameFromUrl(url, fallback = "attachment") {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = new URL(url);
|
|
72
|
+
const last = decodeURIComponent(basename(parsed.pathname)).replace(/[/\\]/g, "_");
|
|
73
|
+
if (last && last !== "/" && last !== ".") return last;
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return fallback;
|
|
77
|
+
}
|
|
78
|
+
function filenameFromContentDisposition(header) {
|
|
79
|
+
if (!header) return void 0;
|
|
80
|
+
const star = header.match(/filename\*\s*=\s*(?:UTF-8''|utf-8'')([^;]+)/i);
|
|
81
|
+
if (star?.[1]) {
|
|
82
|
+
try {
|
|
83
|
+
return decodeURIComponent(star[1].trim().replace(/^["']|["']$/g, ""));
|
|
84
|
+
} catch {
|
|
85
|
+
return star[1].trim().replace(/^["']|["']$/g, "");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const plain = header.match(/filename\s*=\s*("(?:\\.|[^"])*"|[^;]+)/i);
|
|
89
|
+
if (!plain?.[1]) return void 0;
|
|
90
|
+
return plain[1].trim().replace(/^["']|["']$/g, "").replace(/\\"/g, '"');
|
|
91
|
+
}
|
|
92
|
+
function uniqueAttachmentFileName(dir, originalName) {
|
|
93
|
+
const safe = originalName.replace(/[/\\]/g, "_").replace(/^\.+/, "") || "file";
|
|
94
|
+
if (!existsSync(join(dir, safe))) return safe;
|
|
95
|
+
const ext = extname(safe);
|
|
96
|
+
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
97
|
+
for (let i = 1; i < 1e4; i++) {
|
|
98
|
+
const candidate = `${stem}-${i}${ext}`;
|
|
99
|
+
if (!existsSync(join(dir, candidate))) return candidate;
|
|
100
|
+
}
|
|
101
|
+
return `${stem}-${randomUUID()}${ext}`;
|
|
102
|
+
}
|
|
103
|
+
function extractHttpUrls(markdown) {
|
|
104
|
+
const text = markdown ?? "";
|
|
105
|
+
if (!text.trim()) return [];
|
|
106
|
+
const found = [];
|
|
107
|
+
const seen = /* @__PURE__ */ new Set();
|
|
108
|
+
const push = (raw) => {
|
|
109
|
+
const url = raw.replace(/[).,;]+$/, "");
|
|
110
|
+
if (!looksLikeHttpUrl(url) || seen.has(url)) return;
|
|
111
|
+
seen.add(url);
|
|
112
|
+
found.push(url);
|
|
113
|
+
};
|
|
114
|
+
for (const re of [MARKDOWN_IMAGE_RE, MARKDOWN_LINK_RE, HTML_SRC_RE, BARE_URL_RE]) {
|
|
115
|
+
re.lastIndex = 0;
|
|
116
|
+
let match;
|
|
117
|
+
while (match = re.exec(text)) {
|
|
118
|
+
if (match[1]) push(match[1]);
|
|
119
|
+
else if (re === BARE_URL_RE) push(match[0]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
function mergeIssueAttachments(items) {
|
|
125
|
+
const out = [];
|
|
126
|
+
const seen = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (!item) continue;
|
|
129
|
+
const url = item.url.trim();
|
|
130
|
+
const id = item.id.trim() || url;
|
|
131
|
+
if (!id && !url) continue;
|
|
132
|
+
const key = url || id;
|
|
133
|
+
if (seen.has(key) || seen.has(id)) continue;
|
|
134
|
+
seen.add(key);
|
|
135
|
+
if (id !== key) seen.add(id);
|
|
136
|
+
out.push({
|
|
137
|
+
id: id || url,
|
|
138
|
+
name: item.name.trim() || filenameFromUrl(url),
|
|
139
|
+
url,
|
|
140
|
+
...item.contentType ? { contentType: item.contentType } : {},
|
|
141
|
+
...item.sourceType ? { sourceType: item.sourceType } : {}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
function extensionForContentType(contentType, name) {
|
|
147
|
+
if (extname(name)) return name;
|
|
148
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
149
|
+
const ext = EXT_BY_MIME[mime];
|
|
150
|
+
return ext ? `${name}${ext}` : name;
|
|
151
|
+
}
|
|
152
|
+
function assertNotHtml(contentType, url) {
|
|
153
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
154
|
+
if (mime === "text/html" || mime === "application/xhtml+xml") {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Attachment at ${url} returned HTML, not a file. Use the listed file URL (uploads / user-attachments), not a ticket or PR page.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function downloadAuthenticatedFile(opts) {
|
|
161
|
+
const url = opts.url.trim();
|
|
162
|
+
if (!looksLikeHttpUrl(url)) {
|
|
163
|
+
throw new Error(`Attachment URL must be http(s) (got ${url || "empty"})`);
|
|
164
|
+
}
|
|
165
|
+
const res = await httpFetch(url, {
|
|
166
|
+
method: "GET",
|
|
167
|
+
redirect: "follow",
|
|
168
|
+
headers: {
|
|
169
|
+
Accept: "*/*",
|
|
170
|
+
...opts.headers ?? {}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
const detail = await res.text().catch(() => "");
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Attachment download failed ${res.status}${detail ? `: ${detail.slice(0, 200)}` : ""} (${url})`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const contentType = res.headers.get("content-type") ?? void 0;
|
|
180
|
+
assertNotHtml(contentType, url);
|
|
181
|
+
const lengthHeader = res.headers.get("content-length");
|
|
182
|
+
if (lengthHeader) {
|
|
183
|
+
const length = Number(lengthHeader);
|
|
184
|
+
if (Number.isFinite(length) && length > ISSUE_ATTACHMENT_MAX_BYTES) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Attachment is ${length} bytes; max is ${ISSUE_ATTACHMENT_MAX_BYTES} (${url})`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
191
|
+
if (buf.length > ISSUE_ATTACHMENT_MAX_BYTES) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Attachment is ${buf.length} bytes; max is ${ISSUE_ATTACHMENT_MAX_BYTES} (${url})`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const dir = ensureIssueAttachmentsDir(opts.worktreePath);
|
|
197
|
+
const hinted = opts.name?.trim() || filenameFromContentDisposition(res.headers.get("content-disposition")) || filenameFromUrl(url);
|
|
198
|
+
const name = uniqueAttachmentFileName(dir, extensionForContentType(contentType, hinted));
|
|
199
|
+
writeFileSync(join(dir, name), buf);
|
|
200
|
+
return {
|
|
201
|
+
name,
|
|
202
|
+
path: `${ATTACHMENTS_DIR}/${name}`,
|
|
203
|
+
bytes: buf.length,
|
|
204
|
+
...contentType ? { contentType } : {}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
issueAttachmentWorktreePath,
|
|
210
|
+
hostMatches,
|
|
211
|
+
looksLikeHttpUrl,
|
|
212
|
+
looksLikeFileUrl,
|
|
213
|
+
extractHttpUrls,
|
|
214
|
+
mergeIssueAttachments,
|
|
215
|
+
downloadAuthenticatedFile
|
|
216
|
+
};
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveGithubRepoSlug,
|
|
6
6
|
resolveOriginRemote,
|
|
7
7
|
resolveRepoRoot
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KECHK3ZT.js";
|
|
9
9
|
import {
|
|
10
10
|
allocateTeamName,
|
|
11
11
|
createEmptyThread,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
teamSlugFromName,
|
|
15
15
|
updateThread,
|
|
16
16
|
writeThread
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ZSHS46V5.js";
|
|
18
18
|
import {
|
|
19
19
|
ATTACHMENTS_DIR,
|
|
20
20
|
attachmentsGitignoreBody
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
resolveThreadDefaults,
|
|
32
32
|
resolveViewerProfile,
|
|
33
33
|
resolveViewerProfileForRepo
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-QPSKQP6E.js";
|
|
35
35
|
import {
|
|
36
36
|
appDataDir,
|
|
37
37
|
globalAgentCwd,
|