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 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.forceCloseConnection();
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
- log.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
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
- log.error(`Reconnection failed: ${err}`);
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 hasFileIds = post.file_ids && post.file_ids.length > 0;
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 ${post.file_ids.length} file(s), fetching metadata`);
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 post.file_ids) {
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((resolve2) => setTimeout(resolve2, delay));
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((resolve2, reject) => {
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
- resolve2();
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((resolve2) => setTimeout(resolve2, waitTime));
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((resolve2) => setTimeout(resolve2, this.rateLimitDelay));
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((resolve2, reject) => {
52013
+ return new Promise((resolve3, reject) => {
51959
52014
  let settled = false;
51960
52015
  const doResolve = () => {
51961
52016
  if (!settled) {
51962
52017
  settled = true;
51963
- resolve2();
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((resolve2) => {
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
- resolve2(null);
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
- resolve2({
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
- resolve2(null);
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
- resolve2(null);
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((resolve2) => {
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
- resolve2(null);
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
- resolve2({
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
- resolve2(null);
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
- resolve2(null);
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
- resolve2(null);
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
- resolve2(null);
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 existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "fs";
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 (!existsSync4(this.configDir)) {
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 (!existsSync4(this.sessionsFile)) {
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(readFileSync3(this.sessionsFile, "utf-8"));
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 (!existsSync4(this.sessionsFile)) {
53128
+ if (!existsSync5(this.sessionsFile)) {
53111
53129
  return { version: STORE_VERSION, sessions: {} };
53112
53130
  }
53113
53131
  try {
53114
- return JSON.parse(readFileSync3(this.sessionsFile, "utf-8"));
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 existsSync6 } from "fs";
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 existsSync5, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync4 } from "fs";
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 dirname3 } from "path";
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 = dirname3(this.logPath);
53161
- if (!existsSync5(dir)) {
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 (!existsSync5(LOGS_BASE_DIR)) {
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 (!existsSync5(logPath)) {
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 = readFileSync4(logPath, "utf8");
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((resolve2, reject) => {
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
- resolve2(stdout.trim());
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((resolve2) => {
53807
+ return new Promise((resolve3) => {
53790
53808
  try {
53791
53809
  const deleted = cleanupOldLogs(this.logRetentionDays);
53792
- resolve2(deleted);
53810
+ resolve3(deleted);
53793
53811
  } catch (err) {
53794
53812
  log8.warn(`Log cleanup error: ${err}`);
53795
- resolve2(0);
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 (!existsSync6(worktreesDir)) {
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 resolve2, dirname as dirname5 } from "path";
53943
- import { fileURLToPath as fileURLToPath2 } from "url";
53944
- import { existsSync as existsSync7, readFileSync as readFileSync5, watchFile, unwatchFile, unlinkSync as unlinkSync2, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
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 (existsSync7(this.statusFilePath)) {
53991
- const data = readFileSync5(this.statusFilePath, "utf8");
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 (existsSync7(this.statusFilePath)) {
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((resolve3) => {
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
- resolve3();
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 = fileURLToPath2(import.meta.url);
54240
- const __dirname3 = dirname5(__filename2);
54241
- return resolve2(__dirname3, "..", "mcp", "permission-server.js");
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 = fileURLToPath2(import.meta.url);
54245
- const __dirname3 = dirname5(__filename2);
54246
- return resolve2(__dirname3, "..", "statusline", "writer.js");
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
- const claudeVersion = getClaudeCliVersion().version;
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
- if (!this.usernameMap.has(username)) {
60856
- this.counter++;
60857
- this.usernameMap.set(username, `User${this.counter}`);
60844
+ const existing = this.usernameMap.get(username);
60845
+ if (existing) {
60846
+ return existing;
60858
60847
  }
60859
- return this.usernameMap.get(username);
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 claudeVersion = getClaudeCliVersion().version;
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;