claude-threads 1.3.0 → 1.3.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 +19 -0
- package/dist/index.js +126 -135
- package/dist/mcp/permission-server.js +3125 -3122
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.3.2] - 2026-01-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Clearer version display** - Status bars now show `CT v1.3.2 · CC v2.1.12` instead of `v1.3.2 · CLI 2.1.12` (#232)
|
|
12
|
+
- CT = claude-threads (this bot)
|
|
13
|
+
- CC = Claude Code (the CLI)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Resolve ESLint warnings** - Fix 5 `no-non-null-assertion` lint warnings (#233)
|
|
17
|
+
|
|
18
|
+
## [1.3.1] - 2026-01-17
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **WebSocket reconnection restored** - Fixed critical bugs in reconnection logic that were introduced in v1.0.3 (#231)
|
|
22
|
+
- Heartbeat now properly triggers reconnection when detecting dead connections (was just closing without reconnecting)
|
|
23
|
+
- Auto-retry on reconnection failure restored (was lost when code was refactored to base class)
|
|
24
|
+
- TUI will no longer show "connected" when connection is actually dead
|
|
25
|
+
- All platforms (Mattermost and Slack) now benefit from robust reconnection logic
|
|
26
|
+
|
|
8
27
|
## [1.3.0] - 2026-01-17
|
|
9
28
|
|
|
10
29
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -49566,6 +49566,15 @@ function validateClaudeCli() {
|
|
|
49566
49566
|
rawOutput: result.rawOutput
|
|
49567
49567
|
};
|
|
49568
49568
|
}
|
|
49569
|
+
if (!result.version) {
|
|
49570
|
+
return {
|
|
49571
|
+
installed: true,
|
|
49572
|
+
version: null,
|
|
49573
|
+
compatible: true,
|
|
49574
|
+
message: "Claude CLI found (version unknown)",
|
|
49575
|
+
rawOutput: result.rawOutput ?? undefined
|
|
49576
|
+
};
|
|
49577
|
+
}
|
|
49569
49578
|
const compatible = isVersionCompatible(result.version);
|
|
49570
49579
|
if (!compatible) {
|
|
49571
49580
|
return {
|
|
@@ -50921,6 +50930,7 @@ class BasePlatformClient extends EventEmitter {
|
|
|
50921
50930
|
reconnectAttempts = 0;
|
|
50922
50931
|
maxReconnectAttempts = 10;
|
|
50923
50932
|
reconnectDelay = 1000;
|
|
50933
|
+
reconnectTimeout = null;
|
|
50924
50934
|
isUserAllowed(username) {
|
|
50925
50935
|
if (this.allowedUsers.length === 0) {
|
|
50926
50936
|
return true;
|
|
@@ -50945,6 +50955,10 @@ class BasePlatformClient extends EventEmitter {
|
|
|
50945
50955
|
wsLogger.info("Disconnecting (intentional)");
|
|
50946
50956
|
this.isIntentionalDisconnect = true;
|
|
50947
50957
|
this.stopHeartbeat();
|
|
50958
|
+
if (this.reconnectTimeout) {
|
|
50959
|
+
clearTimeout(this.reconnectTimeout);
|
|
50960
|
+
this.reconnectTimeout = null;
|
|
50961
|
+
}
|
|
50948
50962
|
this.forceCloseConnection();
|
|
50949
50963
|
}
|
|
50950
50964
|
prepareForReconnect() {
|
|
@@ -50960,7 +50974,7 @@ class BasePlatformClient extends EventEmitter {
|
|
|
50960
50974
|
if (silentFor > this.HEARTBEAT_TIMEOUT_MS) {
|
|
50961
50975
|
log.warn(`Connection dead (no activity for ${Math.round(silentFor / 1000)}s), reconnecting...`);
|
|
50962
50976
|
this.stopHeartbeat();
|
|
50963
|
-
this.
|
|
50977
|
+
this.scheduleReconnect();
|
|
50964
50978
|
return;
|
|
50965
50979
|
}
|
|
50966
50980
|
wsLogger.debug(`Heartbeat check (last activity ${Math.round(silentFor / 1000)}s ago)`);
|
|
@@ -50973,6 +50987,10 @@ class BasePlatformClient extends EventEmitter {
|
|
|
50973
50987
|
}
|
|
50974
50988
|
}
|
|
50975
50989
|
scheduleReconnect() {
|
|
50990
|
+
if (this.reconnectTimeout) {
|
|
50991
|
+
clearTimeout(this.reconnectTimeout);
|
|
50992
|
+
this.reconnectTimeout = null;
|
|
50993
|
+
}
|
|
50976
50994
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
50977
50995
|
log.error("Max reconnection attempts reached");
|
|
50978
50996
|
return;
|
|
@@ -50981,11 +50999,17 @@ class BasePlatformClient extends EventEmitter {
|
|
|
50981
50999
|
this.isReconnecting = true;
|
|
50982
51000
|
this.reconnectAttempts++;
|
|
50983
51001
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
50984
|
-
|
|
51002
|
+
wsLogger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
50985
51003
|
this.emit("reconnecting", this.reconnectAttempts);
|
|
50986
|
-
setTimeout(() => {
|
|
51004
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
51005
|
+
this.reconnectTimeout = null;
|
|
51006
|
+
if (this.isIntentionalDisconnect) {
|
|
51007
|
+
wsLogger.debug("Skipping reconnect: intentional disconnect was called");
|
|
51008
|
+
return;
|
|
51009
|
+
}
|
|
50987
51010
|
this.connect().catch((err) => {
|
|
50988
|
-
|
|
51011
|
+
wsLogger.error(`Reconnection failed: ${err}`);
|
|
51012
|
+
this.scheduleReconnect();
|
|
50989
51013
|
});
|
|
50990
51014
|
}, delay);
|
|
50991
51015
|
}
|
|
@@ -51014,6 +51038,33 @@ class BasePlatformClient extends EventEmitter {
|
|
|
51014
51038
|
this.lastMessageAt = Date.now();
|
|
51015
51039
|
}
|
|
51016
51040
|
}
|
|
51041
|
+
// src/version.ts
|
|
51042
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
|
|
51043
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
51044
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
51045
|
+
var __dirname3 = dirname3(fileURLToPath2(import.meta.url));
|
|
51046
|
+
function loadPackageJson() {
|
|
51047
|
+
const candidates = [
|
|
51048
|
+
resolve2(__dirname3, "..", "package.json"),
|
|
51049
|
+
resolve2(__dirname3, "..", "..", "package.json"),
|
|
51050
|
+
resolve2(process.cwd(), "package.json")
|
|
51051
|
+
];
|
|
51052
|
+
for (const candidate of candidates) {
|
|
51053
|
+
if (existsSync4(candidate)) {
|
|
51054
|
+
try {
|
|
51055
|
+
const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
|
|
51056
|
+
if (pkg.name === "claude-threads") {
|
|
51057
|
+
return { version: pkg.version, name: pkg.name };
|
|
51058
|
+
}
|
|
51059
|
+
} catch {}
|
|
51060
|
+
}
|
|
51061
|
+
}
|
|
51062
|
+
return { version: "unknown", name: "claude-threads" };
|
|
51063
|
+
}
|
|
51064
|
+
var pkgInfo = loadPackageJson();
|
|
51065
|
+
var VERSION = pkgInfo.version;
|
|
51066
|
+
var PKG = pkgInfo;
|
|
51067
|
+
|
|
51017
51068
|
// src/utils/format.ts
|
|
51018
51069
|
function extractThreadId(sessionId) {
|
|
51019
51070
|
const colonIndex = sessionId.indexOf(":");
|
|
@@ -51053,6 +51104,10 @@ function formatRelativeTimeShort(date) {
|
|
|
51053
51104
|
return `${minutes}m ago`;
|
|
51054
51105
|
return "<1m ago";
|
|
51055
51106
|
}
|
|
51107
|
+
function formatVersionString() {
|
|
51108
|
+
const claudeVersion = getClaudeCliVersion().version;
|
|
51109
|
+
return claudeVersion ? `CT v${VERSION} · CC v${claudeVersion}` : `CT v${VERSION}`;
|
|
51110
|
+
}
|
|
51056
51111
|
function truncateAtWord(str2, maxLength) {
|
|
51057
51112
|
if (str2.length <= maxLength)
|
|
51058
51113
|
return str2;
|
|
@@ -51344,13 +51399,14 @@ class MattermostClient extends BasePlatformClient {
|
|
|
51344
51399
|
};
|
|
51345
51400
|
}
|
|
51346
51401
|
async processAndEmitPost(post) {
|
|
51347
|
-
const
|
|
51402
|
+
const fileIds = post.file_ids;
|
|
51403
|
+
const hasFileIds = fileIds && fileIds.length > 0;
|
|
51348
51404
|
const hasFileMetadata = post.metadata?.files && post.metadata.files.length > 0;
|
|
51349
51405
|
if (hasFileIds && !hasFileMetadata) {
|
|
51350
|
-
log2.debug(`Post ${formatShortId(post.id)} has ${
|
|
51406
|
+
log2.debug(`Post ${formatShortId(post.id)} has ${fileIds.length} file(s), fetching metadata`);
|
|
51351
51407
|
try {
|
|
51352
51408
|
const files = [];
|
|
51353
|
-
for (const fileId of
|
|
51409
|
+
for (const fileId of fileIds) {
|
|
51354
51410
|
try {
|
|
51355
51411
|
const file = await this.api("GET", `/files/${fileId}/info`);
|
|
51356
51412
|
files.push(file);
|
|
@@ -51394,7 +51450,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
51394
51450
|
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
51395
51451
|
const delay = this.RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
51396
51452
|
log2.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
51397
|
-
await new Promise((
|
|
51453
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
51398
51454
|
return this.api(method, path, body, retryCount + 1, options2);
|
|
51399
51455
|
}
|
|
51400
51456
|
const isSilent = options2?.silent?.includes(response.status);
|
|
@@ -51580,7 +51636,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
51580
51636
|
await this.getBotUser();
|
|
51581
51637
|
wsLogger.debug(`Bot user ID: ${this.botUserId}`);
|
|
51582
51638
|
const wsUrl = this.url.replace(/^http/, "ws").concat("/api/v4/websocket");
|
|
51583
|
-
return new Promise((
|
|
51639
|
+
return new Promise((resolve3, reject) => {
|
|
51584
51640
|
this.ws = new WebSocket(wsUrl);
|
|
51585
51641
|
this.ws.onopen = () => {
|
|
51586
51642
|
wsLogger.info("WebSocket connected, sending auth challenge");
|
|
@@ -51601,7 +51657,7 @@ class MattermostClient extends BasePlatformClient {
|
|
|
51601
51657
|
this.handleEvent(wsEvent);
|
|
51602
51658
|
if (wsEvent.event === "hello") {
|
|
51603
51659
|
this.onConnectionEstablished();
|
|
51604
|
-
|
|
51660
|
+
resolve3();
|
|
51605
51661
|
}
|
|
51606
51662
|
} catch (err) {
|
|
51607
51663
|
wsLogger.warn(`Failed to parse message: ${err}`);
|
|
@@ -51823,7 +51879,6 @@ class SlackClient extends BasePlatformClient {
|
|
|
51823
51879
|
channelId;
|
|
51824
51880
|
skipPermissions;
|
|
51825
51881
|
apiUrl;
|
|
51826
|
-
reconnectTimeout = null;
|
|
51827
51882
|
userCache = new Map;
|
|
51828
51883
|
usernameToIdCache = new Map;
|
|
51829
51884
|
botUserId = null;
|
|
@@ -51886,7 +51941,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
51886
51941
|
if (now < this.rateLimitRetryAfter) {
|
|
51887
51942
|
const waitTime = this.rateLimitRetryAfter - now;
|
|
51888
51943
|
log3.debug(`Rate limited, waiting ${waitTime}ms`);
|
|
51889
|
-
await new Promise((
|
|
51944
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
51890
51945
|
}
|
|
51891
51946
|
this.rateLimitDelay = 0;
|
|
51892
51947
|
}
|
|
@@ -51910,7 +51965,7 @@ class SlackClient extends BasePlatformClient {
|
|
|
51910
51965
|
this.rateLimitDelay = retryAfter * 1000;
|
|
51911
51966
|
this.rateLimitRetryAfter = Date.now() + this.rateLimitDelay;
|
|
51912
51967
|
log3.warn(`Rate limited by Slack, retrying after ${retryAfter}s (attempt ${retryCount + 1}/${this.MAX_RATE_LIMIT_RETRIES})`);
|
|
51913
|
-
await new Promise((
|
|
51968
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.rateLimitDelay));
|
|
51914
51969
|
return this.api(method, endpoint, body, retryCount + 1);
|
|
51915
51970
|
}
|
|
51916
51971
|
if (!response.ok) {
|
|
@@ -51955,12 +52010,12 @@ class SlackClient extends BasePlatformClient {
|
|
|
51955
52010
|
const response = await this.appApi("POST", "apps.connections.open");
|
|
51956
52011
|
const wsUrl = response.url;
|
|
51957
52012
|
wsLogger.info("Socket Mode: Got WebSocket URL, connecting...");
|
|
51958
|
-
return new Promise((
|
|
52013
|
+
return new Promise((resolve3, reject) => {
|
|
51959
52014
|
let settled = false;
|
|
51960
52015
|
const doResolve = () => {
|
|
51961
52016
|
if (!settled) {
|
|
51962
52017
|
settled = true;
|
|
51963
|
-
|
|
52018
|
+
resolve3();
|
|
51964
52019
|
}
|
|
51965
52020
|
};
|
|
51966
52021
|
const doReject = (err) => {
|
|
@@ -52147,33 +52202,6 @@ class SlackClient extends BasePlatformClient {
|
|
|
52147
52202
|
this.ws = null;
|
|
52148
52203
|
}
|
|
52149
52204
|
}
|
|
52150
|
-
scheduleReconnect() {
|
|
52151
|
-
if (this.reconnectTimeout) {
|
|
52152
|
-
clearTimeout(this.reconnectTimeout);
|
|
52153
|
-
this.reconnectTimeout = null;
|
|
52154
|
-
}
|
|
52155
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
52156
|
-
log3.error("Max reconnection attempts reached");
|
|
52157
|
-
return;
|
|
52158
|
-
}
|
|
52159
|
-
this.forceCloseConnection();
|
|
52160
|
-
this.isReconnecting = true;
|
|
52161
|
-
this.reconnectAttempts++;
|
|
52162
|
-
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
52163
|
-
wsLogger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
52164
|
-
this.emit("reconnecting", this.reconnectAttempts);
|
|
52165
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
52166
|
-
this.reconnectTimeout = null;
|
|
52167
|
-
if (this.isIntentionalDisconnect) {
|
|
52168
|
-
wsLogger.debug("Skipping reconnect: intentional disconnect was called");
|
|
52169
|
-
return;
|
|
52170
|
-
}
|
|
52171
|
-
this.connect().catch((err) => {
|
|
52172
|
-
wsLogger.error(`Reconnection failed: ${err}`);
|
|
52173
|
-
this.scheduleReconnect();
|
|
52174
|
-
});
|
|
52175
|
-
}, delay);
|
|
52176
|
-
}
|
|
52177
52205
|
async recoverMissedMessages() {
|
|
52178
52206
|
if (!this.lastProcessedTs) {
|
|
52179
52207
|
return;
|
|
@@ -52204,16 +52232,6 @@ class SlackClient extends BasePlatformClient {
|
|
|
52204
52232
|
log3.warn(`Failed to recover missed messages: ${err}`);
|
|
52205
52233
|
}
|
|
52206
52234
|
}
|
|
52207
|
-
disconnect() {
|
|
52208
|
-
wsLogger.info("Disconnecting Socket Mode WebSocket (intentional)");
|
|
52209
|
-
this.isIntentionalDisconnect = true;
|
|
52210
|
-
this.stopHeartbeat();
|
|
52211
|
-
if (this.reconnectTimeout) {
|
|
52212
|
-
clearTimeout(this.reconnectTimeout);
|
|
52213
|
-
this.reconnectTimeout = null;
|
|
52214
|
-
}
|
|
52215
|
-
this.forceCloseConnection();
|
|
52216
|
-
}
|
|
52217
52235
|
async fetchBotUser() {
|
|
52218
52236
|
const response = await this.api("POST", "auth.test");
|
|
52219
52237
|
this.botUserId = response.user_id;
|
|
@@ -52615,7 +52633,7 @@ class MattermostPermissionApi {
|
|
|
52615
52633
|
await updatePost(this.apiConfig, postId, message);
|
|
52616
52634
|
}
|
|
52617
52635
|
async waitForReaction(postId, botUserId, timeoutMs) {
|
|
52618
|
-
return new Promise((
|
|
52636
|
+
return new Promise((resolve3) => {
|
|
52619
52637
|
const wsUrl = this.config.url.replace(/^http/, "ws") + "/api/v4/websocket";
|
|
52620
52638
|
mcpLogger.debug(`Connecting to WebSocket: ${wsUrl}`);
|
|
52621
52639
|
const ws = new WebSocket(wsUrl);
|
|
@@ -52630,7 +52648,7 @@ class MattermostPermissionApi {
|
|
|
52630
52648
|
mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
|
|
52631
52649
|
resolved = true;
|
|
52632
52650
|
cleanup();
|
|
52633
|
-
|
|
52651
|
+
resolve3(null);
|
|
52634
52652
|
}
|
|
52635
52653
|
}, timeoutMs);
|
|
52636
52654
|
ws.onopen = () => {
|
|
@@ -52658,7 +52676,7 @@ class MattermostPermissionApi {
|
|
|
52658
52676
|
resolved = true;
|
|
52659
52677
|
clearTimeout(timeout);
|
|
52660
52678
|
cleanup();
|
|
52661
|
-
|
|
52679
|
+
resolve3({
|
|
52662
52680
|
postId: reaction.post_id,
|
|
52663
52681
|
userId: reaction.user_id,
|
|
52664
52682
|
emojiName: reaction.emoji_name
|
|
@@ -52673,7 +52691,7 @@ class MattermostPermissionApi {
|
|
|
52673
52691
|
if (!resolved) {
|
|
52674
52692
|
resolved = true;
|
|
52675
52693
|
clearTimeout(timeout);
|
|
52676
|
-
|
|
52694
|
+
resolve3(null);
|
|
52677
52695
|
}
|
|
52678
52696
|
};
|
|
52679
52697
|
ws.onclose = () => {
|
|
@@ -52681,7 +52699,7 @@ class MattermostPermissionApi {
|
|
|
52681
52699
|
if (!resolved) {
|
|
52682
52700
|
resolved = true;
|
|
52683
52701
|
clearTimeout(timeout);
|
|
52684
|
-
|
|
52702
|
+
resolve3(null);
|
|
52685
52703
|
}
|
|
52686
52704
|
};
|
|
52687
52705
|
});
|
|
@@ -52789,7 +52807,7 @@ class SlackPermissionApi {
|
|
|
52789
52807
|
});
|
|
52790
52808
|
}
|
|
52791
52809
|
async waitForReaction(postId, botUserId, timeoutMs) {
|
|
52792
|
-
return new Promise((
|
|
52810
|
+
return new Promise((resolve3) => {
|
|
52793
52811
|
let resolved = false;
|
|
52794
52812
|
let ws = null;
|
|
52795
52813
|
const cleanup = () => {
|
|
@@ -52802,7 +52820,7 @@ class SlackPermissionApi {
|
|
|
52802
52820
|
mcpLogger.debug(`Reaction wait timed out after ${timeoutMs}ms`);
|
|
52803
52821
|
resolved = true;
|
|
52804
52822
|
cleanup();
|
|
52805
|
-
|
|
52823
|
+
resolve3(null);
|
|
52806
52824
|
}
|
|
52807
52825
|
}, timeoutMs);
|
|
52808
52826
|
this.getSocketModeUrl().then((wsUrl) => {
|
|
@@ -52840,7 +52858,7 @@ class SlackPermissionApi {
|
|
|
52840
52858
|
resolved = true;
|
|
52841
52859
|
clearTimeout(timeout);
|
|
52842
52860
|
cleanup();
|
|
52843
|
-
|
|
52861
|
+
resolve3({
|
|
52844
52862
|
postId: item.ts,
|
|
52845
52863
|
userId,
|
|
52846
52864
|
emojiName
|
|
@@ -52856,7 +52874,7 @@ class SlackPermissionApi {
|
|
|
52856
52874
|
resolved = true;
|
|
52857
52875
|
clearTimeout(timeout);
|
|
52858
52876
|
cleanup();
|
|
52859
|
-
|
|
52877
|
+
resolve3(null);
|
|
52860
52878
|
}
|
|
52861
52879
|
}
|
|
52862
52880
|
} catch (err) {
|
|
@@ -52869,7 +52887,7 @@ class SlackPermissionApi {
|
|
|
52869
52887
|
resolved = true;
|
|
52870
52888
|
clearTimeout(timeout);
|
|
52871
52889
|
cleanup();
|
|
52872
|
-
|
|
52890
|
+
resolve3(null);
|
|
52873
52891
|
}
|
|
52874
52892
|
};
|
|
52875
52893
|
ws.onclose = () => {
|
|
@@ -52877,7 +52895,7 @@ class SlackPermissionApi {
|
|
|
52877
52895
|
if (!resolved) {
|
|
52878
52896
|
resolved = true;
|
|
52879
52897
|
clearTimeout(timeout);
|
|
52880
|
-
|
|
52898
|
+
resolve3(null);
|
|
52881
52899
|
}
|
|
52882
52900
|
};
|
|
52883
52901
|
}).catch((err) => {
|
|
@@ -52885,7 +52903,7 @@ class SlackPermissionApi {
|
|
|
52885
52903
|
if (!resolved) {
|
|
52886
52904
|
resolved = true;
|
|
52887
52905
|
clearTimeout(timeout);
|
|
52888
|
-
|
|
52906
|
+
resolve3(null);
|
|
52889
52907
|
}
|
|
52890
52908
|
});
|
|
52891
52909
|
});
|
|
@@ -52901,7 +52919,7 @@ class SlackPermissionApi {
|
|
|
52901
52919
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
52902
52920
|
|
|
52903
52921
|
// src/persistence/session-store.ts
|
|
52904
|
-
import { existsSync as
|
|
52922
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
52905
52923
|
import { homedir as homedir2 } from "os";
|
|
52906
52924
|
import { join as join2 } from "path";
|
|
52907
52925
|
var log5 = createLogger("persist");
|
|
@@ -52922,18 +52940,18 @@ class SessionStore {
|
|
|
52922
52940
|
this.sessionsFile = DEFAULT_SESSIONS_FILE;
|
|
52923
52941
|
this.configDir = DEFAULT_CONFIG_DIR;
|
|
52924
52942
|
}
|
|
52925
|
-
if (!
|
|
52943
|
+
if (!existsSync5(this.configDir)) {
|
|
52926
52944
|
mkdirSync2(this.configDir, { recursive: true });
|
|
52927
52945
|
}
|
|
52928
52946
|
}
|
|
52929
52947
|
load() {
|
|
52930
52948
|
const sessions = new Map;
|
|
52931
|
-
if (!
|
|
52949
|
+
if (!existsSync5(this.sessionsFile)) {
|
|
52932
52950
|
log5.debug("No sessions file found");
|
|
52933
52951
|
return sessions;
|
|
52934
52952
|
}
|
|
52935
52953
|
try {
|
|
52936
|
-
const data = JSON.parse(
|
|
52954
|
+
const data = JSON.parse(readFileSync4(this.sessionsFile, "utf-8"));
|
|
52937
52955
|
if (data.version === 1) {
|
|
52938
52956
|
log5.info("Migrating sessions from v1 to v2 (adding platformId)");
|
|
52939
52957
|
const newSessions = {};
|
|
@@ -53107,11 +53125,11 @@ class SessionStore {
|
|
|
53107
53125
|
return;
|
|
53108
53126
|
}
|
|
53109
53127
|
loadRaw() {
|
|
53110
|
-
if (!
|
|
53128
|
+
if (!existsSync5(this.sessionsFile)) {
|
|
53111
53129
|
return { version: STORE_VERSION, sessions: {} };
|
|
53112
53130
|
}
|
|
53113
53131
|
try {
|
|
53114
|
-
return JSON.parse(
|
|
53132
|
+
return JSON.parse(readFileSync4(this.sessionsFile, "utf-8"));
|
|
53115
53133
|
} catch {
|
|
53116
53134
|
return { version: STORE_VERSION, sessions: {} };
|
|
53117
53135
|
}
|
|
@@ -53126,14 +53144,14 @@ class SessionStore {
|
|
|
53126
53144
|
init_emoji();
|
|
53127
53145
|
|
|
53128
53146
|
// src/cleanup/scheduler.ts
|
|
53129
|
-
import { existsSync as
|
|
53147
|
+
import { existsSync as existsSync7 } from "fs";
|
|
53130
53148
|
import { readdir, rm } from "fs/promises";
|
|
53131
53149
|
import { join as join5 } from "path";
|
|
53132
53150
|
|
|
53133
53151
|
// src/persistence/thread-logger.ts
|
|
53134
|
-
import { existsSync as
|
|
53152
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync5 } from "fs";
|
|
53135
53153
|
import { homedir as homedir3 } from "os";
|
|
53136
|
-
import { join as join3, dirname as
|
|
53154
|
+
import { join as join3, dirname as dirname4 } from "path";
|
|
53137
53155
|
var log6 = createLogger("thread-log");
|
|
53138
53156
|
var LOGS_BASE_DIR = join3(homedir3(), ".claude-threads", "logs");
|
|
53139
53157
|
|
|
@@ -53157,8 +53175,8 @@ class ThreadLoggerImpl {
|
|
|
53157
53175
|
this.flushIntervalMs = options2?.flushIntervalMs ?? 1000;
|
|
53158
53176
|
this.logPath = join3(LOGS_BASE_DIR, platformId, `${claudeSessionId}.jsonl`);
|
|
53159
53177
|
if (this.enabled) {
|
|
53160
|
-
const dir =
|
|
53161
|
-
if (!
|
|
53178
|
+
const dir = dirname4(this.logPath);
|
|
53179
|
+
if (!existsSync6(dir)) {
|
|
53162
53180
|
mkdirSync3(dir, { recursive: true });
|
|
53163
53181
|
}
|
|
53164
53182
|
this.flushTimer = setInterval(() => {
|
|
@@ -53327,7 +53345,7 @@ function createThreadLogger(platformId, threadId, claudeSessionId, options2) {
|
|
|
53327
53345
|
function cleanupOldLogs(retentionDays = 30) {
|
|
53328
53346
|
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
53329
53347
|
let deletedCount = 0;
|
|
53330
|
-
if (!
|
|
53348
|
+
if (!existsSync6(LOGS_BASE_DIR)) {
|
|
53331
53349
|
return 0;
|
|
53332
53350
|
}
|
|
53333
53351
|
try {
|
|
@@ -53375,12 +53393,12 @@ function getLogFilePath(platformId, sessionId) {
|
|
|
53375
53393
|
function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
|
|
53376
53394
|
const logPath = getLogFilePath(platformId, sessionId);
|
|
53377
53395
|
log6.debug(`Reading log entries from: ${logPath}`);
|
|
53378
|
-
if (!
|
|
53396
|
+
if (!existsSync6(logPath)) {
|
|
53379
53397
|
log6.debug(`Log file does not exist: ${logPath}`);
|
|
53380
53398
|
return [];
|
|
53381
53399
|
}
|
|
53382
53400
|
try {
|
|
53383
|
-
const content =
|
|
53401
|
+
const content = readFileSync5(logPath, "utf8");
|
|
53384
53402
|
const lines = content.trim().split(`
|
|
53385
53403
|
`);
|
|
53386
53404
|
log6.debug(`Log file has ${lines.length} lines`);
|
|
@@ -53412,7 +53430,7 @@ var WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
|
|
|
53412
53430
|
async function execGit(args, cwd) {
|
|
53413
53431
|
const cmd = `git ${args.join(" ")}`;
|
|
53414
53432
|
log7.debug(`Executing: ${cmd}`);
|
|
53415
|
-
return new Promise((
|
|
53433
|
+
return new Promise((resolve3, reject) => {
|
|
53416
53434
|
const proc = spawn2("git", args, { cwd });
|
|
53417
53435
|
let stdout = "";
|
|
53418
53436
|
let stderr = "";
|
|
@@ -53425,7 +53443,7 @@ async function execGit(args, cwd) {
|
|
|
53425
53443
|
proc.on("close", (code) => {
|
|
53426
53444
|
if (code === 0) {
|
|
53427
53445
|
log7.debug(`${cmd} → success`);
|
|
53428
|
-
|
|
53446
|
+
resolve3(stdout.trim());
|
|
53429
53447
|
} else {
|
|
53430
53448
|
log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
|
|
53431
53449
|
reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
|
|
@@ -53786,20 +53804,20 @@ class CleanupScheduler {
|
|
|
53786
53804
|
if (!this.threadLogsEnabled) {
|
|
53787
53805
|
return 0;
|
|
53788
53806
|
}
|
|
53789
|
-
return new Promise((
|
|
53807
|
+
return new Promise((resolve3) => {
|
|
53790
53808
|
try {
|
|
53791
53809
|
const deleted = cleanupOldLogs(this.logRetentionDays);
|
|
53792
|
-
|
|
53810
|
+
resolve3(deleted);
|
|
53793
53811
|
} catch (err) {
|
|
53794
53812
|
log8.warn(`Log cleanup error: ${err}`);
|
|
53795
|
-
|
|
53813
|
+
resolve3(0);
|
|
53796
53814
|
}
|
|
53797
53815
|
});
|
|
53798
53816
|
}
|
|
53799
53817
|
async cleanupOrphanedWorktrees() {
|
|
53800
53818
|
const worktreesDir = getWorktreesDir();
|
|
53801
53819
|
const result = { cleaned: 0, metadata: 0 };
|
|
53802
|
-
if (!
|
|
53820
|
+
if (!existsSync7(worktreesDir)) {
|
|
53803
53821
|
log8.debug("No worktrees directory exists, nothing to clean");
|
|
53804
53822
|
return result;
|
|
53805
53823
|
}
|
|
@@ -53939,9 +53957,9 @@ function getSessionStatus(session) {
|
|
|
53939
53957
|
// src/claude/cli.ts
|
|
53940
53958
|
import { spawn as spawn3 } from "child_process";
|
|
53941
53959
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
53942
|
-
import { resolve as
|
|
53943
|
-
import { fileURLToPath as
|
|
53944
|
-
import { existsSync as
|
|
53960
|
+
import { resolve as resolve3, dirname as dirname6 } from "path";
|
|
53961
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
53962
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
|
|
53945
53963
|
import { tmpdir } from "os";
|
|
53946
53964
|
import { join as join6 } from "path";
|
|
53947
53965
|
var log9 = createLogger("claude");
|
|
@@ -53987,8 +54005,8 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53987
54005
|
if (!this.statusFilePath)
|
|
53988
54006
|
return null;
|
|
53989
54007
|
try {
|
|
53990
|
-
if (
|
|
53991
|
-
const data =
|
|
54008
|
+
if (existsSync8(this.statusFilePath)) {
|
|
54009
|
+
const data = readFileSync6(this.statusFilePath, "utf8");
|
|
53992
54010
|
this.lastStatusData = JSON.parse(data);
|
|
53993
54011
|
}
|
|
53994
54012
|
} catch (err) {
|
|
@@ -54015,7 +54033,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
54015
54033
|
if (this.statusFilePath) {
|
|
54016
54034
|
unwatchFile(this.statusFilePath);
|
|
54017
54035
|
try {
|
|
54018
|
-
if (
|
|
54036
|
+
if (existsSync8(this.statusFilePath)) {
|
|
54019
54037
|
unlinkSync2(this.statusFilePath);
|
|
54020
54038
|
}
|
|
54021
54039
|
} catch {}
|
|
@@ -54203,7 +54221,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
54203
54221
|
const pid = proc.pid;
|
|
54204
54222
|
this.process = null;
|
|
54205
54223
|
this.log.debug(`Killing Claude process (pid=${pid})`);
|
|
54206
|
-
return new Promise((
|
|
54224
|
+
return new Promise((resolve4) => {
|
|
54207
54225
|
this.log.debug("Sending first SIGINT");
|
|
54208
54226
|
proc.kill("SIGINT");
|
|
54209
54227
|
const secondSigint = setTimeout(() => {
|
|
@@ -54222,7 +54240,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
54222
54240
|
this.log.debug(`Claude process exited (code=${code})`);
|
|
54223
54241
|
clearTimeout(secondSigint);
|
|
54224
54242
|
clearTimeout(forceKillTimeout);
|
|
54225
|
-
|
|
54243
|
+
resolve4();
|
|
54226
54244
|
});
|
|
54227
54245
|
});
|
|
54228
54246
|
}
|
|
@@ -54236,43 +54254,16 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
54236
54254
|
return true;
|
|
54237
54255
|
}
|
|
54238
54256
|
getMcpServerPath() {
|
|
54239
|
-
const __filename2 =
|
|
54240
|
-
const
|
|
54241
|
-
return
|
|
54257
|
+
const __filename2 = fileURLToPath3(import.meta.url);
|
|
54258
|
+
const __dirname4 = dirname6(__filename2);
|
|
54259
|
+
return resolve3(__dirname4, "..", "mcp", "permission-server.js");
|
|
54242
54260
|
}
|
|
54243
54261
|
getStatusLineWriterPath() {
|
|
54244
|
-
const __filename2 =
|
|
54245
|
-
const
|
|
54246
|
-
return
|
|
54247
|
-
}
|
|
54248
|
-
}
|
|
54249
|
-
|
|
54250
|
-
// src/version.ts
|
|
54251
|
-
import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
|
|
54252
|
-
import { dirname as dirname6, resolve as resolve3 } from "path";
|
|
54253
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
54254
|
-
var __dirname3 = dirname6(fileURLToPath3(import.meta.url));
|
|
54255
|
-
function loadPackageJson() {
|
|
54256
|
-
const candidates = [
|
|
54257
|
-
resolve3(__dirname3, "..", "package.json"),
|
|
54258
|
-
resolve3(__dirname3, "..", "..", "package.json"),
|
|
54259
|
-
resolve3(process.cwd(), "package.json")
|
|
54260
|
-
];
|
|
54261
|
-
for (const candidate of candidates) {
|
|
54262
|
-
if (existsSync8(candidate)) {
|
|
54263
|
-
try {
|
|
54264
|
-
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
54265
|
-
if (pkg.name === "claude-threads") {
|
|
54266
|
-
return { version: pkg.version, name: pkg.name };
|
|
54267
|
-
}
|
|
54268
|
-
} catch {}
|
|
54269
|
-
}
|
|
54262
|
+
const __filename2 = fileURLToPath3(import.meta.url);
|
|
54263
|
+
const __dirname4 = dirname6(__filename2);
|
|
54264
|
+
return resolve3(__dirname4, "..", "statusline", "writer.js");
|
|
54270
54265
|
}
|
|
54271
|
-
return { version: "unknown", name: "claude-threads" };
|
|
54272
54266
|
}
|
|
54273
|
-
var pkgInfo = loadPackageJson();
|
|
54274
|
-
var VERSION = pkgInfo.version;
|
|
54275
|
-
var PKG = pkgInfo;
|
|
54276
54267
|
|
|
54277
54268
|
// src/commands/registry.ts
|
|
54278
54269
|
var COMMAND_REGISTRY = [
|
|
@@ -57283,7 +57274,7 @@ class TaskListExecutor extends BaseExecutor {
|
|
|
57283
57274
|
}
|
|
57284
57275
|
async withBumpQueue(fn) {
|
|
57285
57276
|
const prevQueue = this.bumpQueue;
|
|
57286
|
-
let releaseLock;
|
|
57277
|
+
let releaseLock = () => {};
|
|
57287
57278
|
this.bumpQueue = new Promise((resolve4) => {
|
|
57288
57279
|
releaseLock = resolve4;
|
|
57289
57280
|
});
|
|
@@ -60118,9 +60109,7 @@ async function buildStatusBar(sessionCount, config, formatter, platformId) {
|
|
|
60118
60109
|
items.push(formatter.formatCode(`⏸️ Update deferred`));
|
|
60119
60110
|
}
|
|
60120
60111
|
}
|
|
60121
|
-
|
|
60122
|
-
const versionStr = claudeVersion ? `v${VERSION} · CLI ${claudeVersion}` : `v${VERSION}`;
|
|
60123
|
-
items.push(formatter.formatCode(versionStr));
|
|
60112
|
+
items.push(formatter.formatCode(formatVersionString()));
|
|
60124
60113
|
items.push(formatter.formatCode(`${sessionCount}/${config.maxSessions} sessions`));
|
|
60125
60114
|
const permMode = config.skipPermissions ? "⚡ Auto" : "\uD83D\uDD10 Interactive";
|
|
60126
60115
|
items.push(formatter.formatCode(permMode));
|
|
@@ -60852,11 +60841,14 @@ class UsernameAnonymizer {
|
|
|
60852
60841
|
anonymize(username) {
|
|
60853
60842
|
if (!username)
|
|
60854
60843
|
return "[unknown]";
|
|
60855
|
-
|
|
60856
|
-
|
|
60857
|
-
|
|
60844
|
+
const existing = this.usernameMap.get(username);
|
|
60845
|
+
if (existing) {
|
|
60846
|
+
return existing;
|
|
60858
60847
|
}
|
|
60859
|
-
|
|
60848
|
+
this.counter++;
|
|
60849
|
+
const anonymized = `User${this.counter}`;
|
|
60850
|
+
this.usernameMap.set(username, anonymized);
|
|
60851
|
+
return anonymized;
|
|
60860
60852
|
}
|
|
60861
60853
|
}
|
|
60862
60854
|
function formatLogEntries(entries) {
|
|
@@ -64818,8 +64810,7 @@ async function updateSessionHeader(session, ctx) {
|
|
|
64818
64810
|
const permMode = isInteractive ? "\uD83D\uDD10 Interactive" : "⚡ Auto";
|
|
64819
64811
|
const otherParticipants = [...session.sessionAllowedUsers].filter((u) => u !== session.startedBy).map((u) => formatter.formatUserMention(u)).join(", ");
|
|
64820
64812
|
const statusItems = [];
|
|
64821
|
-
const
|
|
64822
|
-
const versionStr = claudeVersion ? `v${VERSION} · CLI ${claudeVersion}` : `v${VERSION}`;
|
|
64813
|
+
const versionStr = formatVersionString();
|
|
64823
64814
|
statusItems.push(formatter.formatCode(versionStr));
|
|
64824
64815
|
if (session.usageStats) {
|
|
64825
64816
|
const stats = session.usageStats;
|