claude-threads 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.45.0] - 2026-01-07
11
+
12
+ ### Added
13
+ - **Update modal in CLI UI** - Press `u` to open a modal showing update status, changelog, and options to apply or defer updates
14
+ - **Worktree path shortening** - Tool output shows shortened worktree paths as `[branch]/path` instead of full paths for readability
15
+
16
+ ### Fixed
17
+ - **Duplicate task list posts in Slack** - Fixed race condition that caused double task list messages
18
+ - **Worktree prompt cleanup** - Remove ❌ reaction from worktree prompts after user responds
19
+ - **Streaming message failures** - Handle `updatePost` failures gracefully with automatic recovery
20
+
21
+ ### Changed
22
+ - **`.claude-threads-meta.json` added to .gitignore** - Session metadata files are no longer tracked
23
+
10
24
  ## [0.44.0] - 2026-01-07
11
25
 
12
26
  ### Added
package/dist/index.js CHANGED
@@ -45274,12 +45274,11 @@ async function flush(session, registerPost) {
45274
45274
  if (breakInfo && breakInfo.position < content.length) {
45275
45275
  breakPoint = breakInfo.position;
45276
45276
  } else {
45277
- const result = await withErrorHandling(() => session.platform.updatePost(session.currentPostId, content), { action: "Update post (no breakpoint)", session });
45278
- if (result === undefined) {
45279
- sessionLog(session).warn("Update failed (no breakpoint), starting new message");
45277
+ try {
45278
+ await session.platform.updatePost(session.currentPostId, content);
45279
+ } catch {
45280
+ sessionLog(session).debug("Update failed (no breakpoint), will create new post on next flush");
45280
45281
  session.currentPostId = null;
45281
- session.currentPostContent = "";
45282
- return flush(session, registerPost);
45283
45282
  }
45284
45283
  return;
45285
45284
  }
@@ -45295,20 +45294,18 @@ async function flush(session, registerPost) {
45295
45294
  const firstPartWithMarker = remainder ? firstPart + `
45296
45295
 
45297
45296
  ` + formatter2.formatItalic("... (continued below)") : firstPart;
45298
- const updateResult = await withErrorHandling(() => session.platform.updatePost(session.currentPostId, firstPartWithMarker), { action: "Update post with first part", session });
45299
- if (updateResult === undefined) {
45300
- sessionLog(session).warn("Update failed during split, including content in new message");
45301
- session.pendingContent = content;
45302
- } else {
45303
- session.pendingContent = remainder;
45297
+ try {
45298
+ await session.platform.updatePost(session.currentPostId, firstPartWithMarker);
45299
+ } catch {
45300
+ sessionLog(session).debug("Update failed during split, continuing with new post");
45304
45301
  }
45305
45302
  session.currentPostId = null;
45306
- const contentToPost = session.pendingContent;
45307
- if (contentToPost) {
45308
- const continuationMarker = updateResult !== undefined ? formatter2.formatItalic("(continued)") + `
45303
+ session.pendingContent = remainder;
45304
+ if (remainder) {
45305
+ const continuationMarker = formatter2.formatItalic("(continued)");
45306
+ const continuationContent = continuationMarker + `
45309
45307
 
45310
- ` : "";
45311
- const continuationContent = continuationMarker + contentToPost;
45308
+ ` + remainder;
45312
45309
  const hasActiveTasks = session.tasksPostId && session.lastTasksContent && !session.tasksCompleted;
45313
45310
  if (hasActiveTasks) {
45314
45311
  const postId = await bumpTasksToBottomWithContent(session, continuationContent, registerPost);
@@ -45332,12 +45329,11 @@ async function flush(session, registerPost) {
45332
45329
  ` + formatter2.formatItalic("... (truncated)");
45333
45330
  }
45334
45331
  if (session.currentPostId) {
45335
- const result = await withErrorHandling(() => session.platform.updatePost(session.currentPostId, content), { action: "Update current post", session });
45336
- if (result === undefined) {
45337
- sessionLog(session).warn("Update failed, starting new message");
45332
+ try {
45333
+ await session.platform.updatePost(session.currentPostId, content);
45334
+ } catch {
45335
+ sessionLog(session).debug("Update failed, will create new post on next flush");
45338
45336
  session.currentPostId = null;
45339
- session.currentPostContent = "";
45340
- return flush(session, registerPost);
45341
45337
  }
45342
45338
  } else {
45343
45339
  const hasActiveTasks = session.tasksPostId && session.lastTasksContent && !session.tasksCompleted;
@@ -45981,15 +45977,22 @@ class ArrayDiff extends Diff {
45981
45977
  var arrayDiff = new ArrayDiff;
45982
45978
 
45983
45979
  // src/utils/tool-formatter.ts
45984
- var DEFAULT_OPTIONS = {
45985
- detailed: false,
45986
- maxCommandLength: 50,
45987
- maxPathLength: 60,
45988
- maxPreviewLines: 20
45989
- };
45990
- function shortenPath(path2, homeDir) {
45980
+ var DEFAULT_DETAILED = false;
45981
+ var DEFAULT_MAX_COMMAND_LENGTH = 50;
45982
+ var DEFAULT_MAX_PREVIEW_LINES = 20;
45983
+ function shortenPath(path2, homeDir, worktreeInfo) {
45991
45984
  if (!path2)
45992
45985
  return "";
45986
+ if (worktreeInfo?.path && worktreeInfo?.branch) {
45987
+ const worktreePath = worktreeInfo.path.endsWith("/") ? worktreeInfo.path : worktreeInfo.path + "/";
45988
+ if (path2.startsWith(worktreePath)) {
45989
+ const relativePath = path2.slice(worktreePath.length);
45990
+ return `[${worktreeInfo.branch}]/${relativePath}`;
45991
+ }
45992
+ if (path2 === worktreeInfo.path) {
45993
+ return `[${worktreeInfo.branch}]/`;
45994
+ }
45995
+ }
45993
45996
  const home = homeDir ?? process.env.HOME ?? "";
45994
45997
  if (home && path2.startsWith(home)) {
45995
45998
  return "~" + path2.slice(home.length);
@@ -46008,8 +46011,10 @@ function parseMcpToolName(toolName) {
46008
46011
  };
46009
46012
  }
46010
46013
  function formatToolUse(toolName, input, formatter, options = {}) {
46011
- const opts = { ...DEFAULT_OPTIONS, ...options };
46012
- const short = (p) => shortenPath(p);
46014
+ const detailed = options.detailed ?? DEFAULT_DETAILED;
46015
+ const maxCommandLength = options.maxCommandLength ?? DEFAULT_MAX_COMMAND_LENGTH;
46016
+ const maxPreviewLines = options.maxPreviewLines ?? DEFAULT_MAX_PREVIEW_LINES;
46017
+ const short = (p) => shortenPath(p, undefined, options.worktreeInfo);
46013
46018
  switch (toolName) {
46014
46019
  case "Read":
46015
46020
  return `\uD83D\uDCC4 ${formatter.formatBold("Read")} ${formatter.formatCode(short(input.file_path))}`;
@@ -46017,9 +46022,9 @@ function formatToolUse(toolName, input, formatter, options = {}) {
46017
46022
  const filePath = short(input.file_path);
46018
46023
  const oldStr = input.old_string || "";
46019
46024
  const newStr = input.new_string || "";
46020
- if (opts.detailed && (oldStr || newStr)) {
46025
+ if (detailed && (oldStr || newStr)) {
46021
46026
  const changes = diffLines(oldStr, newStr);
46022
- const maxLines = opts.maxPreviewLines;
46027
+ const maxLines = maxPreviewLines;
46023
46028
  let lineCount = 0;
46024
46029
  const diffLines2 = [];
46025
46030
  for (const change of changes) {
@@ -46064,7 +46069,7 @@ ${formatter.formatCodeBlock(diffLines2.join(`
46064
46069
  const lines = content.split(`
46065
46070
  `);
46066
46071
  const lineCount = lines.length;
46067
- if (opts.detailed && content && lineCount > 0) {
46072
+ if (detailed && content && lineCount > 0) {
46068
46073
  const maxLines = 6;
46069
46074
  const previewLines = lines.slice(0, maxLines);
46070
46075
  let preview = `\uD83D\uDCDD ${formatter.formatBold("Write")} ${formatter.formatCode(filePath)} ${formatter.formatItalic(`(${lineCount} lines)`)}
@@ -46082,8 +46087,8 @@ ${formatter.formatCodeBlock(diffLines2.join(`
46082
46087
  return `\uD83D\uDCDD ${formatter.formatBold("Write")} ${formatter.formatCode(filePath)}`;
46083
46088
  }
46084
46089
  case "Bash": {
46085
- const cmd = (input.command || "").substring(0, opts.maxCommandLength);
46086
- const truncated = cmd.length >= opts.maxCommandLength;
46090
+ const cmd = (input.command || "").substring(0, maxCommandLength);
46091
+ const truncated = cmd.length >= maxCommandLength;
46087
46092
  return `\uD83D\uDCBB ${formatter.formatBold("Bash")} ${formatter.formatCode(cmd + (truncated ? "..." : ""))}`;
46088
46093
  }
46089
46094
  case "Glob":
@@ -46373,7 +46378,8 @@ function formatEvent(session, e, ctx) {
46373
46378
  if (text)
46374
46379
  parts.push(text);
46375
46380
  } else if (block.type === "tool_use" && block.name) {
46376
- const formatted = formatToolUse(block.name, block.input || {}, session.platform.getFormatter(), { detailed: true });
46381
+ const worktreeInfo = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
46382
+ const formatted = formatToolUse(block.name, block.input || {}, session.platform.getFormatter(), { detailed: true, worktreeInfo });
46377
46383
  if (formatted)
46378
46384
  parts.push(formatted);
46379
46385
  } else if (block.type === "thinking" && block.thinking) {
@@ -46401,7 +46407,8 @@ function formatEvent(session, e, ctx) {
46401
46407
  if (tool.id) {
46402
46408
  session.activeToolStarts.set(tool.id, Date.now());
46403
46409
  }
46404
- return formatToolUse(tool.name, tool.input || {}, session.platform.getFormatter(), { detailed: true }) || null;
46410
+ const worktreeInfo = session.worktreeInfo ? { path: session.worktreeInfo.worktreePath, branch: session.worktreeInfo.branch } : undefined;
46411
+ return formatToolUse(tool.name, tool.input || {}, session.platform.getFormatter(), { detailed: true, worktreeInfo }) || null;
46405
46412
  }
46406
46413
  case "tool_result": {
46407
46414
  const result = e.tool_result;
@@ -46477,6 +46484,9 @@ async function handleExitPlanMode(session, toolUseId, ctx) {
46477
46484
  ctx.ops.stopTyping(session);
46478
46485
  }
46479
46486
  async function handleTodoWrite(session, input, ctx) {
46487
+ if (session.taskListCreationPromise) {
46488
+ await session.taskListCreationPromise;
46489
+ }
46480
46490
  const todos = input.todos;
46481
46491
  if (!todos || todos.length === 0) {
46482
46492
  session.tasksCompleted = true;
@@ -46558,12 +46568,21 @@ async function handleTodoWrite(session, input, ctx) {
46558
46568
  if (existingTasksPostId) {
46559
46569
  await withErrorHandling(() => session.platform.updatePost(existingTasksPostId, displayMessage), { action: "Update tasks", session });
46560
46570
  } else {
46561
- const post = await withErrorHandling(() => session.platform.createInteractivePost(displayMessage, [TASK_TOGGLE_EMOJIS[0]], session.threadId), { action: "Create tasks post", session });
46562
- if (post) {
46563
- session.tasksPostId = post.id;
46564
- ctx.ops.registerPost(post.id, session.threadId);
46565
- updateLastMessage(session, post);
46566
- await session.platform.pinPost(post.id).catch(() => {});
46571
+ let resolveCreation;
46572
+ session.taskListCreationPromise = new Promise((resolve2) => {
46573
+ resolveCreation = resolve2;
46574
+ });
46575
+ try {
46576
+ const post = await withErrorHandling(() => session.platform.createInteractivePost(displayMessage, [TASK_TOGGLE_EMOJIS[0]], session.threadId), { action: "Create tasks post", session });
46577
+ if (post) {
46578
+ session.tasksPostId = post.id;
46579
+ ctx.ops.registerPost(post.id, session.threadId);
46580
+ updateLastMessage(session, post);
46581
+ await session.platform.pinPost(post.id).catch(() => {});
46582
+ }
46583
+ } finally {
46584
+ resolveCreation();
46585
+ session.taskListCreationPromise = undefined;
46567
46586
  }
46568
46587
  }
46569
46588
  ctx.ops.updateStickyMessage().catch(() => {});
@@ -52250,6 +52269,7 @@ async function handleWorktreeSkip(session, username, persistSession, offerContex
52250
52269
  const promptPostId = session.worktreePromptPostId;
52251
52270
  if (promptPostId) {
52252
52271
  await withErrorHandling(() => session.platform.updatePost(promptPostId, `\u2705 Continuing in main repo (skipped by @${username})`), { action: "Update worktree prompt", session });
52272
+ await withErrorHandling(() => session.platform.removeReaction(promptPostId, "x"), { action: "Remove x reaction from worktree prompt", session });
52253
52273
  }
52254
52274
  session.pendingWorktreePrompt = false;
52255
52275
  session.worktreePromptPostId = undefined;
@@ -52284,6 +52304,7 @@ async function createAndSwitchToWorktree(session, branch, username, options) {
52284
52304
  const worktreePromptId = session.worktreePromptPostId;
52285
52305
  if (worktreePromptId) {
52286
52306
  await withErrorHandling(() => session.platform.updatePost(worktreePromptId, `\u2705 Joining existing worktree for ${fmt.formatCode(branch)}`), { action: "Update worktree prompt", session });
52307
+ await withErrorHandling(() => session.platform.removeReaction(worktreePromptId, "x"), { action: "Remove x reaction from worktree prompt", session });
52287
52308
  }
52288
52309
  const queuedPrompt = session.queuedPrompt;
52289
52310
  const queuedFiles = session.queuedFiles;
@@ -52365,6 +52386,7 @@ ${fmt.formatItalic("Claude Code restarted in the worktree")}`);
52365
52386
  const worktreePromptId = session.worktreePromptPostId;
52366
52387
  if (worktreePromptId) {
52367
52388
  await withErrorHandling(() => session.platform.updatePost(worktreePromptId, `\u2705 Created worktree for \`${branch}\``), { action: "Update worktree prompt", session });
52389
+ await withErrorHandling(() => session.platform.removeReaction(worktreePromptId, "x"), { action: "Remove x reaction from worktree prompt", session });
52368
52390
  }
52369
52391
  const wasPending = session.pendingWorktreePrompt;
52370
52392
  session.pendingWorktreePrompt = false;
@@ -52431,6 +52453,7 @@ ${fmt.formatItalic("Claude Code restarted in the new worktree")}`);
52431
52453
  const worktreePromptId = session.worktreePromptPostId;
52432
52454
  if (worktreePromptId) {
52433
52455
  await withErrorHandling(() => session.platform.updatePost(worktreePromptId, `\u274C Failed to create worktree for ${fmt.formatCode(branch)} - continuing in main repo`), { action: "Update worktree prompt after failure", session });
52456
+ await withErrorHandling(() => session.platform.removeReaction(worktreePromptId, "x"), { action: "Remove x reaction from worktree prompt", session });
52434
52457
  }
52435
52458
  const wasPending = session.pendingWorktreePrompt;
52436
52459
  session.pendingWorktreePrompt = false;
@@ -59517,6 +59540,8 @@ var use_input_default = useInput;
59517
59540
  var import_react20 = __toESM(require_react(), 1);
59518
59541
  // node_modules/ink/build/hooks/use-stdout.js
59519
59542
  var import_react21 = __toESM(require_react(), 1);
59543
+ var useStdout = () => import_react21.useContext(StdoutContext_default);
59544
+ var use_stdout_default = useStdout;
59520
59545
  // node_modules/ink/build/hooks/use-stderr.js
59521
59546
  var import_react22 = __toESM(require_react(), 1);
59522
59547
  // node_modules/ink/build/hooks/use-focus.js
@@ -62860,7 +62885,8 @@ function CollapsibleSession(props) {
62860
62885
  }
62861
62886
  // src/ui/components/StatusLine.tsx
62862
62887
  var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
62863
- function ToggleKey({ keyChar, label, enabled }) {
62888
+ function ToggleKey({ keyChar, label, enabled, color }) {
62889
+ const displayColor = color ?? (enabled ? "green" : "gray");
62864
62890
  return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
62865
62891
  gap: 0,
62866
62892
  children: [
@@ -62869,7 +62895,7 @@ function ToggleKey({ keyChar, label, enabled }) {
62869
62895
  children: "["
62870
62896
  }, undefined, false, undefined, this),
62871
62897
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
62872
- color: enabled ? "green" : "gray",
62898
+ color: displayColor,
62873
62899
  bold: true,
62874
62900
  children: keyChar
62875
62901
  }, undefined, false, undefined, this),
@@ -62878,12 +62904,44 @@ function ToggleKey({ keyChar, label, enabled }) {
62878
62904
  children: "]"
62879
62905
  }, undefined, false, undefined, this),
62880
62906
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
62881
- color: enabled ? "green" : "gray",
62907
+ color: displayColor,
62882
62908
  children: label
62883
62909
  }, undefined, false, undefined, this)
62884
62910
  ]
62885
62911
  }, undefined, true, undefined, this);
62886
62912
  }
62913
+ function getUpdateColor(state) {
62914
+ if (!state)
62915
+ return "gray";
62916
+ switch (state.status) {
62917
+ case "available":
62918
+ return "green";
62919
+ case "scheduled":
62920
+ case "installing":
62921
+ case "pending_restart":
62922
+ return "yellow";
62923
+ case "failed":
62924
+ return "red";
62925
+ case "deferred":
62926
+ case "idle":
62927
+ default:
62928
+ return "gray";
62929
+ }
62930
+ }
62931
+ function getUpdateIndicator(state) {
62932
+ if (!state)
62933
+ return "";
62934
+ switch (state.status) {
62935
+ case "available":
62936
+ return " \uD83C\uDD95";
62937
+ case "installing":
62938
+ return " \uD83D\uDCE6";
62939
+ case "failed":
62940
+ return " \u274C";
62941
+ default:
62942
+ return "";
62943
+ }
62944
+ }
62887
62945
  function PlatformToggle({
62888
62946
  index,
62889
62947
  platform: platform2
@@ -62927,9 +62985,12 @@ function StatusLine({
62927
62985
  shuttingDown,
62928
62986
  sessionCount,
62929
62987
  toggles,
62930
- platforms
62988
+ platforms,
62989
+ updateState
62931
62990
  }) {
62932
62991
  const platformList = Array.from(platforms.values());
62992
+ const updateColor = getUpdateColor(updateState);
62993
+ const updateIndicator = getUpdateIndicator(updateState);
62933
62994
  return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
62934
62995
  flexDirection: "column",
62935
62996
  marginTop: 1,
@@ -63002,6 +63063,20 @@ function StatusLine({
63002
63063
  label: "eep-alive",
63003
63064
  enabled: toggles.keepAliveEnabled
63004
63065
  }, undefined, false, undefined, this),
63066
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
63067
+ gap: 0,
63068
+ children: [
63069
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(ToggleKey, {
63070
+ keyChar: "u",
63071
+ label: "pdate",
63072
+ enabled: updateState?.status === "available",
63073
+ color: updateColor
63074
+ }, undefined, false, undefined, this),
63075
+ updateIndicator && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
63076
+ children: updateIndicator
63077
+ }, undefined, false, undefined, this)
63078
+ ]
63079
+ }, undefined, true, undefined, this),
63005
63080
  platformList.length > 0 && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
63006
63081
  children: [
63007
63082
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
@@ -63093,6 +63168,187 @@ function LogPanel({ logs, maxLines = 10 }) {
63093
63168
  }, log19.id, true, undefined, this))
63094
63169
  }, undefined, false, undefined, this);
63095
63170
  }
63171
+ // src/ui/components/Modal.tsx
63172
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
63173
+ function Modal({ title, children, hint }) {
63174
+ const { stdout } = use_stdout_default();
63175
+ const terminalWidth = stdout?.columns ?? 80;
63176
+ const modalWidth = Math.min(50, terminalWidth - 4);
63177
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63178
+ flexDirection: "column",
63179
+ borderStyle: "round",
63180
+ borderColor: "cyan",
63181
+ paddingX: 1,
63182
+ paddingY: 0,
63183
+ width: modalWidth,
63184
+ children: [
63185
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63186
+ justifyContent: "center",
63187
+ marginBottom: 1,
63188
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
63189
+ color: "cyan",
63190
+ bold: true,
63191
+ children: title
63192
+ }, undefined, false, undefined, this)
63193
+ }, undefined, false, undefined, this),
63194
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63195
+ flexDirection: "column",
63196
+ children
63197
+ }, undefined, false, undefined, this),
63198
+ hint && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63199
+ justifyContent: "center",
63200
+ marginTop: 1,
63201
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
63202
+ dimColor: true,
63203
+ children: hint
63204
+ }, undefined, false, undefined, this)
63205
+ }, undefined, false, undefined, this)
63206
+ ]
63207
+ }, undefined, true, undefined, this);
63208
+ }
63209
+ // src/ui/components/UpdateModal.tsx
63210
+ var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
63211
+ function formatTime(date) {
63212
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
63213
+ }
63214
+ function getStatusDisplay(state) {
63215
+ switch (state.status) {
63216
+ case "idle":
63217
+ return { icon: "\u2713", label: "Up to date", color: "green" };
63218
+ case "available":
63219
+ return { icon: "\uD83C\uDD95", label: "Update available", color: "green" };
63220
+ case "scheduled":
63221
+ return { icon: "\u23F0", label: "Restart scheduled", color: "yellow" };
63222
+ case "installing":
63223
+ return { icon: "\uD83D\uDCE6", label: "Installing...", color: "cyan" };
63224
+ case "pending_restart":
63225
+ return { icon: "\uD83D\uDD04", label: "Restarting...", color: "yellow" };
63226
+ case "failed":
63227
+ return { icon: "\u274C", label: "Update failed", color: "red" };
63228
+ case "deferred":
63229
+ return { icon: "\u23F8\uFE0F", label: "Update deferred", color: "gray" };
63230
+ default:
63231
+ return { icon: "?", label: "Unknown", color: "gray" };
63232
+ }
63233
+ }
63234
+ function getHint(state) {
63235
+ const canUpdate = state.status === "available" || state.status === "deferred";
63236
+ if (canUpdate) {
63237
+ return "Press [Shift+U] to update now | [u] or [Esc] to close";
63238
+ }
63239
+ return "Press [u] or [Esc] to close";
63240
+ }
63241
+ function UpdateModal({ state }) {
63242
+ const statusDisplay = getStatusDisplay(state);
63243
+ const hint = getHint(state);
63244
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Modal, {
63245
+ title: "Update Status",
63246
+ hint,
63247
+ children: [
63248
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63249
+ flexDirection: "column",
63250
+ gap: 0,
63251
+ children: [
63252
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63253
+ children: [
63254
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63255
+ dimColor: true,
63256
+ children: "Current version: "
63257
+ }, undefined, false, undefined, this),
63258
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63259
+ bold: true,
63260
+ children: [
63261
+ "v",
63262
+ state.currentVersion
63263
+ ]
63264
+ }, undefined, true, undefined, this)
63265
+ ]
63266
+ }, undefined, true, undefined, this),
63267
+ state.latestVersion && state.latestVersion !== state.currentVersion && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63268
+ children: [
63269
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63270
+ dimColor: true,
63271
+ children: "Latest version: "
63272
+ }, undefined, false, undefined, this),
63273
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63274
+ bold: true,
63275
+ color: "green",
63276
+ children: [
63277
+ "v",
63278
+ state.latestVersion
63279
+ ]
63280
+ }, undefined, true, undefined, this)
63281
+ ]
63282
+ }, undefined, true, undefined, this)
63283
+ ]
63284
+ }, undefined, true, undefined, this),
63285
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63286
+ marginTop: 1,
63287
+ children: state.status === "installing" ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63288
+ gap: 1,
63289
+ children: [
63290
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Spinner2, {
63291
+ type: "dots"
63292
+ }, undefined, false, undefined, this),
63293
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63294
+ color: statusDisplay.color,
63295
+ children: statusDisplay.label
63296
+ }, undefined, false, undefined, this)
63297
+ ]
63298
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63299
+ gap: 1,
63300
+ children: [
63301
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63302
+ children: statusDisplay.icon
63303
+ }, undefined, false, undefined, this),
63304
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63305
+ color: statusDisplay.color,
63306
+ children: statusDisplay.label
63307
+ }, undefined, false, undefined, this)
63308
+ ]
63309
+ }, undefined, true, undefined, this)
63310
+ }, undefined, false, undefined, this),
63311
+ state.status === "scheduled" && state.scheduledRestartAt && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63312
+ marginTop: 1,
63313
+ children: [
63314
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63315
+ dimColor: true,
63316
+ children: "Restart at: "
63317
+ }, undefined, false, undefined, this),
63318
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63319
+ children: formatTime(state.scheduledRestartAt)
63320
+ }, undefined, false, undefined, this)
63321
+ ]
63322
+ }, undefined, true, undefined, this),
63323
+ state.status === "deferred" && state.deferredUntil && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63324
+ marginTop: 1,
63325
+ children: [
63326
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63327
+ dimColor: true,
63328
+ children: "Deferred until: "
63329
+ }, undefined, false, undefined, this),
63330
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63331
+ children: formatTime(state.deferredUntil)
63332
+ }, undefined, false, undefined, this)
63333
+ ]
63334
+ }, undefined, true, undefined, this),
63335
+ state.status === "failed" && state.errorMessage && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
63336
+ marginTop: 1,
63337
+ flexDirection: "column",
63338
+ children: [
63339
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63340
+ color: "red",
63341
+ children: "Error:"
63342
+ }, undefined, false, undefined, this),
63343
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
63344
+ dimColor: true,
63345
+ children: state.errorMessage
63346
+ }, undefined, false, undefined, this)
63347
+ ]
63348
+ }, undefined, true, undefined, this)
63349
+ ]
63350
+ }, undefined, true, undefined, this);
63351
+ }
63096
63352
  // src/ui/hooks/useAppState.ts
63097
63353
  var import_react57 = __toESM(require_react(), 1);
63098
63354
  var logIdCounter = 0;
@@ -63237,15 +63493,26 @@ function useKeyboard({
63237
63493
  onDebugToggle,
63238
63494
  onPermissionsToggle,
63239
63495
  onChromeToggle,
63240
- onKeepAliveToggle
63496
+ onKeepAliveToggle,
63497
+ onUpdateModalToggle,
63498
+ onForceUpdate,
63499
+ updateModalVisible
63241
63500
  }) {
63242
63501
  use_input_default((input, key) => {
63502
+ if (key.escape && updateModalVisible) {
63503
+ onUpdateModalToggle?.();
63504
+ return;
63505
+ }
63243
63506
  if (input === "\x03" || input === "c" && key.ctrl) {
63244
63507
  if (onQuit) {
63245
63508
  onQuit();
63246
63509
  }
63247
63510
  return;
63248
63511
  }
63512
+ if (input === "U") {
63513
+ onForceUpdate?.();
63514
+ return;
63515
+ }
63249
63516
  const platformIndex = SHIFT_NUMBER_MAP[input];
63250
63517
  if (platformIndex !== undefined && onPlatformToggle) {
63251
63518
  const platformId = platformIds[platformIndex];
@@ -63274,6 +63541,9 @@ function useKeyboard({
63274
63541
  case "k":
63275
63542
  onKeepAliveToggle?.();
63276
63543
  break;
63544
+ case "u":
63545
+ onUpdateModalToggle?.();
63546
+ break;
63277
63547
  case "q":
63278
63548
  onQuit?.();
63279
63549
  break;
@@ -63282,7 +63552,7 @@ function useKeyboard({
63282
63552
  }
63283
63553
 
63284
63554
  // src/ui/App.tsx
63285
- var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
63555
+ var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
63286
63556
  function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks }) {
63287
63557
  const {
63288
63558
  state,
@@ -63303,7 +63573,12 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63303
63573
  debugMode: process.env.DEBUG === "1",
63304
63574
  skipPermissions: config.skipPermissions,
63305
63575
  chromeEnabled: config.chromeEnabled,
63306
- keepAliveEnabled: config.keepAliveEnabled
63576
+ keepAliveEnabled: config.keepAliveEnabled,
63577
+ updateModalVisible: false
63578
+ });
63579
+ const [updateState, setUpdateState] = import_react58.default.useState({
63580
+ status: "idle",
63581
+ currentVersion: config.version
63307
63582
  });
63308
63583
  const handleDebugToggle = import_react58.default.useCallback(() => {
63309
63584
  setToggles((prev) => {
@@ -63334,6 +63609,9 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63334
63609
  return { ...prev, keepAliveEnabled: newValue };
63335
63610
  });
63336
63611
  }, [toggleCallbacks]);
63612
+ const handleUpdateModalToggle = import_react58.default.useCallback(() => {
63613
+ setToggles((prev) => ({ ...prev, updateModalVisible: !prev.updateModalVisible }));
63614
+ }, []);
63337
63615
  const handlePlatformToggle = import_react58.default.useCallback((platformId) => {
63338
63616
  const newEnabled = togglePlatformEnabled(platformId);
63339
63617
  toggleCallbacks?.onPlatformToggle?.(platformId, newEnabled);
@@ -63348,9 +63626,10 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63348
63626
  removeSession,
63349
63627
  addLog,
63350
63628
  setPlatformStatus,
63629
+ setUpdateState,
63351
63630
  getToggles
63352
63631
  });
63353
- }, [onStateReady, setReady, setShuttingDown2, addSession, updateSession, removeSession, addLog, setPlatformStatus, getToggles]);
63632
+ }, [onStateReady, setReady, setShuttingDown2, addSession, updateSession, removeSession, addLog, setPlatformStatus, setUpdateState, getToggles]);
63354
63633
  import_react58.default.useEffect(() => {
63355
63634
  if (onResizeReady) {
63356
63635
  onResizeReady(() => setResizeCount((c) => c + 1));
@@ -63367,58 +63646,61 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63367
63646
  onDebugToggle: handleDebugToggle,
63368
63647
  onPermissionsToggle: handlePermissionsToggle,
63369
63648
  onChromeToggle: handleChromeToggle,
63370
- onKeepAliveToggle: handleKeepAliveToggle
63649
+ onKeepAliveToggle: handleKeepAliveToggle,
63650
+ onUpdateModalToggle: handleUpdateModalToggle,
63651
+ onForceUpdate: toggleCallbacks?.onForceUpdate,
63652
+ updateModalVisible: toggles.updateModalVisible
63371
63653
  });
63372
63654
  const staticContent = import_react58.default.useMemo(() => [
63373
- { id: `header-${resizeCount}`, element: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Header, {
63655
+ { id: `header-${resizeCount}`, element: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Header, {
63374
63656
  version: config.version
63375
63657
  }, undefined, false, undefined, this) },
63376
- { id: `config-${resizeCount}`, element: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ConfigSummary, {
63658
+ { id: `config-${resizeCount}`, element: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(ConfigSummary, {
63377
63659
  config
63378
63660
  }, undefined, false, undefined, this) }
63379
63661
  ], [config, resizeCount]);
63380
63662
  const globalLogs = getGlobalLogs();
63381
63663
  const hasLogs = globalLogs.length > 0;
63382
63664
  const hasSessions = state.sessions.size > 0;
63383
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63665
+ return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63384
63666
  flexDirection: "column",
63385
63667
  children: [
63386
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Static, {
63668
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Static, {
63387
63669
  items: staticContent,
63388
- children: (item) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63670
+ children: (item) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63389
63671
  children: item.element
63390
63672
  }, item.id, false, undefined, this)
63391
63673
  }, undefined, false, undefined, this),
63392
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Platforms, {
63674
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Platforms, {
63393
63675
  platforms: state.platforms
63394
63676
  }, undefined, false, undefined, this),
63395
- hasLogs && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
63677
+ hasLogs && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(jsx_dev_runtime11.Fragment, {
63396
63678
  children: [
63397
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63679
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63398
63680
  marginTop: 1,
63399
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
63681
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
63400
63682
  dimColor: true,
63401
63683
  children: "\u2500".repeat(50)
63402
63684
  }, undefined, false, undefined, this)
63403
63685
  }, undefined, false, undefined, this),
63404
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LogPanel, {
63686
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(LogPanel, {
63405
63687
  logs: globalLogs,
63406
63688
  maxLines: 10
63407
63689
  }, undefined, false, undefined, this)
63408
63690
  ]
63409
63691
  }, undefined, true, undefined, this),
63410
- hasSessions && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
63692
+ hasSessions && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(jsx_dev_runtime11.Fragment, {
63411
63693
  children: [
63412
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63694
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63413
63695
  marginTop: 1,
63414
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
63696
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
63415
63697
  dimColor: true,
63416
63698
  children: "\u2500".repeat(50)
63417
63699
  }, undefined, false, undefined, this)
63418
63700
  }, undefined, false, undefined, this),
63419
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
63701
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63420
63702
  marginTop: 0,
63421
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
63703
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text, {
63422
63704
  dimColor: true,
63423
63705
  children: [
63424
63706
  "Sessions (",
@@ -63427,7 +63709,7 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63427
63709
  ]
63428
63710
  }, undefined, true, undefined, this)
63429
63711
  }, undefined, false, undefined, this),
63430
- Array.from(state.sessions.entries()).map(([id, session], index) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(CollapsibleSession, {
63712
+ Array.from(state.sessions.entries()).map(([id, session], index) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(CollapsibleSession, {
63431
63713
  session,
63432
63714
  logs: getLogsForSession(id),
63433
63715
  expanded: state.expandedSessions.has(id),
@@ -63435,12 +63717,20 @@ function App2({ config, onStateReady, onResizeReady, onQuit, toggleCallbacks })
63435
63717
  }, id, false, undefined, this))
63436
63718
  ]
63437
63719
  }, undefined, true, undefined, this),
63438
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(StatusLine, {
63720
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(StatusLine, {
63439
63721
  ready: state.ready,
63440
63722
  shuttingDown: state.shuttingDown,
63441
63723
  sessionCount: state.sessions.size,
63442
63724
  toggles,
63443
- platforms: state.platforms
63725
+ platforms: state.platforms,
63726
+ updateState
63727
+ }, undefined, false, undefined, this),
63728
+ toggles.updateModalVisible && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
63729
+ marginTop: 1,
63730
+ justifyContent: "center",
63731
+ children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(UpdateModal, {
63732
+ state: updateState
63733
+ }, undefined, false, undefined, this)
63444
63734
  }, undefined, false, undefined, this)
63445
63735
  ]
63446
63736
  }, undefined, true, undefined, this);
@@ -63487,6 +63777,7 @@ async function startUI(options) {
63487
63777
  removeSession: handlers.removeSession,
63488
63778
  addLog: handlers.addLog,
63489
63779
  setPlatformStatus: handlers.setPlatformStatus,
63780
+ setUpdateState: handlers.setUpdateState,
63490
63781
  waitUntilExit,
63491
63782
  getToggles: handlers.getToggles
63492
63783
  };
@@ -64613,6 +64904,16 @@ async function main() {
64613
64904
  ui.setPlatformStatus(platformId, { connected: false });
64614
64905
  ui.addLog({ level: "info", component: "toggle", message: `\u2713 Platform ${platformId} disabled` });
64615
64906
  }
64907
+ },
64908
+ onForceUpdate: () => {
64909
+ if (autoUpdateManager?.hasUpdate()) {
64910
+ ui.addLog({ level: "info", component: "update", message: "\uD83D\uDE80 Force updating via Shift+U..." });
64911
+ autoUpdateManager.forceUpdate().catch((err) => {
64912
+ ui.addLog({ level: "error", component: "update", message: `Force update failed: ${err}` });
64913
+ });
64914
+ } else {
64915
+ ui.addLog({ level: "info", component: "update", message: "No update available to install" });
64916
+ }
64616
64917
  }
64617
64918
  }
64618
64919
  });
@@ -64677,19 +64978,53 @@ async function main() {
64677
64978
  session.setAutoUpdateManager(autoUpdateManager);
64678
64979
  autoUpdateManager.on("update:available", (info) => {
64679
64980
  ui.addLog({ level: "info", component: "update", message: `\uD83C\uDD95 Update available: v${info.currentVersion} \u2192 v${info.latestVersion}` });
64981
+ ui.setUpdateState({
64982
+ status: "available",
64983
+ currentVersion: info.currentVersion,
64984
+ latestVersion: info.latestVersion
64985
+ });
64680
64986
  });
64681
64987
  autoUpdateManager.on("update:countdown", (seconds) => {
64682
64988
  if (seconds === 60 || seconds === 30 || seconds === 10 || seconds <= 5) {
64683
64989
  ui.addLog({ level: "info", component: "update", message: `\uD83D\uDD04 Restarting in ${seconds} seconds...` });
64684
64990
  }
64991
+ const restartAt = autoUpdateManager?.getScheduledRestartAt();
64992
+ const updateInfo = autoUpdateManager?.getUpdateInfo();
64993
+ if (restartAt) {
64994
+ ui.setUpdateState({
64995
+ status: "scheduled",
64996
+ currentVersion: VERSION,
64997
+ latestVersion: updateInfo?.latestVersion,
64998
+ scheduledRestartAt: restartAt
64999
+ });
65000
+ }
64685
65001
  });
64686
65002
  autoUpdateManager.on("update:status", (status, message) => {
64687
65003
  if (message) {
64688
65004
  ui.addLog({ level: "info", component: "update", message: `\uD83D\uDD04 ${status}: ${message}` });
64689
65005
  }
65006
+ const updateInfo = autoUpdateManager?.getUpdateInfo();
65007
+ const state = autoUpdateManager?.getState();
65008
+ ui.setUpdateState({
65009
+ status,
65010
+ currentVersion: VERSION,
65011
+ latestVersion: updateInfo?.latestVersion,
65012
+ scheduledRestartAt: autoUpdateManager?.getScheduledRestartAt() ?? undefined,
65013
+ errorMessage: state?.errorMessage
65014
+ });
64690
65015
  });
64691
65016
  autoUpdateManager.on("update:failed", (error) => {
64692
65017
  ui.addLog({ level: "error", component: "update", message: `\u274C Update failed: ${error}` });
65018
+ ui.setUpdateState({
65019
+ status: "failed",
65020
+ currentVersion: VERSION,
65021
+ latestVersion: autoUpdateManager?.getUpdateInfo()?.latestVersion,
65022
+ errorMessage: error
65023
+ });
65024
+ });
65025
+ ui.setUpdateState({
65026
+ status: "idle",
65027
+ currentVersion: VERSION
64693
65028
  });
64694
65029
  autoUpdateManager.start();
64695
65030
  ui.setReady();
@@ -33641,9 +33641,19 @@ function isAllowAllEmoji(emoji3) {
33641
33641
  }
33642
33642
 
33643
33643
  // src/utils/tool-formatter.ts
33644
- function shortenPath(path, homeDir) {
33644
+ function shortenPath(path, homeDir, worktreeInfo) {
33645
33645
  if (!path)
33646
33646
  return "";
33647
+ if (worktreeInfo?.path && worktreeInfo?.branch) {
33648
+ const worktreePath = worktreeInfo.path.endsWith("/") ? worktreeInfo.path : worktreeInfo.path + "/";
33649
+ if (path.startsWith(worktreePath)) {
33650
+ const relativePath = path.slice(worktreePath.length);
33651
+ return `[${worktreeInfo.branch}]/${relativePath}`;
33652
+ }
33653
+ if (path === worktreeInfo.path) {
33654
+ return `[${worktreeInfo.branch}]/`;
33655
+ }
33656
+ }
33647
33657
  const home = homeDir ?? process.env.HOME ?? "";
33648
33658
  if (home && path.startsWith(home)) {
33649
33659
  return "~" + path.slice(home.length);
@@ -33661,8 +33671,8 @@ function parseMcpToolName(toolName) {
33661
33671
  tool: parts.slice(2).join("__")
33662
33672
  };
33663
33673
  }
33664
- function formatToolForPermission(toolName, input, formatter) {
33665
- const short = (p) => shortenPath(p);
33674
+ function formatToolForPermission(toolName, input, formatter, options = {}) {
33675
+ const short = (p) => shortenPath(p, undefined, options.worktreeInfo);
33666
33676
  switch (toolName) {
33667
33677
  case "Read":
33668
33678
  return `\uD83D\uDCC4 ${formatter.formatBold("Read")} ${formatter.formatCode(short(input.file_path))}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.44.0",
3
+ "version": "0.45.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",