claude-threads 0.31.2 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.js +142 -42
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.32.0] - 2026-01-03
11
+
12
+ ### Added
13
+ - **Claude CLI version check** - Validates Claude CLI version at startup and exits if incompatible (bypass with `--skip-version-check`). Compatible versions: `>=2.0.74 <=2.0.76`. Version is displayed in terminal startup output, sticky channel message, and session headers.
14
+
15
+ ## [0.31.3] - 2026-01-03
16
+
17
+ ### Fixed
18
+ - **Clean up stale browser bridge sockets** - Removes stale `claude-mcp-browser-bridge-*` socket files from temp directory before starting Claude CLI. This works around a Claude CLI bug where it tries to `fs.watch()` existing socket files, which fails with `EOPNOTSUPP`. The socket files are left over from previous Chrome integration sessions.
19
+
10
20
  ## [0.31.2] - 2026-01-03
11
21
 
12
22
  ### Fixed
package/dist/index.js CHANGED
@@ -8933,7 +8933,7 @@ var require_minimist = __commonJS((exports, module) => {
8933
8933
  // node_modules/rc/index.js
8934
8934
  var require_rc = __commonJS((exports, module) => {
8935
8935
  var cc = require_utils();
8936
- var join2 = __require("path").join;
8936
+ var join3 = __require("path").join;
8937
8937
  var deepExtend = require_deep_extend();
8938
8938
  var etc = "/etc";
8939
8939
  var win = process.platform === "win32";
@@ -8959,15 +8959,15 @@ var require_rc = __commonJS((exports, module) => {
8959
8959
  }
8960
8960
  if (!win)
8961
8961
  [
8962
- join2(etc, name, "config"),
8963
- join2(etc, name + "rc")
8962
+ join3(etc, name, "config"),
8963
+ join3(etc, name + "rc")
8964
8964
  ].forEach(addConfigFile);
8965
8965
  if (home)
8966
8966
  [
8967
- join2(home, ".config", name, "config"),
8968
- join2(home, ".config", name),
8969
- join2(home, "." + name, "config"),
8970
- join2(home, "." + name + "rc")
8967
+ join3(home, ".config", name, "config"),
8968
+ join3(home, ".config", name),
8969
+ join3(home, "." + name, "config"),
8970
+ join3(home, "." + name + "rc")
8971
8971
  ].forEach(addConfigFile);
8972
8972
  addConfigFile(cc.find("." + name + "rc"));
8973
8973
  if (env3.config)
@@ -12879,6 +12879,8 @@ var dim = (s) => `${colors.dim}${s}${colors.reset}`;
12879
12879
  var bold = (s) => `${colors.bold}${s}${colors.reset}`;
12880
12880
  var cyan = (s) => `${colors.cyan}${s}${colors.reset}`;
12881
12881
  var green = (s) => `${colors.green}${s}${colors.reset}`;
12882
+ var red = (s) => `${colors.red}${s}${colors.reset}`;
12883
+ var yellow = (s) => `${colors.yellow}${s}${colors.reset}`;
12882
12884
  function log(emoji, message) {
12883
12885
  console.log(` ${emoji} ${message}`);
12884
12886
  }
@@ -15729,8 +15731,30 @@ import { spawn } from "child_process";
15729
15731
  import { EventEmitter as EventEmitter2 } from "events";
15730
15732
  import { resolve as resolve2, dirname as dirname2 } from "path";
15731
15733
  import { fileURLToPath } from "url";
15732
- import { existsSync as existsSync4, readFileSync as readFileSync4, watchFile, unwatchFile, unlinkSync } from "fs";
15734
+ import { existsSync as existsSync4, readFileSync as readFileSync4, watchFile, unwatchFile, unlinkSync, statSync, readdirSync } from "fs";
15735
+ import { tmpdir } from "os";
15736
+ import { join as join2 } from "path";
15733
15737
  var log8 = createLogger("claude");
15738
+ function cleanupBrowserBridgeSockets() {
15739
+ try {
15740
+ const tempDir = tmpdir();
15741
+ const files = readdirSync(tempDir);
15742
+ for (const file of files) {
15743
+ if (file.startsWith("claude-mcp-browser-bridge-")) {
15744
+ const filePath = join2(tempDir, file);
15745
+ try {
15746
+ const stats = statSync(filePath);
15747
+ if (stats.isSocket()) {
15748
+ unlinkSync(filePath);
15749
+ log8.debug(`Removed stale browser bridge socket: ${file}`);
15750
+ }
15751
+ } catch {}
15752
+ }
15753
+ }
15754
+ } catch (err) {
15755
+ log8.debug(`Browser bridge cleanup failed: ${err}`);
15756
+ }
15757
+ }
15734
15758
 
15735
15759
  class ClaudeCli extends EventEmitter2 {
15736
15760
  process = null;
@@ -15784,6 +15808,7 @@ class ClaudeCli extends EventEmitter2 {
15784
15808
  if (this.process)
15785
15809
  throw new Error("Already running");
15786
15810
  this.stderrBuffer = "";
15811
+ cleanupBrowserBridgeSockets();
15787
15812
  const claudePath = process.env.CLAUDE_PATH || "claude";
15788
15813
  const args = [
15789
15814
  "--input-format",
@@ -15969,7 +15994,7 @@ class ClaudeCli extends EventEmitter2 {
15969
15994
  // src/session/commands.ts
15970
15995
  import { randomUUID as randomUUID2 } from "crypto";
15971
15996
  import { resolve as resolve5 } from "path";
15972
- import { existsSync as existsSync7, statSync } from "fs";
15997
+ import { existsSync as existsSync7, statSync as statSync2 } from "fs";
15973
15998
 
15974
15999
  // node_modules/update-notifier/update-notifier.js
15975
16000
  import process10 from "process";
@@ -16698,7 +16723,7 @@ class Configstore {
16698
16723
  var ANSI_BACKGROUND_OFFSET = 10;
16699
16724
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
16700
16725
  var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
16701
- var wrapAnsi16m = (offset = 0) => (red, green2, blue) => `\x1B[${38 + offset};2;${red};${green2};${blue}m`;
16726
+ var wrapAnsi16m = (offset = 0) => (red2, green2, blue) => `\x1B[${38 + offset};2;${red2};${green2};${blue}m`;
16702
16727
  var styles = {
16703
16728
  modifier: {
16704
16729
  reset: [0, 0],
@@ -16786,17 +16811,17 @@ function assembleStyles() {
16786
16811
  styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
16787
16812
  Object.defineProperties(styles, {
16788
16813
  rgbToAnsi256: {
16789
- value(red, green2, blue) {
16790
- if (red === green2 && green2 === blue) {
16791
- if (red < 8) {
16814
+ value(red2, green2, blue) {
16815
+ if (red2 === green2 && green2 === blue) {
16816
+ if (red2 < 8) {
16792
16817
  return 16;
16793
16818
  }
16794
- if (red > 248) {
16819
+ if (red2 > 248) {
16795
16820
  return 231;
16796
16821
  }
16797
- return Math.round((red - 8) / 247 * 24) + 232;
16822
+ return Math.round((red2 - 8) / 247 * 24) + 232;
16798
16823
  }
16799
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green2 / 255 * 5) + Math.round(blue / 255 * 5);
16824
+ return 16 + 36 * Math.round(red2 / 255 * 5) + 6 * Math.round(green2 / 255 * 5) + Math.round(blue / 255 * 5);
16800
16825
  },
16801
16826
  enumerable: false
16802
16827
  },
@@ -16831,25 +16856,25 @@ function assembleStyles() {
16831
16856
  if (code < 16) {
16832
16857
  return 90 + (code - 8);
16833
16858
  }
16834
- let red;
16859
+ let red2;
16835
16860
  let green2;
16836
16861
  let blue;
16837
16862
  if (code >= 232) {
16838
- red = ((code - 232) * 10 + 8) / 255;
16839
- green2 = red;
16840
- blue = red;
16863
+ red2 = ((code - 232) * 10 + 8) / 255;
16864
+ green2 = red2;
16865
+ blue = red2;
16841
16866
  } else {
16842
16867
  code -= 16;
16843
16868
  const remainder = code % 36;
16844
- red = Math.floor(code / 36) / 5;
16869
+ red2 = Math.floor(code / 36) / 5;
16845
16870
  green2 = Math.floor(remainder / 6) / 5;
16846
16871
  blue = remainder % 6 / 5;
16847
16872
  }
16848
- const value = Math.max(red, green2, blue) * 2;
16873
+ const value = Math.max(red2, green2, blue) * 2;
16849
16874
  if (value === 0) {
16850
16875
  return 30;
16851
16876
  }
16852
- let result = 30 + (Math.round(blue) << 2 | Math.round(green2) << 1 | Math.round(red));
16877
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green2) << 1 | Math.round(red2));
16853
16878
  if (value === 2) {
16854
16879
  result += 60;
16855
16880
  }
@@ -16858,7 +16883,7 @@ function assembleStyles() {
16858
16883
  enumerable: false
16859
16884
  },
16860
16885
  rgbToAnsi: {
16861
- value: (red, green2, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green2, blue)),
16886
+ value: (red2, green2, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red2, green2, blue)),
16862
16887
  enumerable: false
16863
16888
  },
16864
16889
  hexToAnsi: {
@@ -18425,7 +18450,7 @@ var import_ansi_align = __toESM(require_ansi_align(), 1);
18425
18450
  var ANSI_BACKGROUND_OFFSET2 = 10;
18426
18451
  var wrapAnsi162 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
18427
18452
  var wrapAnsi2562 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
18428
- var wrapAnsi16m2 = (offset = 0) => (red, green2, blue) => `\x1B[${38 + offset};2;${red};${green2};${blue}m`;
18453
+ var wrapAnsi16m2 = (offset = 0) => (red2, green2, blue) => `\x1B[${38 + offset};2;${red2};${green2};${blue}m`;
18429
18454
  var styles3 = {
18430
18455
  modifier: {
18431
18456
  reset: [0, 0],
@@ -18513,17 +18538,17 @@ function assembleStyles2() {
18513
18538
  styles3.bgColor.ansi16m = wrapAnsi16m2(ANSI_BACKGROUND_OFFSET2);
18514
18539
  Object.defineProperties(styles3, {
18515
18540
  rgbToAnsi256: {
18516
- value(red, green2, blue) {
18517
- if (red === green2 && green2 === blue) {
18518
- if (red < 8) {
18541
+ value(red2, green2, blue) {
18542
+ if (red2 === green2 && green2 === blue) {
18543
+ if (red2 < 8) {
18519
18544
  return 16;
18520
18545
  }
18521
- if (red > 248) {
18546
+ if (red2 > 248) {
18522
18547
  return 231;
18523
18548
  }
18524
- return Math.round((red - 8) / 247 * 24) + 232;
18549
+ return Math.round((red2 - 8) / 247 * 24) + 232;
18525
18550
  }
18526
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green2 / 255 * 5) + Math.round(blue / 255 * 5);
18551
+ return 16 + 36 * Math.round(red2 / 255 * 5) + 6 * Math.round(green2 / 255 * 5) + Math.round(blue / 255 * 5);
18527
18552
  },
18528
18553
  enumerable: false
18529
18554
  },
@@ -18558,25 +18583,25 @@ function assembleStyles2() {
18558
18583
  if (code < 16) {
18559
18584
  return 90 + (code - 8);
18560
18585
  }
18561
- let red;
18586
+ let red2;
18562
18587
  let green2;
18563
18588
  let blue;
18564
18589
  if (code >= 232) {
18565
- red = ((code - 232) * 10 + 8) / 255;
18566
- green2 = red;
18567
- blue = red;
18590
+ red2 = ((code - 232) * 10 + 8) / 255;
18591
+ green2 = red2;
18592
+ blue = red2;
18568
18593
  } else {
18569
18594
  code -= 16;
18570
18595
  const remainder = code % 36;
18571
- red = Math.floor(code / 36) / 5;
18596
+ red2 = Math.floor(code / 36) / 5;
18572
18597
  green2 = Math.floor(remainder / 6) / 5;
18573
18598
  blue = remainder % 6 / 5;
18574
18599
  }
18575
- const value = Math.max(red, green2, blue) * 2;
18600
+ const value = Math.max(red2, green2, blue) * 2;
18576
18601
  if (value === 0) {
18577
18602
  return 30;
18578
18603
  }
18579
- let result = 30 + (Math.round(blue) << 2 | Math.round(green2) << 1 | Math.round(red));
18604
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green2) << 1 | Math.round(red2));
18580
18605
  if (value === 2) {
18581
18606
  result += 60;
18582
18607
  }
@@ -18585,7 +18610,7 @@ function assembleStyles2() {
18585
18610
  enumerable: false
18586
18611
  },
18587
18612
  rgbToAnsi: {
18588
- value: (red, green2, blue) => styles3.ansi256ToAnsi(styles3.rgbToAnsi256(red, green2, blue)),
18613
+ value: (red2, green2, blue) => styles3.ansi256ToAnsi(styles3.rgbToAnsi256(red2, green2, blue)),
18589
18614
  enumerable: false
18590
18615
  },
18591
18616
  hexToAnsi: {
@@ -19918,6 +19943,58 @@ function isValidBranchName(name) {
19918
19943
  return true;
19919
19944
  }
19920
19945
 
19946
+ // src/claude/version-check.ts
19947
+ var import_semver3 = __toESM(require_semver2(), 1);
19948
+ import { execSync } from "child_process";
19949
+ var CLAUDE_CLI_VERSION_RANGE = ">=2.0.74 <=2.0.76";
19950
+ function getClaudeCliVersion() {
19951
+ const claudePath = process.env.CLAUDE_PATH || "claude";
19952
+ try {
19953
+ const output = execSync(`${claudePath} --version`, {
19954
+ encoding: "utf8",
19955
+ timeout: 5000,
19956
+ stdio: ["pipe", "pipe", "pipe"]
19957
+ }).trim();
19958
+ const match = output.match(/^([\d.]+)/);
19959
+ return match ? match[1] : null;
19960
+ } catch {
19961
+ return null;
19962
+ }
19963
+ }
19964
+ function isVersionCompatible(version) {
19965
+ const semverVersion = import_semver3.coerce(version);
19966
+ if (!semverVersion)
19967
+ return false;
19968
+ return import_semver3.satisfies(semverVersion, CLAUDE_CLI_VERSION_RANGE);
19969
+ }
19970
+ function validateClaudeCli() {
19971
+ const version = getClaudeCliVersion();
19972
+ if (!version) {
19973
+ return {
19974
+ installed: false,
19975
+ version: null,
19976
+ compatible: false,
19977
+ message: "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code"
19978
+ };
19979
+ }
19980
+ const compatible = isVersionCompatible(version);
19981
+ if (!compatible) {
19982
+ return {
19983
+ installed: true,
19984
+ version,
19985
+ compatible: false,
19986
+ message: `Claude CLI version ${version} is not compatible. Required: ${CLAUDE_CLI_VERSION_RANGE}
19987
+ ` + `Install a compatible version: npm install -g @anthropic-ai/claude-code@2.0.76`
19988
+ };
19989
+ }
19990
+ return {
19991
+ installed: true,
19992
+ version,
19993
+ compatible: true,
19994
+ message: `Claude CLI ${version} \u2713`
19995
+ };
19996
+ }
19997
+
19921
19998
  // src/session/commands.ts
19922
19999
  var log9 = createLogger("commands");
19923
20000
  async function restartClaudeSession(session, cliOptions, ctx, actionName) {
@@ -19993,7 +20070,7 @@ async function changeDirectory(session, newDir, username, ctx) {
19993
20070
  await postError(session, `Directory does not exist: \`${newDir}\``);
19994
20071
  return;
19995
20072
  }
19996
- if (!statSync(absoluteDir).isDirectory()) {
20073
+ if (!statSync2(absoluteDir).isDirectory()) {
19997
20074
  await postError(session, `Not a directory: \`${newDir}\``);
19998
20075
  return;
19999
20076
  }
@@ -20175,6 +20252,10 @@ async function updateSessionHeader(session, ctx) {
20175
20252
  if (otherParticipants) {
20176
20253
  rows.push(`| \uD83D\uDC65 **Participants** | ${otherParticipants} |`);
20177
20254
  }
20255
+ const claudeVersion = getClaudeCliVersion();
20256
+ if (claudeVersion) {
20257
+ rows.push(`| \uD83E\uDD16 **Claude CLI** | \`${claudeVersion}\` |`);
20258
+ }
20178
20259
  rows.push(`| \uD83C\uDD94 **Session ID** | \`${session.claudeSessionId.substring(0, 8)}\` |`);
20179
20260
  const updateInfo = getUpdateInfo();
20180
20261
  const updateNotice = updateInfo ? `
@@ -21304,7 +21385,9 @@ function formatHistoryEntry(session) {
21304
21385
  }
21305
21386
  async function buildStatusBar(sessionCount, config) {
21306
21387
  const items = [];
21307
- items.push(`\`v${VERSION}\``);
21388
+ const claudeVersion = getClaudeCliVersion();
21389
+ const versionStr = claudeVersion ? `v${VERSION} \xB7 CLI ${claudeVersion}` : `v${VERSION}`;
21390
+ items.push(`\`${versionStr}\``);
21308
21391
  items.push(`\`${sessionCount}/${config.maxSessions} sessions\``);
21309
21392
  const permMode = config.skipPermissions ? "\u26A1 Auto" : "\uD83D\uDD10 Interactive";
21310
21393
  items.push(`\`${permMode}\``);
@@ -22103,7 +22186,7 @@ class SessionManager {
22103
22186
  }
22104
22187
  }
22105
22188
  // src/index.ts
22106
- program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--skip-permissions", "Skip interactive permission prompts").option("--no-skip-permissions", "Enable interactive permission prompts (override env)").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").parse();
22189
+ program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--skip-permissions", "Skip interactive permission prompts").option("--no-skip-permissions", "Enable interactive permission prompts (override env)").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").parse();
22107
22190
  var opts = program.opts();
22108
22191
  function hasRequiredCliArgs(args) {
22109
22192
  return !!(args.url && args.token && args.channel);
@@ -22151,11 +22234,21 @@ async function main() {
22151
22234
  }
22152
22235
  const config = newConfig;
22153
22236
  printLogo();
22237
+ const claudeValidation = validateClaudeCli();
22154
22238
  console.log(dim(` v${VERSION}`));
22155
22239
  console.log("");
22156
22240
  console.log(` \uD83D\uDCC2 ${cyan(workingDir)}`);
22157
22241
  console.log(` \uD83D\uDCAC ${cyan("@" + platformConfig.botName)}`);
22158
22242
  console.log(` \uD83C\uDF10 ${dim(platformConfig.url)}`);
22243
+ if (claudeValidation.installed) {
22244
+ if (claudeValidation.compatible) {
22245
+ console.log(` \uD83E\uDD16 ${dim(`Claude CLI ${claudeValidation.version}`)}`);
22246
+ } else {
22247
+ console.log(` \uD83E\uDD16 ${yellow(`Claude CLI ${claudeValidation.version} (incompatible)`)}`);
22248
+ }
22249
+ } else {
22250
+ console.log(` \uD83E\uDD16 ${red("Claude CLI not found")}`);
22251
+ }
22159
22252
  if (platformConfig.skipPermissions) {
22160
22253
  console.log(` \u26A0\uFE0F ${dim("Permissions disabled")}`);
22161
22254
  } else {
@@ -22168,6 +22261,13 @@ async function main() {
22168
22261
  console.log(` \u2615 ${dim("Keep-alive enabled")}`);
22169
22262
  }
22170
22263
  console.log("");
22264
+ if (!claudeValidation.compatible && !opts.skipVersionCheck) {
22265
+ console.error(red(` \u274C ${claudeValidation.message}`));
22266
+ console.error("");
22267
+ console.error(dim(` Use --skip-version-check to bypass this check (not recommended)`));
22268
+ console.error("");
22269
+ process.exit(1);
22270
+ }
22171
22271
  const mattermost = new MattermostClient(platformConfig);
22172
22272
  const session = new SessionManager(workingDir, platformConfig.skipPermissions, config.chrome, config.worktreeMode);
22173
22273
  session.addPlatform(platformConfig.id, mattermost);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.31.2",
3
+ "version": "0.32.0",
4
4
  "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",