claude-threads 1.40.0 → 1.40.2
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 +20 -0
- package/dist/index.js +210 -108
- package/dist/mcp/mcp-server.js +3 -1
- package/docs/CONFIGURATION.md +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.40.2] - 2026-10-30
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `hono` 4.13.14, plus dev tooling (#655, #656).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Auto-update respects `engines`.** The updater now skips a release this host's Node or Bun cannot run, logs why, and `!update` says so instead of reporting "Up to date". npm only warns about `engines`, so an update used to install anyway. A host that is held back gets no further releases until its runtime is upgraded (#659).
|
|
15
|
+
|
|
16
|
+
## [1.40.1] - 2026-10-30
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`bugReports: { repo: owner/name }` files `!bug` reports in your own repository** (#577). `true` and `false` keep their meaning, and a malformed `repo` disables bug reports rather than falling back to the public default.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **Screenshots no longer go to catbox.moe** (#577). They are attached to the GitHub issue with `gh issue create --attach`, which needs `gh` 2.99 or newer and write access to the target repository. Without those the report is filed without them and says so. Attachments on a public repository stay online permanently, even if the issue is deleted, and the preview warns about that.
|
|
23
|
+
- **Nothing leaves before approval.** Screenshots used to be uploaded while the preview was being built, so a cancelled report had already sent them. The preview now also names the destination repository and whether it is public.
|
|
24
|
+
|
|
25
|
+
### Security
|
|
26
|
+
- **The `!bug` title can no longer run commands on the host.** The title went into a shell string with only double quotes escaped, so `$(...)` and backticks in a bug description were executed. `gh` is now called with an argument list.
|
|
27
|
+
|
|
8
28
|
## [1.40.0] - 2026-10-30
|
|
9
29
|
|
|
10
30
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -2345,6 +2345,10 @@ function resolveWatchesEnabled(value, fieldPath) {
|
|
|
2345
2345
|
function resolveAuditLogEnabled(value, fieldPath) {
|
|
2346
2346
|
return resolveBooleanFeature(value, fieldPath ?? "auditLog", { default: false, verb: "audit log stays off" });
|
|
2347
2347
|
}
|
|
2348
|
+
function isBugReportRepoConfig(value) {
|
|
2349
|
+
const repo = value?.repo;
|
|
2350
|
+
return typeof repo === "string" && GITHUB_REPO_PATTERN.test(repo);
|
|
2351
|
+
}
|
|
2348
2352
|
function resolveBugReportsEnabled(value, fieldPath) {
|
|
2349
2353
|
if (value === undefined)
|
|
2350
2354
|
return true;
|
|
@@ -2352,9 +2356,14 @@ function resolveBugReportsEnabled(value, fieldPath) {
|
|
|
2352
2356
|
return true;
|
|
2353
2357
|
if (value === false)
|
|
2354
2358
|
return false;
|
|
2355
|
-
|
|
2359
|
+
if (isBugReportRepoConfig(value))
|
|
2360
|
+
return true;
|
|
2361
|
+
console.warn(`Invalid ${fieldPath ?? "bugReports"} config: expected boolean or { repo: owner/name }, got ${JSON.stringify(value)} — ` + `bug reports are DISABLED (this flag fails closed: it controls data leaving your infrastructure)`);
|
|
2356
2362
|
return false;
|
|
2357
2363
|
}
|
|
2364
|
+
function resolveBugReportRepo(value) {
|
|
2365
|
+
return isBugReportRepoConfig(value) ? value.repo : DEFAULT_BUG_REPORT_REPO;
|
|
2366
|
+
}
|
|
2358
2367
|
function isRemoteMcpServer(server) {
|
|
2359
2368
|
return server.type === "http" || server.type === "sse";
|
|
2360
2369
|
}
|
|
@@ -2473,7 +2482,7 @@ function effectivePermissionMode(input) {
|
|
|
2473
2482
|
return "default";
|
|
2474
2483
|
return input.botWideMode;
|
|
2475
2484
|
}
|
|
2476
|
-
var OVERHEAD_VISIBILITY_VALUES, DEFAULT_OVERHEAD_VISIBILITY = "full", RECONNECT_POLICY_VALUES, DEFAULT_RECONNECT_POLICY = "retry", PRESENTATION_MODE_VALUES, PRESENTATION_MODE_EXPANSIONS, TURN_MARKER_VALUES, DEFAULT_TURN_MARKER, DEFAULT_TURN_MARKER_EMOJI = "checkered_flag", TURN_COMPLETE_EVENT_TYPE = "claude_threads_turn_complete", TURN_COMPLETE_PAYLOAD_VERSION = 1, TOOL_ACTIVITY_VALUES, TOOL_DETAILS_VALUES, DEFAULT_TOOL_ACTIVITY, DEFAULT_MEMORY_CONFIG, MEMORY_DISABLED, BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS, LIMITS_DEFAULTS, MODE_INFO;
|
|
2485
|
+
var OVERHEAD_VISIBILITY_VALUES, DEFAULT_OVERHEAD_VISIBILITY = "full", RECONNECT_POLICY_VALUES, DEFAULT_RECONNECT_POLICY = "retry", PRESENTATION_MODE_VALUES, PRESENTATION_MODE_EXPANSIONS, TURN_MARKER_VALUES, DEFAULT_TURN_MARKER, DEFAULT_TURN_MARKER_EMOJI = "checkered_flag", TURN_COMPLETE_EVENT_TYPE = "claude_threads_turn_complete", TURN_COMPLETE_PAYLOAD_VERSION = 1, TOOL_ACTIVITY_VALUES, TOOL_DETAILS_VALUES, DEFAULT_TOOL_ACTIVITY, DEFAULT_MEMORY_CONFIG, MEMORY_DISABLED, DEFAULT_BUG_REPORT_REPO = "anneschuth/claude-threads", GITHUB_REPO_PATTERN, BOT_MCP_SERVER_NAME = "claude-threads-mcp", STDIO_KEYS, REMOTE_KEYS, LIMITS_DEFAULTS, MODE_INFO;
|
|
2477
2486
|
var init_types = __esm(() => {
|
|
2478
2487
|
OVERHEAD_VISIBILITY_VALUES = ["full", "minimal", "hidden"];
|
|
2479
2488
|
RECONNECT_POLICY_VALUES = ["retry", "exit"];
|
|
@@ -2503,6 +2512,7 @@ var init_types = __esm(() => {
|
|
|
2503
2512
|
channelLayer: false,
|
|
2504
2513
|
distillation: false
|
|
2505
2514
|
};
|
|
2515
|
+
GITHUB_REPO_PATTERN = /^[A-Za-z0-9][\w.-]*\/[A-Za-z0-9][\w.-]*$/;
|
|
2506
2516
|
STDIO_KEYS = new Set(["type", "command", "args", "env"]);
|
|
2507
2517
|
REMOTE_KEYS = new Set(["type", "url", "headers"]);
|
|
2508
2518
|
LIMITS_DEFAULTS = {
|
|
@@ -57530,8 +57540,8 @@ function getUpdateInfo() {
|
|
|
57530
57540
|
init_emoji();
|
|
57531
57541
|
|
|
57532
57542
|
// src/operations/bug-report/handler.ts
|
|
57533
|
-
import { execSync as execSync2 } from "child_process";
|
|
57534
|
-
import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
57543
|
+
import { execSync as execSync2, execFileSync } from "child_process";
|
|
57544
|
+
import { writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, mkdtempSync as mkdtempSync2, rmSync } from "fs";
|
|
57535
57545
|
import { tmpdir as tmpdir4 } from "os";
|
|
57536
57546
|
import { join as join9 } from "path";
|
|
57537
57547
|
|
|
@@ -57791,58 +57801,67 @@ class Redactor {
|
|
|
57791
57801
|
}
|
|
57792
57802
|
|
|
57793
57803
|
// src/operations/bug-report/handler.ts
|
|
57804
|
+
var import_semver4 = __toESM(require_semver2(), 1);
|
|
57794
57805
|
init_version_check();
|
|
57795
57806
|
var piiRedactor = new Redactor({ aggressive: true });
|
|
57796
|
-
var
|
|
57797
|
-
|
|
57798
|
-
|
|
57799
|
-
|
|
57800
|
-
|
|
57801
|
-
|
|
57802
|
-
const response = await fetch(CATBOX_API_URL, {
|
|
57803
|
-
method: "POST",
|
|
57804
|
-
body: formData
|
|
57805
|
-
});
|
|
57806
|
-
if (!response.ok) {
|
|
57807
|
-
throw new Error(`Catbox upload failed: ${response.status} ${response.statusText}`);
|
|
57808
|
-
}
|
|
57809
|
-
const responseText = await response.text();
|
|
57810
|
-
if (responseText.startsWith("https://")) {
|
|
57811
|
-
return responseText.trim();
|
|
57812
|
-
}
|
|
57813
|
-
throw new Error(`Catbox upload failed: ${responseText}`);
|
|
57814
|
-
}
|
|
57815
|
-
async function uploadImages(files, downloadFile) {
|
|
57816
|
-
const results = [];
|
|
57807
|
+
var GH_MIN_ATTACH_VERSION = "2.99.0";
|
|
57808
|
+
var MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
57809
|
+
var ATTACH_PERMISSIONS = ["ADMIN", "MAINTAIN", "WRITE"];
|
|
57810
|
+
function selectScreenshots(files, target) {
|
|
57811
|
+
const screenshots = [];
|
|
57812
|
+
const skipped = [];
|
|
57817
57813
|
for (const file of files) {
|
|
57818
57814
|
if (!file.mimeType.startsWith("image/")) {
|
|
57819
|
-
|
|
57820
|
-
|
|
57821
|
-
|
|
57822
|
-
|
|
57823
|
-
});
|
|
57824
|
-
|
|
57815
|
+
skipped.push(`${file.name}: not an image`);
|
|
57816
|
+
} else if (file.size > MAX_SCREENSHOT_BYTES) {
|
|
57817
|
+
skipped.push(`${file.name}: larger than 10 MB`);
|
|
57818
|
+
} else if (!target.canAttach) {
|
|
57819
|
+
skipped.push(`${file.name}: ${target.attachBlocker}`);
|
|
57820
|
+
} else {
|
|
57821
|
+
screenshots.push(file);
|
|
57825
57822
|
}
|
|
57823
|
+
}
|
|
57824
|
+
return { screenshots, skipped };
|
|
57825
|
+
}
|
|
57826
|
+
async function saveScreenshots(files, downloadFile) {
|
|
57827
|
+
if (files.length === 0) {
|
|
57828
|
+
return { paths: [], failed: 0, cleanup: () => {} };
|
|
57829
|
+
}
|
|
57830
|
+
const dir = mkdtempSync2(join9(tmpdir4(), "bug-screenshots-"));
|
|
57831
|
+
const paths = [];
|
|
57832
|
+
let failed = 0;
|
|
57833
|
+
for (const [index, file] of files.entries()) {
|
|
57826
57834
|
try {
|
|
57835
|
+
if (!downloadFile)
|
|
57836
|
+
throw new Error("Platform cannot download files");
|
|
57827
57837
|
const buffer = await downloadFile(file.id);
|
|
57828
|
-
const
|
|
57829
|
-
|
|
57830
|
-
|
|
57831
|
-
|
|
57832
|
-
|
|
57833
|
-
|
|
57834
|
-
} catch (err) {
|
|
57835
|
-
results.push({
|
|
57836
|
-
success: false,
|
|
57837
|
-
error: err instanceof Error ? err.message : String(err),
|
|
57838
|
-
originalFile: file
|
|
57839
|
-
});
|
|
57838
|
+
const extension = (file.extension ?? file.mimeType.split("/")[1] ?? "").replace(/^\./, "").toLowerCase();
|
|
57839
|
+
const path = join9(dir, `screenshot-${index + 1}.${/^[a-z0-9]+$/.test(extension) ? extension : "png"}`);
|
|
57840
|
+
writeFileSync5(path, buffer, { mode: 384 });
|
|
57841
|
+
paths.push(path);
|
|
57842
|
+
} catch {
|
|
57843
|
+
failed++;
|
|
57840
57844
|
}
|
|
57841
57845
|
}
|
|
57842
|
-
return
|
|
57846
|
+
return {
|
|
57847
|
+
paths,
|
|
57848
|
+
failed,
|
|
57849
|
+
cleanup: () => {
|
|
57850
|
+
try {
|
|
57851
|
+
rmSync(dir, { recursive: true, force: true });
|
|
57852
|
+
} catch {}
|
|
57853
|
+
}
|
|
57854
|
+
};
|
|
57855
|
+
}
|
|
57856
|
+
function omittedScreenshotsNote(count) {
|
|
57857
|
+
return `${count} screenshot(s) were shared in the chat thread but are not attached to this issue.`;
|
|
57858
|
+
}
|
|
57859
|
+
function withOmittedScreenshots(body, count) {
|
|
57860
|
+
return count > 0 ? `${body}
|
|
57861
|
+
|
|
57862
|
+
${omittedScreenshotsNote(count)}` : body;
|
|
57843
57863
|
}
|
|
57844
57864
|
var MAX_RECENT_EVENTS = 10;
|
|
57845
|
-
var GITHUB_REPO = "anneschuth/claude-threads";
|
|
57846
57865
|
function trackEvent(session, type, summary) {
|
|
57847
57866
|
if (!session.recentEvents) {
|
|
57848
57867
|
session.recentEvents = [];
|
|
@@ -58033,18 +58052,15 @@ function formatLogEntries(entries) {
|
|
|
58033
58052
|
}).join(`
|
|
58034
58053
|
`);
|
|
58035
58054
|
}
|
|
58036
|
-
function formatIssueBody(context, userDescription,
|
|
58055
|
+
function formatIssueBody(context, userDescription, omittedScreenshots = 0) {
|
|
58037
58056
|
const sections = [];
|
|
58038
58057
|
sections.push(`## Description
|
|
58039
58058
|
|
|
58040
58059
|
${sanitizeText(userDescription)}`);
|
|
58041
|
-
if (
|
|
58042
|
-
const imageSection = imageUrls.map((url, i) => ``).join(`
|
|
58043
|
-
|
|
58044
|
-
`);
|
|
58060
|
+
if (omittedScreenshots > 0) {
|
|
58045
58061
|
sections.push(`## Screenshots
|
|
58046
58062
|
|
|
58047
|
-
${
|
|
58063
|
+
${omittedScreenshotsNote(omittedScreenshots)}`);
|
|
58048
58064
|
}
|
|
58049
58065
|
sections.push(`## Environment
|
|
58050
58066
|
|
|
@@ -58104,16 +58120,15 @@ _Reported via claude-threads bug report feature_`);
|
|
|
58104
58120
|
|
|
58105
58121
|
`);
|
|
58106
58122
|
}
|
|
58107
|
-
|
|
58108
|
-
|
|
58109
|
-
|
|
58110
|
-
|
|
58123
|
+
var runGh = (args, options) => execFileSync("gh", args, options);
|
|
58124
|
+
var GH_CHECK_OPTIONS = {
|
|
58125
|
+
encoding: "utf-8",
|
|
58126
|
+
timeout: 5000,
|
|
58127
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
58128
|
+
};
|
|
58129
|
+
function checkGitHubCli(exec = runGh) {
|
|
58111
58130
|
try {
|
|
58112
|
-
exec("
|
|
58113
|
-
encoding: "utf-8",
|
|
58114
|
-
timeout: 5000,
|
|
58115
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
58116
|
-
});
|
|
58131
|
+
exec(["--version"], GH_CHECK_OPTIONS);
|
|
58117
58132
|
} catch {
|
|
58118
58133
|
return {
|
|
58119
58134
|
installed: false,
|
|
@@ -58122,11 +58137,7 @@ function checkGitHubCli(exec = execSync2) {
|
|
|
58122
58137
|
};
|
|
58123
58138
|
}
|
|
58124
58139
|
try {
|
|
58125
|
-
exec("
|
|
58126
|
-
encoding: "utf-8",
|
|
58127
|
-
timeout: 5000,
|
|
58128
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
58129
|
-
});
|
|
58140
|
+
exec(["auth", "status"], GH_CHECK_OPTIONS);
|
|
58130
58141
|
} catch {
|
|
58131
58142
|
return {
|
|
58132
58143
|
installed: true,
|
|
@@ -58136,29 +58147,79 @@ function checkGitHubCli(exec = execSync2) {
|
|
|
58136
58147
|
}
|
|
58137
58148
|
return { installed: true, authenticated: true };
|
|
58138
58149
|
}
|
|
58139
|
-
|
|
58150
|
+
function inspectBugReportTarget(repo, exec = runGh) {
|
|
58151
|
+
let visibility;
|
|
58152
|
+
let viewerPermission;
|
|
58153
|
+
try {
|
|
58154
|
+
const info = JSON.parse(exec(["repo", "view", repo, "--json", "viewerPermission,visibility"], GH_CHECK_OPTIONS));
|
|
58155
|
+
visibility = info.visibility;
|
|
58156
|
+
viewerPermission = info.viewerPermission;
|
|
58157
|
+
} catch {
|
|
58158
|
+
return { repo, isPublic: true, canAttach: false, attachBlocker: `could not read ${repo}` };
|
|
58159
|
+
}
|
|
58160
|
+
const isPublic = visibility !== "PRIVATE" && visibility !== "INTERNAL";
|
|
58161
|
+
if (viewerPermission && !ATTACH_PERMISSIONS.includes(viewerPermission)) {
|
|
58162
|
+
return { repo, isPublic, canAttach: false, attachBlocker: `attaching needs write access to ${repo}` };
|
|
58163
|
+
}
|
|
58164
|
+
let ghVersion = null;
|
|
58165
|
+
try {
|
|
58166
|
+
ghVersion = import_semver4.coerce(exec(["--version"], GH_CHECK_OPTIONS));
|
|
58167
|
+
} catch {}
|
|
58168
|
+
if (ghVersion && import_semver4.lt(ghVersion, GH_MIN_ATTACH_VERSION)) {
|
|
58169
|
+
return { repo, isPublic, canAttach: false, attachBlocker: `attaching needs gh ${GH_MIN_ATTACH_VERSION} or newer` };
|
|
58170
|
+
}
|
|
58171
|
+
return { repo, isPublic, canAttach: true };
|
|
58172
|
+
}
|
|
58173
|
+
async function createGitHubIssue(title, body, workingDir, options) {
|
|
58174
|
+
const { repo, attachments = [], exec = runGh } = options;
|
|
58140
58175
|
const ghStatus = checkGitHubCli(exec);
|
|
58141
58176
|
if (!ghStatus.installed || !ghStatus.authenticated) {
|
|
58142
58177
|
throw new Error(ghStatus.error);
|
|
58143
58178
|
}
|
|
58144
58179
|
const bodyFile = join9(tmpdir4(), `bug-body-${Date.now()}.md`);
|
|
58145
|
-
|
|
58146
|
-
writeFileSync5(bodyFile,
|
|
58147
|
-
|
|
58148
|
-
|
|
58180
|
+
const create = (issueBody, paths) => {
|
|
58181
|
+
writeFileSync5(bodyFile, issueBody, "utf-8");
|
|
58182
|
+
return exec([
|
|
58183
|
+
"issue",
|
|
58184
|
+
"create",
|
|
58185
|
+
"--repo",
|
|
58186
|
+
repo,
|
|
58187
|
+
"--title",
|
|
58188
|
+
title,
|
|
58189
|
+
"--body-file",
|
|
58190
|
+
bodyFile,
|
|
58191
|
+
...paths.flatMap((path, index) => ["--attach", `${path}#Screenshot ${index + 1}`])
|
|
58192
|
+
], {
|
|
58149
58193
|
cwd: workingDir,
|
|
58150
58194
|
encoding: "utf-8",
|
|
58151
|
-
timeout: 30000,
|
|
58195
|
+
timeout: paths.length > 0 ? 120000 : 30000,
|
|
58152
58196
|
stdio: ["pipe", "pipe", "pipe"]
|
|
58153
|
-
});
|
|
58154
|
-
|
|
58197
|
+
}).trim();
|
|
58198
|
+
};
|
|
58199
|
+
try {
|
|
58200
|
+
try {
|
|
58201
|
+
return { url: create(body, attachments), attachmentsIncomplete: false };
|
|
58202
|
+
} catch (err) {
|
|
58203
|
+
if (attachments.length === 0)
|
|
58204
|
+
throw err;
|
|
58205
|
+
const failure = err;
|
|
58206
|
+
const url = String(failure.stdout ?? "").match(/https:\/\/\S+\/issues\/\d+/)?.[0];
|
|
58207
|
+
if (url)
|
|
58208
|
+
return { url, attachmentsIncomplete: true };
|
|
58209
|
+
if (failure.code === "ETIMEDOUT")
|
|
58210
|
+
throw err;
|
|
58211
|
+
return {
|
|
58212
|
+
url: create(withOmittedScreenshots(body, attachments.length), []),
|
|
58213
|
+
attachmentsIncomplete: true
|
|
58214
|
+
};
|
|
58215
|
+
}
|
|
58155
58216
|
} finally {
|
|
58156
58217
|
try {
|
|
58157
58218
|
unlinkSync3(bodyFile);
|
|
58158
58219
|
} catch {}
|
|
58159
58220
|
}
|
|
58160
58221
|
}
|
|
58161
|
-
function formatBugPreview(title, description, context,
|
|
58222
|
+
function formatBugPreview(title, description, context, target, screenshotCount, skippedScreenshots, formatter) {
|
|
58162
58223
|
const lines = [];
|
|
58163
58224
|
lines.push(`${formatter.formatBold("Bug Report Preview")}`);
|
|
58164
58225
|
lines.push("");
|
|
@@ -58185,14 +58246,20 @@ function formatBugPreview(title, description, context, imageUrls, imageErrors, f
|
|
|
58185
58246
|
lines.push(formatter.formatListItem(`${event.type}: ${event.summary.substring(0, 40)}...`));
|
|
58186
58247
|
}
|
|
58187
58248
|
}
|
|
58188
|
-
|
|
58249
|
+
lines.push("");
|
|
58250
|
+
lines.push(formatter.formatBold("Destination:"));
|
|
58251
|
+
lines.push(formatter.formatListItem(`Issue on ${formatter.formatCode(target.repo)}${target.isPublic ? " (public: anyone can read it)" : ""}`));
|
|
58252
|
+
if (screenshotCount > 0 || skippedScreenshots.length > 0) {
|
|
58189
58253
|
lines.push("");
|
|
58190
58254
|
lines.push(formatter.formatBold("Screenshots:"));
|
|
58191
|
-
if (
|
|
58192
|
-
lines.push(formatter.formatListItem(
|
|
58255
|
+
if (screenshotCount > 0) {
|
|
58256
|
+
lines.push(formatter.formatListItem(`\uD83D\uDCCE ${screenshotCount} image(s) will be attached when you approve`));
|
|
58257
|
+
if (target.isPublic) {
|
|
58258
|
+
lines.push(formatter.formatListItem("⚠️ Attachments on a public repository stay online permanently, even if the issue is deleted. Check them for personal data first."));
|
|
58259
|
+
}
|
|
58193
58260
|
}
|
|
58194
|
-
if (
|
|
58195
|
-
lines.push(formatter.formatListItem(`⚠️ ${
|
|
58261
|
+
if (skippedScreenshots.length > 0) {
|
|
58262
|
+
lines.push(formatter.formatListItem(`⚠️ ${skippedScreenshots.length} image(s) not attached (${skippedScreenshots[0]}). Add them to the issue by hand if needed.`));
|
|
58196
58263
|
}
|
|
58197
58264
|
}
|
|
58198
58265
|
lines.push("");
|
|
@@ -67363,6 +67430,11 @@ async function showUpdateStatus(session, updateManager, ctx) {
|
|
|
67363
67430
|
}
|
|
67364
67431
|
const updateInfo = await updateManager.checkNow();
|
|
67365
67432
|
if (!updateInfo || !updateInfo.available) {
|
|
67433
|
+
const blocked = updateManager.getBlockedUpdate();
|
|
67434
|
+
if (blocked) {
|
|
67435
|
+
await post(session, "warning", `${formatter.formatBold(`v${blocked.latestVersion} is available but not installable`)}: ${blocked.reason}`);
|
|
67436
|
+
return;
|
|
67437
|
+
}
|
|
67366
67438
|
await post(session, "success", `${formatter.formatBold("Up to date")} - no updates available`);
|
|
67367
67439
|
return;
|
|
67368
67440
|
}
|
|
@@ -67396,7 +67468,8 @@ async function forceUpdateNow(session, username, updateManager) {
|
|
|
67396
67468
|
}
|
|
67397
67469
|
const updateInfo = await updateManager.checkNow();
|
|
67398
67470
|
if (!updateInfo || !updateInfo.available) {
|
|
67399
|
-
|
|
67471
|
+
const blocked = updateManager.getBlockedUpdate();
|
|
67472
|
+
await post(session, "info", blocked ? `Not installing v${blocked.latestVersion}: ${blocked.reason}` : `No update available to install`);
|
|
67400
67473
|
return;
|
|
67401
67474
|
}
|
|
67402
67475
|
await post(session, "info", `\uD83D\uDD04 ${formatter.formatBold("Forcing update")} to v${updateInfo.latestVersion} - restarting shortly...
|
|
@@ -67420,7 +67493,7 @@ async function deferUpdate(session, username, updateManager) {
|
|
|
67420
67493
|
await post(session, "success", `⏸️ ${formatter.formatBold("Update deferred")} for 1 hour
|
|
67421
67494
|
` + formatter.formatItalic("Use !update now to apply earlier"));
|
|
67422
67495
|
}
|
|
67423
|
-
async function reportBug(session, description, username, ctx, errorContext, attachedFiles) {
|
|
67496
|
+
async function reportBug(session, description, username, ctx, errorContext, attachedFiles, exec) {
|
|
67424
67497
|
const formatter = session.platform.getFormatter();
|
|
67425
67498
|
if (!ctx.config.bugReportsEnabled) {
|
|
67426
67499
|
await post(session, "info", `${formatter.formatBold("Bug reporting is disabled")} by this server's configuration ` + `(${formatter.formatCode("bugReports: false")}).
|
|
@@ -67435,25 +67508,18 @@ async function reportBug(session, description, username, ctx, errorContext, atta
|
|
|
67435
67508
|
` + `Or react with \uD83D\uDC1B on any error message to report it.`);
|
|
67436
67509
|
return;
|
|
67437
67510
|
}
|
|
67438
|
-
const ghStatus = checkGitHubCli();
|
|
67511
|
+
const ghStatus = checkGitHubCli(exec);
|
|
67439
67512
|
if (!ghStatus.installed || !ghStatus.authenticated) {
|
|
67440
67513
|
await postError(session, ghStatus.error || "GitHub CLI not configured");
|
|
67441
67514
|
return;
|
|
67442
67515
|
}
|
|
67443
67516
|
const bugDescription = description || (errorContext ? `Error: ${errorContext.message.substring(0, 200)}` : "Unknown error");
|
|
67444
67517
|
const context = await collectBugReportContext(session, errorContext);
|
|
67445
|
-
|
|
67446
|
-
|
|
67447
|
-
const downloadFile = session.platform.downloadFile?.bind(session.platform);
|
|
67448
|
-
if (attachedFiles && attachedFiles.length > 0 && downloadFile) {
|
|
67449
|
-
await post(session, "info", `\uD83D\uDCE4 Uploading ${attachedFiles.length} image(s)...`);
|
|
67450
|
-
const uploadResults = await uploadImages(attachedFiles, downloadFile);
|
|
67451
|
-
imageUrls = uploadResults.filter((r) => r.success && typeof r.url === "string").map((r) => r.url);
|
|
67452
|
-
imageErrors = uploadResults.filter((r) => !r.success).map((r) => `${r.originalFile.name}: ${r.error}`);
|
|
67453
|
-
}
|
|
67518
|
+
const target = inspectBugReportTarget(ctx.config.bugReportRepo, exec);
|
|
67519
|
+
const { screenshots, skipped } = selectScreenshots(attachedFiles ?? [], target);
|
|
67454
67520
|
const title = generateIssueTitle(bugDescription);
|
|
67455
|
-
const body = formatIssueBody(context, bugDescription,
|
|
67456
|
-
const preview = formatBugPreview(title, bugDescription, context,
|
|
67521
|
+
const body = formatIssueBody(context, bugDescription, skipped.length);
|
|
67522
|
+
const preview = formatBugPreview(title, bugDescription, context, target, screenshots.length, skipped, formatter);
|
|
67457
67523
|
const previewMessage = `\uD83D\uDC1B ${preview}`;
|
|
67458
67524
|
const bugReportPost = await postInteractiveAndRegister(session, previewMessage, [APPROVAL_EMOJIS[0], DENIAL_EMOJIS[0]], ctx.ops.registerPost);
|
|
67459
67525
|
session.messageManager?.setPendingBugReport({
|
|
@@ -67461,8 +67527,7 @@ async function reportBug(session, description, username, ctx, errorContext, atta
|
|
|
67461
67527
|
title,
|
|
67462
67528
|
body,
|
|
67463
67529
|
userDescription: bugDescription,
|
|
67464
|
-
|
|
67465
|
-
imageErrors,
|
|
67530
|
+
screenshots,
|
|
67466
67531
|
errorContext
|
|
67467
67532
|
});
|
|
67468
67533
|
sessionLog8(session).info(`\uD83D\uDC1B Bug report preview created by @${username}: ${title}`);
|
|
@@ -67476,14 +67541,19 @@ async function handleBugReportApproval(session, isApproved, username, ctx, exec)
|
|
|
67476
67541
|
}
|
|
67477
67542
|
const formatter = session.platform.getFormatter();
|
|
67478
67543
|
if (isApproved) {
|
|
67544
|
+
const saved = await saveScreenshots(pending.screenshots ?? [], session.platform.downloadFile?.bind(session.platform));
|
|
67479
67545
|
try {
|
|
67480
|
-
const
|
|
67481
|
-
|
|
67482
|
-
|
|
67546
|
+
const issue = await createGitHubIssue(pending.title, withOmittedScreenshots(pending.body, saved.failed), session.workingDir, { repo: ctx.config.bugReportRepo, attachments: saved.paths, exec });
|
|
67547
|
+
const incomplete = issue.attachmentsIncomplete || saved.failed > 0;
|
|
67548
|
+
await updatePostSuccess(session, pending.postId, `${formatter.formatBold("Bug report submitted")}: ${issue.url}` + (incomplete ? `
|
|
67549
|
+
⚠️ Not every screenshot could be attached. Add the missing ones to the issue by hand.` : ""));
|
|
67550
|
+
sessionLog8(session).info(`\uD83D\uDC1B Bug report created by @${username}: ${issue.url}`);
|
|
67483
67551
|
} catch (err) {
|
|
67484
67552
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
67485
67553
|
await updatePostError(session, pending.postId, `${formatter.formatBold("Failed to create bug report")}: ${errorMessage}`);
|
|
67486
67554
|
sessionLog8(session).error(`Failed to create bug report: ${errorMessage}`);
|
|
67555
|
+
} finally {
|
|
67556
|
+
saved.cleanup();
|
|
67487
67557
|
}
|
|
67488
67558
|
} else {
|
|
67489
67559
|
await updatePostCancelled(session, pending.postId, `${formatter.formatBold("Bug report cancelled")} by ${formatter.formatUserMention(username)}`);
|
|
@@ -73915,6 +73985,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73915
73985
|
userAttribution;
|
|
73916
73986
|
threadLogsEnabled;
|
|
73917
73987
|
bugReportsEnabled;
|
|
73988
|
+
bugReportRepo;
|
|
73918
73989
|
usageShowEmails;
|
|
73919
73990
|
threadLogsRetentionDays;
|
|
73920
73991
|
limits;
|
|
@@ -73944,7 +74015,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73944
74015
|
connectorsOff = null;
|
|
73945
74016
|
usageRefreshInFlight = null;
|
|
73946
74017
|
usageRefreshedAt = 0;
|
|
73947
|
-
constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts, respondOnlyWhenMentioned = false, userAttribution = true, bugReportsEnabled = true, usage) {
|
|
74018
|
+
constructor(workingDir, permissionModeOrSkipFlag = "default", chromeEnabled = false, worktreeMode = "prompt", sessionsPath, threadLogsEnabled = true, threadLogsRetentionDays = 30, limits, claudeAccounts, respondOnlyWhenMentioned = false, userAttribution = true, bugReportsEnabled = true, usage, bugReportRepo = DEFAULT_BUG_REPORT_REPO) {
|
|
73948
74019
|
super();
|
|
73949
74020
|
this.workingDir = workingDir;
|
|
73950
74021
|
this.permissionMode = typeof permissionModeOrSkipFlag === "boolean" ? permissionModeOrSkipFlag ? "bypass" : "default" : permissionModeOrSkipFlag;
|
|
@@ -73953,6 +74024,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
73953
74024
|
this.respondOnlyWhenMentioned = respondOnlyWhenMentioned;
|
|
73954
74025
|
this.userAttribution = userAttribution;
|
|
73955
74026
|
this.bugReportsEnabled = bugReportsEnabled;
|
|
74027
|
+
this.bugReportRepo = bugReportRepo;
|
|
73956
74028
|
this.threadLogsEnabled = threadLogsEnabled;
|
|
73957
74029
|
this.threadLogsRetentionDays = threadLogsRetentionDays;
|
|
73958
74030
|
this.limits = resolveLimits(limits);
|
|
@@ -74097,7 +74169,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
74097
74169
|
threadLogsRetentionDays: this.threadLogsRetentionDays,
|
|
74098
74170
|
permissionTimeoutMs: this.limits.permissionTimeoutSeconds * 1000,
|
|
74099
74171
|
flushDelayMs: this.limits.flushDelayMs,
|
|
74100
|
-
bugReportsEnabled: this.bugReportsEnabled
|
|
74172
|
+
bugReportsEnabled: this.bugReportsEnabled,
|
|
74173
|
+
bugReportRepo: this.bugReportRepo
|
|
74101
74174
|
};
|
|
74102
74175
|
const state = {
|
|
74103
74176
|
sessions: this.registry.getSessions(),
|
|
@@ -74762,7 +74835,8 @@ class SessionManager extends EventEmitter4 {
|
|
|
74762
74835
|
}
|
|
74763
74836
|
const updateInfo = await this.autoUpdateManager.checkNow();
|
|
74764
74837
|
if (!updateInfo || !updateInfo.available) {
|
|
74765
|
-
|
|
74838
|
+
const blocked = this.autoUpdateManager.getBlockedUpdate();
|
|
74839
|
+
await platform.createPost(blocked ? `⚠️ ${formatter.formatBold(`v${blocked.latestVersion} is available but not installable`)}: ${blocked.reason}` : `✅ ${formatter.formatBold("Up to date")} - no updates available`, threadId);
|
|
74766
74840
|
return;
|
|
74767
74841
|
}
|
|
74768
74842
|
const scheduledAt = this.autoUpdateManager.getScheduledRestartAt();
|
|
@@ -77817,12 +77891,12 @@ function wrapAnsi2(string, columns, options) {
|
|
|
77817
77891
|
|
|
77818
77892
|
// node_modules/terminal-size/index.js
|
|
77819
77893
|
import process12 from "node:process";
|
|
77820
|
-
import { execFileSync } from "node:child_process";
|
|
77894
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
77821
77895
|
import fs6 from "node:fs";
|
|
77822
77896
|
import tty2 from "node:tty";
|
|
77823
77897
|
var defaultColumns = 80;
|
|
77824
77898
|
var defaultRows = 24;
|
|
77825
|
-
var exec5 = (command, arguments_, { shell, env } = {}) =>
|
|
77899
|
+
var exec5 = (command, arguments_, { shell, env } = {}) => execFileSync2(command, arguments_, {
|
|
77826
77900
|
encoding: "utf8",
|
|
77827
77901
|
stdio: ["ignore", "pipe", "ignore"],
|
|
77828
77902
|
timeout: 500,
|
|
@@ -89734,6 +89808,7 @@ import { EventEmitter as EventEmitter9 } from "events";
|
|
|
89734
89808
|
|
|
89735
89809
|
// src/auto-update/checker.ts
|
|
89736
89810
|
init_logger();
|
|
89811
|
+
var import_semver5 = __toESM(require_semver2(), 1);
|
|
89737
89812
|
import { EventEmitter as EventEmitter7 } from "events";
|
|
89738
89813
|
var log54 = createLogger("checker");
|
|
89739
89814
|
var PACKAGE_NAME = "claude-threads";
|
|
@@ -89750,7 +89825,16 @@ function compareVersions(a, b) {
|
|
|
89750
89825
|
}
|
|
89751
89826
|
return 0;
|
|
89752
89827
|
}
|
|
89753
|
-
|
|
89828
|
+
function unmetEngine(engines, versions = process.versions) {
|
|
89829
|
+
const [runtime, running] = versions.bun ? ["bun", versions.bun] : ["node", versions.node];
|
|
89830
|
+
const required = engines?.[runtime];
|
|
89831
|
+
if (!required || !import_semver5.validRange(required))
|
|
89832
|
+
return null;
|
|
89833
|
+
if (import_semver5.satisfies(running, required, { includePrerelease: true }))
|
|
89834
|
+
return null;
|
|
89835
|
+
return `requires ${runtime} ${required}, this host runs ${running}`;
|
|
89836
|
+
}
|
|
89837
|
+
async function fetchLatestRelease() {
|
|
89754
89838
|
try {
|
|
89755
89839
|
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
89756
89840
|
headers: {
|
|
@@ -89762,7 +89846,7 @@ async function fetchLatestVersion() {
|
|
|
89762
89846
|
return null;
|
|
89763
89847
|
}
|
|
89764
89848
|
const data = await response.json();
|
|
89765
|
-
return data.version
|
|
89849
|
+
return data.version ? { version: data.version, engines: data.engines } : null;
|
|
89766
89850
|
} catch (err) {
|
|
89767
89851
|
log54.warn(`Failed to fetch latest version: ${err}`);
|
|
89768
89852
|
return null;
|
|
@@ -89774,6 +89858,7 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
89774
89858
|
checkInterval = null;
|
|
89775
89859
|
lastCheck = null;
|
|
89776
89860
|
lastUpdateInfo = null;
|
|
89861
|
+
blockedUpdate = null;
|
|
89777
89862
|
isChecking = false;
|
|
89778
89863
|
constructor(config) {
|
|
89779
89864
|
super();
|
|
@@ -89813,14 +89898,25 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
89813
89898
|
this.emit("check:start");
|
|
89814
89899
|
try {
|
|
89815
89900
|
log54.debug("Checking for updates...");
|
|
89816
|
-
const
|
|
89817
|
-
if (!
|
|
89901
|
+
const latest = await fetchLatestRelease();
|
|
89902
|
+
if (!latest) {
|
|
89818
89903
|
this.emit("check:complete", false);
|
|
89819
89904
|
return null;
|
|
89820
89905
|
}
|
|
89821
89906
|
this.lastCheck = new Date;
|
|
89907
|
+
const latestVersion = latest.version;
|
|
89822
89908
|
const currentVersion = VERSION;
|
|
89823
89909
|
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
|
|
89910
|
+
const blockedReason = hasUpdate ? unmetEngine(latest.engines) : null;
|
|
89911
|
+
if (blockedReason) {
|
|
89912
|
+
if (this.blockedUpdate?.latestVersion !== latestVersion) {
|
|
89913
|
+
log54.warn(`⚠️ Not updating to v${latestVersion}: ${blockedReason}`);
|
|
89914
|
+
}
|
|
89915
|
+
this.blockedUpdate = { latestVersion, reason: blockedReason };
|
|
89916
|
+
this.emit("check:complete", false);
|
|
89917
|
+
return null;
|
|
89918
|
+
}
|
|
89919
|
+
this.blockedUpdate = null;
|
|
89824
89920
|
if (hasUpdate) {
|
|
89825
89921
|
const updateInfo = {
|
|
89826
89922
|
available: true,
|
|
@@ -89850,6 +89946,9 @@ class UpdateChecker extends EventEmitter7 {
|
|
|
89850
89946
|
getLastUpdateInfo() {
|
|
89851
89947
|
return this.lastUpdateInfo;
|
|
89852
89948
|
}
|
|
89949
|
+
getBlockedUpdate() {
|
|
89950
|
+
return this.blockedUpdate;
|
|
89951
|
+
}
|
|
89853
89952
|
getLastCheckTime() {
|
|
89854
89953
|
return this.lastCheck;
|
|
89855
89954
|
}
|
|
@@ -90557,6 +90656,9 @@ class AutoUpdateManager extends EventEmitter9 {
|
|
|
90557
90656
|
getUpdateInfo() {
|
|
90558
90657
|
return this.state.updateInfo;
|
|
90559
90658
|
}
|
|
90659
|
+
getBlockedUpdate() {
|
|
90660
|
+
return this.checker.getBlockedUpdate();
|
|
90661
|
+
}
|
|
90560
90662
|
getScheduledRestartAt() {
|
|
90561
90663
|
return this.scheduler.getScheduledRestartAt();
|
|
90562
90664
|
}
|
|
@@ -91137,7 +91239,7 @@ async function startWithoutDaemon() {
|
|
|
91137
91239
|
const threadLogsRetentionDays = config.threadLogs?.retentionDays ?? 30;
|
|
91138
91240
|
const bugReportsEnabled = resolveBugReportsEnabled(config.bugReports);
|
|
91139
91241
|
configureBugReports(bugReportsEnabled);
|
|
91140
|
-
const session = new SessionManager(workingDir, initialPermissionMode, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts, config.respondOnlyWhenMentioned, config.userAttribution, bugReportsEnabled, config.usage);
|
|
91242
|
+
const session = new SessionManager(workingDir, initialPermissionMode, config.chrome, config.worktreeMode, undefined, threadLogsEnabled, threadLogsRetentionDays, config.limits, config.claudeAccounts, config.respondOnlyWhenMentioned, config.userAttribution, bugReportsEnabled, config.usage, resolveBugReportRepo(config.bugReports));
|
|
91141
91243
|
if (config.stickyMessage) {
|
|
91142
91244
|
session.setStickyMessageCustomization(config.stickyMessage.description, config.stickyMessage.footer);
|
|
91143
91245
|
}
|
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -38835,6 +38835,7 @@ class Redactor {
|
|
|
38835
38835
|
}
|
|
38836
38836
|
|
|
38837
38837
|
// src/operations/bug-report/handler.ts
|
|
38838
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
38838
38839
|
init_version_check();
|
|
38839
38840
|
|
|
38840
38841
|
// src/persistence/thread-logger.ts
|
|
@@ -38845,6 +38846,7 @@ var LOGS_BASE_DIR = join4(stateHome(), ".claude-threads", "logs");
|
|
|
38845
38846
|
|
|
38846
38847
|
// src/operations/bug-report/handler.ts
|
|
38847
38848
|
var piiRedactor = new Redactor({ aggressive: true });
|
|
38849
|
+
var MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
38848
38850
|
// src/mcp/decision-bridge.ts
|
|
38849
38851
|
import { createServer, createConnection } from "node:net";
|
|
38850
38852
|
var MAX_BRIDGE_REQUEST_BYTES = 1024 * 1024;
|
|
@@ -39812,7 +39814,7 @@ var CHAT_PLATFORM_PROMPT = generateChatPlatformPrompt();
|
|
|
39812
39814
|
init_cli();
|
|
39813
39815
|
|
|
39814
39816
|
// src/update-notifier.ts
|
|
39815
|
-
var
|
|
39817
|
+
var import_semver3 = __toESM(require_semver2(), 1);
|
|
39816
39818
|
|
|
39817
39819
|
// src/operations/commands/handler.ts
|
|
39818
39820
|
init_emoji();
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -54,7 +54,7 @@ platforms:
|
|
|
54
54
|
| `respondOnlyWhenMentioned` | Start new threads in quiet mode, where the bot only replies to messages that @mention it. Users can still toggle per-thread with `!mentions`. | `false` |
|
|
55
55
|
| `userAttribution` | Prefix each user turn sent to Claude with the sender's `[@username]:` so Claude can tell who is speaking in multi-user threads. Only applied once a thread has more than one participant (after `!invite`); solo threads are left untouched. Set `false` to disable. Applies to new sessions. | `true` |
|
|
56
56
|
| `keepAlive` | Prevent system sleep while sessions are active | `true` |
|
|
57
|
-
| `bugReports` | Whether `!bug` may file a report. ⚠️ A bug report leaves your infrastructure:
|
|
57
|
+
| `bugReports` | Whether `!bug` may file a report, and where. ⚠️ A bug report leaves your infrastructure: the report body — session context plus recent daemon log lines — is filed as an issue on the project's public GitHub repository, behind best-effort redaction, unless you set `bugReports: { repo: owner/name }` to file in your own repository. Attached screenshots go along with the issue once someone approves the preview; this needs `gh` 2.99 or newer and write access to the target repository, and without those the report is filed without them. An attachment on a public repository stays online permanently, even if the issue is deleted. Claude can also invoke `!bug` itself, but a person still has to approve the preview. Set `false` and the whole path is removed: the command is refused, Claude cannot invoke it either, the 🐛 error reaction does nothing, the approval that would file the issue is refused, and `!bug` disappears from `!help`. **Fails closed** — a malformed value disables it rather than defaulting to on. | `true` |
|
|
58
58
|
| `limits` | Resource limits and timeouts (see below) | see below |
|
|
59
59
|
| `threadLogs` | Thread logging (see below) | enabled |
|
|
60
60
|
| `stickyMessage` | Sticky message text customization (see below) | none |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-threads",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.2",
|
|
4
4
|
"description": "Run Claude Code from Slack or Mattermost. Sessions stream live into threads where your whole team can watch and steer.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"cross-spawn": "^7.0.6",
|
|
79
79
|
"diff": "^8.0.3",
|
|
80
80
|
"express-rate-limit": "^8.7.1",
|
|
81
|
-
"hono": "4.13.
|
|
81
|
+
"hono": "4.13.14",
|
|
82
82
|
"ink": "^8.0.0",
|
|
83
83
|
"js-yaml": "^4.3.1",
|
|
84
84
|
"prompts": "^2.4.2",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"@types/ws": "^8.18.2",
|
|
102
102
|
"eslint": "^10.13.0",
|
|
103
103
|
"husky": "^9.1.7",
|
|
104
|
-
"knip": "6.
|
|
104
|
+
"knip": "6.43.0",
|
|
105
105
|
"lint-staged": "^17.0.4",
|
|
106
106
|
"prettier": "^3.9.10",
|
|
107
107
|
"react-devtools-core": "^8.0.0",
|