claude-threads 0.50.0 → 0.51.0
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/CHANGELOG.md +10 -0
- package/README.md +4 -0
- package/dist/index.js +127 -23
- package/dist/mcp/permission-server.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.51.0] - 2026-01-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Image upload for bug reports** - Bug reports can now include screenshots uploaded to Catbox.moe. Use `!bug <description>` with an attached image or paste a screenshot (#153)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Duplicate task lists** - Fixed issue where multiple task lists would appear in threads due to race conditions (#152, #151)
|
|
17
|
+
- **Code block rendering** - Fixed issues with code blocks not rendering correctly, including improved handling of language tags and empty blocks (#154)
|
|
18
|
+
- **Website logo rendering** - Improved SVG logo rendering on the project website (#155)
|
|
19
|
+
|
|
10
20
|
## [0.50.0] - 2026-01-09
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
✴ ▀█▄ █ ✴
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://claude-threads.run"><strong>claude-threads.run</strong></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
9
13
|
[](https://www.npmjs.com/package/claude-threads)
|
|
10
14
|
[](https://www.npmjs.com/package/claude-threads)
|
|
11
15
|
[](https://github.com/anneschuth/claude-threads/actions/workflows/ci.yml)
|
package/dist/index.js
CHANGED
|
@@ -42763,9 +42763,11 @@ ${code}
|
|
|
42763
42763
|
`);
|
|
42764
42764
|
}
|
|
42765
42765
|
formatMarkdown(content) {
|
|
42766
|
-
|
|
42766
|
+
let processed = content.replace(/(?<=\n)```(?=\S)(?![a-zA-Z]*\n)/g, "```\n");
|
|
42767
|
+
processed = processed.replace(/\n{3,}/g, `
|
|
42767
42768
|
|
|
42768
42769
|
`);
|
|
42770
|
+
return processed;
|
|
42769
42771
|
}
|
|
42770
42772
|
}
|
|
42771
42773
|
|
|
@@ -43407,6 +43409,7 @@ function convertMarkdownToSlack(content) {
|
|
|
43407
43409
|
for (let i = 0;i < codeBlocks.length; i++) {
|
|
43408
43410
|
preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
|
|
43409
43411
|
}
|
|
43412
|
+
preserved = preserved.replace(/(?<=\n)```(?=\S)(?![a-zA-Z]*\n)/g, "```\n");
|
|
43410
43413
|
return preserved;
|
|
43411
43414
|
}
|
|
43412
43415
|
function convertMarkdownTablesToSlack(content) {
|
|
@@ -46403,14 +46406,15 @@ function formatToolUse(toolName, input, formatter, options = {}) {
|
|
|
46403
46406
|
for (const line of lines) {
|
|
46404
46407
|
if (lineCount >= maxLines)
|
|
46405
46408
|
break;
|
|
46409
|
+
const escapedLine = line.replace(/```/g, "` ``");
|
|
46406
46410
|
if (change.added) {
|
|
46407
|
-
diffLines2.push(`+ ${
|
|
46411
|
+
diffLines2.push(`+ ${escapedLine}`);
|
|
46408
46412
|
lineCount++;
|
|
46409
46413
|
} else if (change.removed) {
|
|
46410
|
-
diffLines2.push(`- ${
|
|
46414
|
+
diffLines2.push(`- ${escapedLine}`);
|
|
46411
46415
|
lineCount++;
|
|
46412
46416
|
} else {
|
|
46413
|
-
diffLines2.push(` ${
|
|
46417
|
+
diffLines2.push(` ${escapedLine}`);
|
|
46414
46418
|
lineCount++;
|
|
46415
46419
|
}
|
|
46416
46420
|
}
|
|
@@ -46441,7 +46445,7 @@ ${formatter.formatCodeBlock(diffLines2.join(`
|
|
|
46441
46445
|
const lineCount = lines.length;
|
|
46442
46446
|
if (detailed && content && lineCount > 0) {
|
|
46443
46447
|
const maxLines = 6;
|
|
46444
|
-
const previewLines = lines.slice(0, maxLines);
|
|
46448
|
+
const previewLines = lines.slice(0, maxLines).map((line) => line.replace(/```/g, "` ``"));
|
|
46445
46449
|
let preview = `\uD83D\uDCDD ${formatter.formatBold("Write")} ${formatter.formatCode(filePath)} ${formatter.formatItalic(`(${lineCount} lines)`)}
|
|
46446
46450
|
`;
|
|
46447
46451
|
if (lineCount > maxLines) {
|
|
@@ -50562,6 +50566,54 @@ function validateClaudeCli() {
|
|
|
50562
50566
|
}
|
|
50563
50567
|
|
|
50564
50568
|
// src/session/bug-report.ts
|
|
50569
|
+
var CATBOX_API_URL = "https://catbox.moe/user/api.php";
|
|
50570
|
+
async function uploadImageToCatbox(imageBuffer, filename) {
|
|
50571
|
+
const arrayBuffer = imageBuffer.buffer.slice(imageBuffer.byteOffset, imageBuffer.byteOffset + imageBuffer.byteLength);
|
|
50572
|
+
const formData = new FormData;
|
|
50573
|
+
formData.append("reqtype", "fileupload");
|
|
50574
|
+
formData.append("fileToUpload", new Blob([arrayBuffer]), filename);
|
|
50575
|
+
const response = await fetch(CATBOX_API_URL, {
|
|
50576
|
+
method: "POST",
|
|
50577
|
+
body: formData
|
|
50578
|
+
});
|
|
50579
|
+
if (!response.ok) {
|
|
50580
|
+
throw new Error(`Catbox upload failed: ${response.status} ${response.statusText}`);
|
|
50581
|
+
}
|
|
50582
|
+
const responseText = await response.text();
|
|
50583
|
+
if (responseText.startsWith("https://")) {
|
|
50584
|
+
return responseText.trim();
|
|
50585
|
+
}
|
|
50586
|
+
throw new Error(`Catbox upload failed: ${responseText}`);
|
|
50587
|
+
}
|
|
50588
|
+
async function uploadImages(files, downloadFile) {
|
|
50589
|
+
const results = [];
|
|
50590
|
+
for (const file of files) {
|
|
50591
|
+
if (!file.mimeType.startsWith("image/")) {
|
|
50592
|
+
results.push({
|
|
50593
|
+
success: false,
|
|
50594
|
+
error: "Not an image file",
|
|
50595
|
+
originalFile: file
|
|
50596
|
+
});
|
|
50597
|
+
continue;
|
|
50598
|
+
}
|
|
50599
|
+
try {
|
|
50600
|
+
const buffer = await downloadFile(file.id);
|
|
50601
|
+
const url = await uploadImageToCatbox(buffer, file.name);
|
|
50602
|
+
results.push({
|
|
50603
|
+
success: true,
|
|
50604
|
+
url,
|
|
50605
|
+
originalFile: file
|
|
50606
|
+
});
|
|
50607
|
+
} catch (err) {
|
|
50608
|
+
results.push({
|
|
50609
|
+
success: false,
|
|
50610
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50611
|
+
originalFile: file
|
|
50612
|
+
});
|
|
50613
|
+
}
|
|
50614
|
+
}
|
|
50615
|
+
return results;
|
|
50616
|
+
}
|
|
50565
50617
|
var MAX_RECENT_EVENTS = 10;
|
|
50566
50618
|
var GITHUB_REPO = "anneschuth/claude-threads";
|
|
50567
50619
|
function trackEvent(session, type, summary) {
|
|
@@ -50647,11 +50699,19 @@ function formatRecentEvents(events) {
|
|
|
50647
50699
|
}).join(`
|
|
50648
50700
|
`);
|
|
50649
50701
|
}
|
|
50650
|
-
function formatIssueBody(context, userDescription) {
|
|
50702
|
+
function formatIssueBody(context, userDescription, imageUrls = []) {
|
|
50651
50703
|
const sections = [];
|
|
50652
50704
|
sections.push(`## Description
|
|
50653
50705
|
|
|
50654
50706
|
${sanitizeText(userDescription)}`);
|
|
50707
|
+
if (imageUrls.length > 0) {
|
|
50708
|
+
const imageSection = imageUrls.map((url, i) => ``).join(`
|
|
50709
|
+
|
|
50710
|
+
`);
|
|
50711
|
+
sections.push(`## Screenshots
|
|
50712
|
+
|
|
50713
|
+
${imageSection}`);
|
|
50714
|
+
}
|
|
50655
50715
|
sections.push(`## Environment
|
|
50656
50716
|
|
|
50657
50717
|
| Property | Value |
|
|
@@ -50729,7 +50789,7 @@ function checkGitHubCli() {
|
|
|
50729
50789
|
}
|
|
50730
50790
|
return { installed: true, authenticated: true };
|
|
50731
50791
|
}
|
|
50732
|
-
async function createGitHubIssue(title, body,
|
|
50792
|
+
async function createGitHubIssue(title, body, workingDir) {
|
|
50733
50793
|
const ghStatus = checkGitHubCli();
|
|
50734
50794
|
if (!ghStatus.installed || !ghStatus.authenticated) {
|
|
50735
50795
|
throw new Error(ghStatus.error);
|
|
@@ -50744,15 +50804,14 @@ async function createGitHubIssue(title, body, _attachments, workingDir) {
|
|
|
50744
50804
|
timeout: 30000,
|
|
50745
50805
|
stdio: ["pipe", "pipe", "pipe"]
|
|
50746
50806
|
});
|
|
50747
|
-
|
|
50748
|
-
return issueUrl;
|
|
50807
|
+
return result.trim();
|
|
50749
50808
|
} finally {
|
|
50750
50809
|
try {
|
|
50751
50810
|
unlinkSync2(bodyFile);
|
|
50752
50811
|
} catch {}
|
|
50753
50812
|
}
|
|
50754
50813
|
}
|
|
50755
|
-
function formatBugPreview(title, description, context,
|
|
50814
|
+
function formatBugPreview(title, description, context, imageUrls, imageErrors, formatter) {
|
|
50756
50815
|
const lines = [];
|
|
50757
50816
|
lines.push(`${formatter.formatBold("Bug Report Preview")}`);
|
|
50758
50817
|
lines.push("");
|
|
@@ -50779,10 +50838,15 @@ function formatBugPreview(title, description, context, attachments, formatter) {
|
|
|
50779
50838
|
lines.push(formatter.formatListItem(`${event.type}: ${event.summary.substring(0, 40)}...`));
|
|
50780
50839
|
}
|
|
50781
50840
|
}
|
|
50782
|
-
if (
|
|
50841
|
+
if (imageUrls.length > 0 || imageErrors.length > 0) {
|
|
50783
50842
|
lines.push("");
|
|
50784
|
-
lines.push(formatter.formatBold("
|
|
50785
|
-
|
|
50843
|
+
lines.push(formatter.formatBold("Screenshots:"));
|
|
50844
|
+
if (imageUrls.length > 0) {
|
|
50845
|
+
lines.push(formatter.formatListItem(`\u2705 ${imageUrls.length} image(s) uploaded successfully`));
|
|
50846
|
+
}
|
|
50847
|
+
if (imageErrors.length > 0) {
|
|
50848
|
+
lines.push(formatter.formatListItem(`\u26A0\uFE0F ${imageErrors.length} image(s) failed: ${imageErrors[0]}`));
|
|
50849
|
+
}
|
|
50786
50850
|
}
|
|
50787
50851
|
lines.push("");
|
|
50788
50852
|
lines.push(`React ${formatter.formatCode("\uD83D\uDC4D")} to create GitHub issue or ${formatter.formatCode("\uD83D\uDC4E")} to cancel`);
|
|
@@ -51465,12 +51529,13 @@ async function deferUpdate(session, username, updateManager) {
|
|
|
51465
51529
|
await postSuccess(session, `\u23F8\uFE0F ${formatter.formatBold("Update deferred")} for 1 hour
|
|
51466
51530
|
` + formatter.formatItalic("Use !update now to apply earlier"));
|
|
51467
51531
|
}
|
|
51468
|
-
async function reportBug(session, description, username, ctx, errorContext,
|
|
51532
|
+
async function reportBug(session, description, username, ctx, errorContext, attachedFiles) {
|
|
51469
51533
|
const formatter = session.platform.getFormatter();
|
|
51470
51534
|
if (!description && !errorContext) {
|
|
51471
51535
|
await postInfo(session, `Usage: ${formatter.formatCode("!bug <description>")}
|
|
51472
51536
|
` + `Example: ${formatter.formatCode("!bug Session crashed when uploading large image")}
|
|
51473
51537
|
|
|
51538
|
+
` + `You can also attach screenshots to the !bug message.
|
|
51474
51539
|
` + `Or react with \uD83D\uDC1B on any error message to report it.`);
|
|
51475
51540
|
return;
|
|
51476
51541
|
}
|
|
@@ -51481,9 +51546,18 @@ async function reportBug(session, description, username, ctx, errorContext, _att
|
|
|
51481
51546
|
}
|
|
51482
51547
|
const bugDescription = description || (errorContext ? `Error: ${errorContext.message.substring(0, 200)}` : "Unknown error");
|
|
51483
51548
|
const context = await collectBugReportContext(session, errorContext);
|
|
51549
|
+
let imageUrls = [];
|
|
51550
|
+
let imageErrors = [];
|
|
51551
|
+
const downloadFile = session.platform.downloadFile;
|
|
51552
|
+
if (attachedFiles && attachedFiles.length > 0 && downloadFile) {
|
|
51553
|
+
await postInfo(session, `\uD83D\uDCE4 Uploading ${attachedFiles.length} image(s)...`);
|
|
51554
|
+
const uploadResults = await uploadImages(attachedFiles, (fileId) => downloadFile(fileId));
|
|
51555
|
+
imageUrls = uploadResults.filter((r) => r.success && typeof r.url === "string").map((r) => r.url);
|
|
51556
|
+
imageErrors = uploadResults.filter((r) => !r.success).map((r) => `${r.originalFile.name}: ${r.error}`);
|
|
51557
|
+
}
|
|
51484
51558
|
const title = generateIssueTitle(bugDescription);
|
|
51485
|
-
const body = formatIssueBody(context, bugDescription);
|
|
51486
|
-
const preview = formatBugPreview(title, bugDescription, context,
|
|
51559
|
+
const body = formatIssueBody(context, bugDescription, imageUrls);
|
|
51560
|
+
const preview = formatBugPreview(title, bugDescription, context, imageUrls, imageErrors, formatter);
|
|
51487
51561
|
const previewMessage = `\uD83D\uDC1B ${preview}`;
|
|
51488
51562
|
const post = await session.platform.createInteractivePost(previewMessage, [APPROVAL_EMOJIS[0], DENIAL_EMOJIS[0]], session.threadId);
|
|
51489
51563
|
session.pendingBugReport = {
|
|
@@ -51491,7 +51565,8 @@ async function reportBug(session, description, username, ctx, errorContext, _att
|
|
|
51491
51565
|
title,
|
|
51492
51566
|
body,
|
|
51493
51567
|
userDescription: bugDescription,
|
|
51494
|
-
|
|
51568
|
+
imageUrls,
|
|
51569
|
+
imageErrors,
|
|
51495
51570
|
errorContext
|
|
51496
51571
|
};
|
|
51497
51572
|
ctx.ops.registerPost(post.id, session.threadId);
|
|
@@ -51505,7 +51580,7 @@ async function handleBugReportApproval(session, isApproved, username) {
|
|
|
51505
51580
|
const formatter = session.platform.getFormatter();
|
|
51506
51581
|
if (isApproved) {
|
|
51507
51582
|
try {
|
|
51508
|
-
const issueUrl = await createGitHubIssue(pending.title, pending.body,
|
|
51583
|
+
const issueUrl = await createGitHubIssue(pending.title, pending.body, session.workingDir);
|
|
51509
51584
|
await withErrorHandling(() => session.platform.updatePost(pending.postId, `\u2705 ${formatter.formatBold("Bug report submitted")}: ${issueUrl}`), { action: "Update bug report post", session });
|
|
51510
51585
|
sessionLog2(session).info(`\uD83D\uDC1B Bug report created by @${username}: ${issueUrl}`);
|
|
51511
51586
|
} catch (err) {
|
|
@@ -52300,6 +52375,28 @@ async function handleExitPlanMode(session, toolUseId, ctx) {
|
|
|
52300
52375
|
session.pendingApproval = { postId: post.id, type: "plan", toolUseId };
|
|
52301
52376
|
ctx.ops.stopTyping(session);
|
|
52302
52377
|
}
|
|
52378
|
+
async function cleanupOrphanedTaskPosts(session, currentTaskPostId) {
|
|
52379
|
+
try {
|
|
52380
|
+
const history = await session.platform.getThreadHistory(session.threadId, { limit: 50 });
|
|
52381
|
+
const taskPostPattern = /^(?:(?:---|___|\*\*\*|\u2014+)\s*\n)?\uD83D\uDCCB/;
|
|
52382
|
+
let cleanedCount = 0;
|
|
52383
|
+
for (const msg of history) {
|
|
52384
|
+
if (msg.id === currentTaskPostId)
|
|
52385
|
+
continue;
|
|
52386
|
+
if (!taskPostPattern.test(msg.message))
|
|
52387
|
+
continue;
|
|
52388
|
+
sessionLog4(session).info(`Cleaning up orphaned task post ${msg.id.substring(0, 8)}`);
|
|
52389
|
+
await session.platform.unpinPost(msg.id).catch(() => {});
|
|
52390
|
+
await session.platform.deletePost(msg.id).catch(() => {});
|
|
52391
|
+
cleanedCount++;
|
|
52392
|
+
}
|
|
52393
|
+
if (cleanedCount > 0) {
|
|
52394
|
+
sessionLog4(session).info(`Cleaned up ${cleanedCount} orphaned task post(s)`);
|
|
52395
|
+
}
|
|
52396
|
+
} catch (err) {
|
|
52397
|
+
sessionLog4(session).debug(`Task cleanup failed: ${err}`);
|
|
52398
|
+
}
|
|
52399
|
+
}
|
|
52303
52400
|
async function handleTodoWrite(session, input, ctx) {
|
|
52304
52401
|
const releaseLock = await acquireTaskListLock(session);
|
|
52305
52402
|
try {
|
|
@@ -52388,14 +52485,20 @@ async function handleTodoWriteWithLock(session, input, ctx) {
|
|
|
52388
52485
|
const displayMessage = session.tasksMinimized ? minimizedMessage : fullMessage;
|
|
52389
52486
|
const existingTasksPostId = session.tasksPostId;
|
|
52390
52487
|
if (existingTasksPostId) {
|
|
52391
|
-
await withErrorHandling(() => session.platform.updatePost(existingTasksPostId, displayMessage), { action: "Update tasks", session });
|
|
52392
|
-
|
|
52488
|
+
const updated = await withErrorHandling(() => session.platform.updatePost(existingTasksPostId, displayMessage), { action: "Update tasks", session });
|
|
52489
|
+
if (updated === undefined) {
|
|
52490
|
+
sessionLog4(session).warn(`Task post ${existingTasksPostId.substring(0, 8)} update failed, will create new one`);
|
|
52491
|
+
session.tasksPostId = null;
|
|
52492
|
+
}
|
|
52493
|
+
}
|
|
52494
|
+
if (!session.tasksPostId) {
|
|
52393
52495
|
const post = await withErrorHandling(() => session.platform.createInteractivePost(displayMessage, [TASK_TOGGLE_EMOJIS[0]], session.threadId), { action: "Create tasks post", session });
|
|
52394
52496
|
if (post) {
|
|
52395
52497
|
session.tasksPostId = post.id;
|
|
52396
52498
|
ctx.ops.registerPost(post.id, session.threadId);
|
|
52397
52499
|
updateLastMessage(session, post);
|
|
52398
52500
|
await session.platform.pinPost(post.id).catch(() => {});
|
|
52501
|
+
await cleanupOrphanedTaskPosts(session, post.id);
|
|
52399
52502
|
}
|
|
52400
52503
|
}
|
|
52401
52504
|
ctx.ops.updateStickyMessage().catch(() => {});
|
|
@@ -54857,11 +54960,11 @@ class SessionManager extends EventEmitter4 {
|
|
|
54857
54960
|
return;
|
|
54858
54961
|
await enableInteractivePermissions(session, username, this.getContext());
|
|
54859
54962
|
}
|
|
54860
|
-
async reportBug(threadId, description, username) {
|
|
54963
|
+
async reportBug(threadId, description, username, files) {
|
|
54861
54964
|
const session = this.findSessionByThreadId(threadId);
|
|
54862
54965
|
if (!session)
|
|
54863
54966
|
return;
|
|
54864
|
-
await reportBug(session, description, username, this.getContext());
|
|
54967
|
+
await reportBug(session, description, username, this.getContext(), undefined, files);
|
|
54865
54968
|
}
|
|
54866
54969
|
async showUpdateStatus(threadId, _username) {
|
|
54867
54970
|
const session = this.findSessionByThreadId(threadId);
|
|
@@ -65701,7 +65804,8 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
65701
65804
|
return;
|
|
65702
65805
|
case "bug":
|
|
65703
65806
|
if (isAllowed) {
|
|
65704
|
-
|
|
65807
|
+
const files3 = post.metadata?.files;
|
|
65808
|
+
await session.reportBug(threadRoot, parsed.args, username, files3);
|
|
65705
65809
|
}
|
|
65706
65810
|
return;
|
|
65707
65811
|
case "kill":
|
|
@@ -33831,9 +33831,11 @@ ${code}
|
|
|
33831
33831
|
`);
|
|
33832
33832
|
}
|
|
33833
33833
|
formatMarkdown(content) {
|
|
33834
|
-
|
|
33834
|
+
let processed = content.replace(/(?<=\n)```(?=\S)(?![a-zA-Z]*\n)/g, "```\n");
|
|
33835
|
+
processed = processed.replace(/\n{3,}/g, `
|
|
33835
33836
|
|
|
33836
33837
|
`);
|
|
33838
|
+
return processed;
|
|
33837
33839
|
}
|
|
33838
33840
|
}
|
|
33839
33841
|
|
|
@@ -34104,6 +34106,7 @@ function convertMarkdownToSlack(content) {
|
|
|
34104
34106
|
for (let i = 0;i < codeBlocks.length; i++) {
|
|
34105
34107
|
preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
|
|
34106
34108
|
}
|
|
34109
|
+
preserved = preserved.replace(/(?<=\n)```(?=\S)(?![a-zA-Z]*\n)/g, "```\n");
|
|
34107
34110
|
return preserved;
|
|
34108
34111
|
}
|
|
34109
34112
|
function convertMarkdownTablesToSlack(content) {
|