claude-threads 0.42.0 → 0.43.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 +11 -0
- package/bin/claude-threads-daemon +151 -0
- package/dist/index.js +922 -2
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.43.0] - 2026-01-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Auto-restart on updates** - Bot automatically restarts after installing updates when running with daemon wrapper
|
|
14
|
+
- **`!update` command family** - Check update status, force immediate update (`!update now`), or defer (`!update defer`)
|
|
15
|
+
- **`--auto-restart` / `--no-auto-restart` CLI flags** - Control auto-restart behavior (enabled by default when `autoUpdate.enabled`)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Platform-specific formatting for update messages** - Update notifications now use proper bold/italic formatting per platform (Mattermost vs Slack)
|
|
19
|
+
- **Improved daemon wrapper** - Now correctly uses local binary instead of global installation
|
|
20
|
+
|
|
10
21
|
## [0.42.0] - 2026-01-07
|
|
11
22
|
|
|
12
23
|
### Fixed
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# claude-threads-daemon - Auto-restart wrapper for claude-threads
|
|
4
|
+
#
|
|
5
|
+
# This script runs claude-threads and automatically restarts it when
|
|
6
|
+
# it exits with code 42 (the special "update restart" signal).
|
|
7
|
+
#
|
|
8
|
+
# For other exit codes:
|
|
9
|
+
# - 0: Clean exit, don't restart
|
|
10
|
+
# - 1+: Error, optional restart based on flags
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# claude-threads-daemon [options]
|
|
14
|
+
# claude-threads-daemon --restart-on-error # Also restart on errors
|
|
15
|
+
# claude-threads-daemon --max-restarts 10 # Limit restart count
|
|
16
|
+
#
|
|
17
|
+
# Environment variables:
|
|
18
|
+
# CLAUDE_THREADS_MAX_RESTARTS - Maximum restart attempts (default: unlimited)
|
|
19
|
+
# CLAUDE_THREADS_RESTART_DELAY - Delay between restarts in seconds (default: 2)
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
# Exit code that signals "restart for update"
|
|
25
|
+
RESTART_EXIT_CODE=42
|
|
26
|
+
|
|
27
|
+
# Configuration
|
|
28
|
+
MAX_RESTARTS=${CLAUDE_THREADS_MAX_RESTARTS:-0} # 0 = unlimited
|
|
29
|
+
RESTART_DELAY=${CLAUDE_THREADS_RESTART_DELAY:-2}
|
|
30
|
+
RESTART_ON_ERROR=false
|
|
31
|
+
VERBOSE=false
|
|
32
|
+
|
|
33
|
+
# Path to claude-threads binary (set by index.ts when spawning daemon)
|
|
34
|
+
# Falls back to global 'claude-threads' command if not set
|
|
35
|
+
# If CLAUDE_THREADS_BIN is a .js file, run it with bun
|
|
36
|
+
if [ -n "$CLAUDE_THREADS_BIN" ]; then
|
|
37
|
+
if [[ "$CLAUDE_THREADS_BIN" == *.js ]]; then
|
|
38
|
+
CLAUDE_THREADS_CMD="bun $CLAUDE_THREADS_BIN"
|
|
39
|
+
else
|
|
40
|
+
CLAUDE_THREADS_CMD="$CLAUDE_THREADS_BIN"
|
|
41
|
+
fi
|
|
42
|
+
else
|
|
43
|
+
CLAUDE_THREADS_CMD="claude-threads"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Track restart count
|
|
47
|
+
restart_count=0
|
|
48
|
+
|
|
49
|
+
# Parse arguments
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case $1 in
|
|
52
|
+
--restart-on-error)
|
|
53
|
+
RESTART_ON_ERROR=true
|
|
54
|
+
shift
|
|
55
|
+
;;
|
|
56
|
+
--max-restarts)
|
|
57
|
+
MAX_RESTARTS="$2"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--restart-delay)
|
|
61
|
+
RESTART_DELAY="$2"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
--verbose|-v)
|
|
65
|
+
VERBOSE=true
|
|
66
|
+
shift
|
|
67
|
+
;;
|
|
68
|
+
--help|-h)
|
|
69
|
+
echo "claude-threads-daemon - Auto-restart wrapper for claude-threads"
|
|
70
|
+
echo ""
|
|
71
|
+
echo "Usage: claude-threads-daemon [options]"
|
|
72
|
+
echo ""
|
|
73
|
+
echo "Options:"
|
|
74
|
+
echo " --restart-on-error Also restart on non-zero exit codes"
|
|
75
|
+
echo " --max-restarts N Maximum number of restarts (default: unlimited)"
|
|
76
|
+
echo " --restart-delay N Seconds to wait between restarts (default: 2)"
|
|
77
|
+
echo " --verbose, -v Print debug information"
|
|
78
|
+
echo " --help, -h Show this help message"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "Exit codes:"
|
|
81
|
+
echo " 0 Clean exit, no restart"
|
|
82
|
+
echo " 42 Update restart signal (always restarts)"
|
|
83
|
+
echo " other Error (restarts only with --restart-on-error)"
|
|
84
|
+
exit 0
|
|
85
|
+
;;
|
|
86
|
+
*)
|
|
87
|
+
# Pass through to claude-threads
|
|
88
|
+
break
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
log() {
|
|
94
|
+
if [ "$VERBOSE" = true ]; then
|
|
95
|
+
echo "[daemon] $*" >&2
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
log "Starting claude-threads daemon"
|
|
100
|
+
log "Binary: $CLAUDE_THREADS_CMD"
|
|
101
|
+
log "Max restarts: ${MAX_RESTARTS:-unlimited}"
|
|
102
|
+
log "Restart delay: ${RESTART_DELAY}s"
|
|
103
|
+
log "Restart on error: $RESTART_ON_ERROR"
|
|
104
|
+
|
|
105
|
+
while true; do
|
|
106
|
+
log "Starting claude-threads (restart #$restart_count)..."
|
|
107
|
+
|
|
108
|
+
# Run claude-threads with any remaining arguments
|
|
109
|
+
# Note: CLAUDE_THREADS_CMD may be "bun /path/to/file.js" so we use eval
|
|
110
|
+
set +e
|
|
111
|
+
eval $CLAUDE_THREADS_CMD '"$@"'
|
|
112
|
+
exit_code=$?
|
|
113
|
+
set -e
|
|
114
|
+
|
|
115
|
+
log "claude-threads exited with code $exit_code"
|
|
116
|
+
|
|
117
|
+
# Check if we should restart
|
|
118
|
+
should_restart=false
|
|
119
|
+
|
|
120
|
+
if [ $exit_code -eq $RESTART_EXIT_CODE ]; then
|
|
121
|
+
# Exit code 42 = restart for update
|
|
122
|
+
log "Received update restart signal"
|
|
123
|
+
should_restart=true
|
|
124
|
+
elif [ $exit_code -eq 0 ]; then
|
|
125
|
+
# Clean exit
|
|
126
|
+
log "Clean exit, not restarting"
|
|
127
|
+
should_restart=false
|
|
128
|
+
elif [ "$RESTART_ON_ERROR" = true ]; then
|
|
129
|
+
# Error exit with restart-on-error enabled
|
|
130
|
+
log "Error exit, restarting due to --restart-on-error"
|
|
131
|
+
should_restart=true
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [ "$should_restart" = false ]; then
|
|
135
|
+
log "Exiting daemon with code $exit_code"
|
|
136
|
+
exit $exit_code
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Check max restarts
|
|
140
|
+
if [ $MAX_RESTARTS -gt 0 ]; then
|
|
141
|
+
restart_count=$((restart_count + 1))
|
|
142
|
+
if [ $restart_count -ge $MAX_RESTARTS ]; then
|
|
143
|
+
echo "[daemon] Maximum restart count ($MAX_RESTARTS) reached, exiting" >&2
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Wait before restart
|
|
149
|
+
log "Waiting ${RESTART_DELAY}s before restart..."
|
|
150
|
+
sleep "$RESTART_DELAY"
|
|
151
|
+
done
|
package/dist/index.js
CHANGED
|
@@ -51460,6 +51460,74 @@ async function updateSessionHeader(session, ctx) {
|
|
|
51460
51460
|
const postId = session.sessionStartPostId;
|
|
51461
51461
|
await withErrorHandling(() => session.platform.updatePost(postId, msg), { action: "Update session header", session });
|
|
51462
51462
|
}
|
|
51463
|
+
async function showUpdateStatus(session, updateManager) {
|
|
51464
|
+
const formatter = session.platform.getFormatter();
|
|
51465
|
+
if (!updateManager) {
|
|
51466
|
+
await postInfo(session, `Auto-update is not available`);
|
|
51467
|
+
return;
|
|
51468
|
+
}
|
|
51469
|
+
if (!updateManager.isEnabled()) {
|
|
51470
|
+
await postInfo(session, `Auto-update is disabled in configuration`);
|
|
51471
|
+
return;
|
|
51472
|
+
}
|
|
51473
|
+
const updateInfo = await updateManager.checkNow();
|
|
51474
|
+
if (!updateInfo || !updateInfo.available) {
|
|
51475
|
+
await postSuccess(session, `${formatter.formatBold("Up to date")} - no updates available`);
|
|
51476
|
+
return;
|
|
51477
|
+
}
|
|
51478
|
+
const scheduledAt = updateManager.getScheduledRestartAt();
|
|
51479
|
+
const config = updateManager.getConfig();
|
|
51480
|
+
let statusLine;
|
|
51481
|
+
if (scheduledAt) {
|
|
51482
|
+
const secondsRemaining = Math.max(0, Math.round((scheduledAt.getTime() - Date.now()) / 1000));
|
|
51483
|
+
statusLine = `Restarting in ${secondsRemaining} seconds`;
|
|
51484
|
+
} else {
|
|
51485
|
+
statusLine = `Mode: ${config.autoRestartMode}`;
|
|
51486
|
+
}
|
|
51487
|
+
await postInfo(session, `\uD83D\uDD04 ${formatter.formatBold("Update available")}
|
|
51488
|
+
|
|
51489
|
+
` + `Current: v${updateInfo.currentVersion}
|
|
51490
|
+
` + `Latest: v${updateInfo.latestVersion}
|
|
51491
|
+
` + `${statusLine}
|
|
51492
|
+
|
|
51493
|
+
` + `Commands:
|
|
51494
|
+
` + `\u2022 ${formatter.formatCode("!update now")} - Update immediately
|
|
51495
|
+
` + `\u2022 ${formatter.formatCode("!update defer")} - Defer for 1 hour`);
|
|
51496
|
+
}
|
|
51497
|
+
async function forceUpdateNow(session, username, updateManager) {
|
|
51498
|
+
if (!await requireSessionOwner(session, username, "force updates")) {
|
|
51499
|
+
return;
|
|
51500
|
+
}
|
|
51501
|
+
const formatter = session.platform.getFormatter();
|
|
51502
|
+
if (!updateManager) {
|
|
51503
|
+
await postWarning(session, `Auto-update is not available`);
|
|
51504
|
+
return;
|
|
51505
|
+
}
|
|
51506
|
+
if (!updateManager.hasUpdate()) {
|
|
51507
|
+
await postInfo(session, `No update available to install`);
|
|
51508
|
+
return;
|
|
51509
|
+
}
|
|
51510
|
+
await postInfo(session, `\uD83D\uDD04 ${formatter.formatBold("Forcing update")} - restarting shortly...
|
|
51511
|
+
` + formatter.formatItalic("Sessions will resume automatically"));
|
|
51512
|
+
await updateManager.forceUpdate();
|
|
51513
|
+
}
|
|
51514
|
+
async function deferUpdate(session, username, updateManager) {
|
|
51515
|
+
if (!await requireSessionOwner(session, username, "defer updates")) {
|
|
51516
|
+
return;
|
|
51517
|
+
}
|
|
51518
|
+
const formatter = session.platform.getFormatter();
|
|
51519
|
+
if (!updateManager) {
|
|
51520
|
+
await postWarning(session, `Auto-update is not available`);
|
|
51521
|
+
return;
|
|
51522
|
+
}
|
|
51523
|
+
if (!updateManager.hasUpdate()) {
|
|
51524
|
+
await postInfo(session, `No pending update to defer`);
|
|
51525
|
+
return;
|
|
51526
|
+
}
|
|
51527
|
+
updateManager.deferUpdate(60);
|
|
51528
|
+
await postSuccess(session, `\u23F8\uFE0F ${formatter.formatBold("Update deferred")} for 1 hour
|
|
51529
|
+
` + formatter.formatItalic("Use !update now to apply earlier"));
|
|
51530
|
+
}
|
|
51463
51531
|
|
|
51464
51532
|
// src/session/lifecycle.ts
|
|
51465
51533
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -51529,6 +51597,9 @@ Users can control sessions with these commands:
|
|
|
51529
51597
|
- \`!kick @user\`: Remove a user from the session
|
|
51530
51598
|
- \`!cd /path\`: Change working directory (restarts the session)
|
|
51531
51599
|
- \`!permissions interactive|skip\`: Toggle permission prompts
|
|
51600
|
+
- \`!update\`: Show auto-update status
|
|
51601
|
+
- \`!update now\`: Apply pending update immediately
|
|
51602
|
+
- \`!update defer\`: Defer pending update for 1 hour
|
|
51532
51603
|
|
|
51533
51604
|
SESSION METADATA: At the START of your first response, include metadata about this session:
|
|
51534
51605
|
|
|
@@ -52848,6 +52919,18 @@ async function buildStatusBar(sessionCount, config, formatter, platformId) {
|
|
|
52848
52919
|
if (pausedPlatforms.get(platformId)) {
|
|
52849
52920
|
items.push(formatter.formatCode("\u23F8\uFE0F Platform paused"));
|
|
52850
52921
|
}
|
|
52922
|
+
if (config.updateStatus?.available) {
|
|
52923
|
+
const status = config.updateStatus;
|
|
52924
|
+
if (status.countdownSeconds !== undefined && status.countdownSeconds > 0) {
|
|
52925
|
+
items.push(formatter.formatCode(`\uD83D\uDD04 Restarting in ${status.countdownSeconds}s`));
|
|
52926
|
+
} else if (status.status === "installing") {
|
|
52927
|
+
items.push(formatter.formatCode(`\uD83D\uDCE6 Installing v${status.latestVersion}...`));
|
|
52928
|
+
} else if (status.status === "available") {
|
|
52929
|
+
items.push(formatter.formatCode(`\uD83C\uDD95 Update: v${status.latestVersion}`));
|
|
52930
|
+
} else if (status.status === "deferred") {
|
|
52931
|
+
items.push(formatter.formatCode(`\u23F8\uFE0F Update deferred`));
|
|
52932
|
+
}
|
|
52933
|
+
}
|
|
52851
52934
|
const claudeVersion = getClaudeCliVersion();
|
|
52852
52935
|
const versionStr = claudeVersion ? `v${VERSION} \xB7 CLI ${claudeVersion}` : `v${VERSION}`;
|
|
52853
52936
|
items.push(formatter.formatCode(versionStr));
|
|
@@ -53143,6 +53226,7 @@ class SessionManager extends EventEmitter4 {
|
|
|
53143
53226
|
sessionStore;
|
|
53144
53227
|
cleanupTimer = null;
|
|
53145
53228
|
isShuttingDown = false;
|
|
53229
|
+
autoUpdateManager = null;
|
|
53146
53230
|
constructor(workingDir, skipPermissions = false, chromeEnabled = false, worktreeMode = "prompt", sessionsPath) {
|
|
53147
53231
|
super();
|
|
53148
53232
|
this.workingDir = workingDir;
|
|
@@ -53179,6 +53263,9 @@ class SessionManager extends EventEmitter4 {
|
|
|
53179
53263
|
removePlatform(platformId) {
|
|
53180
53264
|
this.platforms.delete(platformId);
|
|
53181
53265
|
}
|
|
53266
|
+
setAutoUpdateManager(manager) {
|
|
53267
|
+
this.autoUpdateManager = manager;
|
|
53268
|
+
}
|
|
53182
53269
|
registerWorktreeUser(worktreePath, sessionId) {
|
|
53183
53270
|
if (!this.worktreeUsers.has(worktreePath)) {
|
|
53184
53271
|
this.worktreeUsers.set(worktreePath, new Set);
|
|
@@ -53789,6 +53876,24 @@ class SessionManager extends EventEmitter4 {
|
|
|
53789
53876
|
return;
|
|
53790
53877
|
await enableInteractivePermissions(session, username, this.getContext());
|
|
53791
53878
|
}
|
|
53879
|
+
async showUpdateStatus(threadId, _username) {
|
|
53880
|
+
const session = this.findSessionByThreadId(threadId);
|
|
53881
|
+
if (!session)
|
|
53882
|
+
return;
|
|
53883
|
+
await showUpdateStatus(session, this.autoUpdateManager);
|
|
53884
|
+
}
|
|
53885
|
+
async forceUpdateNow(threadId, username) {
|
|
53886
|
+
const session = this.findSessionByThreadId(threadId);
|
|
53887
|
+
if (!session)
|
|
53888
|
+
return;
|
|
53889
|
+
await forceUpdateNow(session, username, this.autoUpdateManager);
|
|
53890
|
+
}
|
|
53891
|
+
async deferUpdate(threadId, username) {
|
|
53892
|
+
const session = this.findSessionByThreadId(threadId);
|
|
53893
|
+
if (!session)
|
|
53894
|
+
return;
|
|
53895
|
+
await deferUpdate(session, username, this.autoUpdateManager);
|
|
53896
|
+
}
|
|
53792
53897
|
isSessionInteractive(threadId) {
|
|
53793
53898
|
const session = this.findSessionByThreadId(threadId);
|
|
53794
53899
|
if (!session)
|
|
@@ -53928,6 +54033,60 @@ class SessionManager extends EventEmitter4 {
|
|
|
53928
54033
|
this.isShuttingDown = true;
|
|
53929
54034
|
setShuttingDown(true);
|
|
53930
54035
|
}
|
|
54036
|
+
getActivityInfo() {
|
|
54037
|
+
const sessions = [...this.sessions.values()];
|
|
54038
|
+
if (sessions.length === 0) {
|
|
54039
|
+
return {
|
|
54040
|
+
activeSessionCount: 0,
|
|
54041
|
+
lastActivityAt: null,
|
|
54042
|
+
anySessionBusy: false
|
|
54043
|
+
};
|
|
54044
|
+
}
|
|
54045
|
+
let lastActivity = null;
|
|
54046
|
+
let anyBusy = false;
|
|
54047
|
+
for (const session of sessions) {
|
|
54048
|
+
if (!lastActivity || session.lastActivityAt > lastActivity) {
|
|
54049
|
+
lastActivity = session.lastActivityAt;
|
|
54050
|
+
}
|
|
54051
|
+
if (session.typingTimer !== null) {
|
|
54052
|
+
anyBusy = true;
|
|
54053
|
+
}
|
|
54054
|
+
}
|
|
54055
|
+
return {
|
|
54056
|
+
activeSessionCount: sessions.length,
|
|
54057
|
+
lastActivityAt: lastActivity,
|
|
54058
|
+
anySessionBusy: anyBusy
|
|
54059
|
+
};
|
|
54060
|
+
}
|
|
54061
|
+
async broadcastToAll(messageBuilder) {
|
|
54062
|
+
for (const session of this.sessions.values()) {
|
|
54063
|
+
try {
|
|
54064
|
+
const formatter = session.platform.getFormatter();
|
|
54065
|
+
const message = messageBuilder(formatter);
|
|
54066
|
+
await postInfo(session, message);
|
|
54067
|
+
} catch (err) {
|
|
54068
|
+
log18.warn(`Failed to broadcast to session ${session.threadId}: ${err}`);
|
|
54069
|
+
}
|
|
54070
|
+
}
|
|
54071
|
+
}
|
|
54072
|
+
async postUpdateAskMessage(threadIds, version) {
|
|
54073
|
+
for (const threadId of threadIds) {
|
|
54074
|
+
const session = this.findSessionByThreadId(threadId);
|
|
54075
|
+
if (!session)
|
|
54076
|
+
continue;
|
|
54077
|
+
try {
|
|
54078
|
+
const fmt = session.platform.getFormatter();
|
|
54079
|
+
const message = `\uD83D\uDD04 ${fmt.formatBold("Update available:")} v${version}
|
|
54080
|
+
|
|
54081
|
+
` + `React: \uD83D\uDC4D to update now | \uD83D\uDC4E to defer for 1 hour
|
|
54082
|
+
` + fmt.formatItalic("Update will proceed automatically after timeout if no response");
|
|
54083
|
+
const post = await session.platform.createInteractivePost(message, ["\uD83D\uDC4D", "\uD83D\uDC4E"], session.threadId);
|
|
54084
|
+
this.registerPost(post.id, session.threadId);
|
|
54085
|
+
} catch (err) {
|
|
54086
|
+
log18.warn(`Failed to post ask message to ${threadId}: ${err}`);
|
|
54087
|
+
}
|
|
54088
|
+
}
|
|
54089
|
+
}
|
|
53931
54090
|
async shutdown(message) {
|
|
53932
54091
|
this.isShuttingDown = true;
|
|
53933
54092
|
if (this.cleanupTimer) {
|
|
@@ -63380,6 +63539,9 @@ async function handleMessage(client, session, post, user, options) {
|
|
|
63380
63539
|
[code("!kick @user"), "Remove an invited user"],
|
|
63381
63540
|
[code("!permissions interactive"), "Enable interactive permissions"],
|
|
63382
63541
|
[code("!approve"), "Approve pending plan (alternative to \uD83D\uDC4D reaction)"],
|
|
63542
|
+
[code("!update"), "Show auto-update status"],
|
|
63543
|
+
[code("!update now"), "Apply pending update immediately"],
|
|
63544
|
+
[code("!update defer"), "Defer pending update for 1 hour"],
|
|
63383
63545
|
[code("!escape"), "Interrupt current task (session stays active)"],
|
|
63384
63546
|
[code("!stop"), "Stop this session"],
|
|
63385
63547
|
[code("!kill"), "Emergency shutdown (kills ALL sessions, exits bot)"]
|
|
@@ -63430,6 +63592,18 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
63430
63592
|
await session.changeDirectory(threadRoot, cdMatch[1].trim(), username);
|
|
63431
63593
|
return;
|
|
63432
63594
|
}
|
|
63595
|
+
const updateMatch = content.trim().match(/^!update(?:\s+(now|defer))?$/i);
|
|
63596
|
+
if (updateMatch) {
|
|
63597
|
+
const subcommand = updateMatch[1]?.toLowerCase();
|
|
63598
|
+
if (subcommand === "now") {
|
|
63599
|
+
await session.forceUpdateNow(threadRoot, username);
|
|
63600
|
+
} else if (subcommand === "defer") {
|
|
63601
|
+
await session.deferUpdate(threadRoot, username);
|
|
63602
|
+
} else {
|
|
63603
|
+
await session.showUpdateStatus(threadRoot, username);
|
|
63604
|
+
}
|
|
63605
|
+
return;
|
|
63606
|
+
}
|
|
63433
63607
|
const worktreeMatch = content.match(/^!worktree\s+(\S+)(?:\s+(.*))?$/i);
|
|
63434
63608
|
if (worktreeMatch) {
|
|
63435
63609
|
const subcommand = worktreeMatch[1].toLowerCase();
|
|
@@ -63535,6 +63709,681 @@ Release notes not available. See ${formatter.formatLink("GitHub releases", "http
|
|
|
63535
63709
|
}
|
|
63536
63710
|
}
|
|
63537
63711
|
|
|
63712
|
+
// src/auto-update/manager.ts
|
|
63713
|
+
import { EventEmitter as EventEmitter9 } from "events";
|
|
63714
|
+
|
|
63715
|
+
// src/auto-update/checker.ts
|
|
63716
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
63717
|
+
var log19 = createLogger("checker");
|
|
63718
|
+
var PACKAGE_NAME2 = "claude-threads";
|
|
63719
|
+
function compareVersions(a, b) {
|
|
63720
|
+
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
63721
|
+
const partsB = b.replace(/^v/, "").split(".").map(Number);
|
|
63722
|
+
for (let i = 0;i < 3; i++) {
|
|
63723
|
+
const numA = partsA[i] || 0;
|
|
63724
|
+
const numB = partsB[i] || 0;
|
|
63725
|
+
if (numA > numB)
|
|
63726
|
+
return 1;
|
|
63727
|
+
if (numA < numB)
|
|
63728
|
+
return -1;
|
|
63729
|
+
}
|
|
63730
|
+
return 0;
|
|
63731
|
+
}
|
|
63732
|
+
async function fetchLatestVersion() {
|
|
63733
|
+
try {
|
|
63734
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME2}/latest`, {
|
|
63735
|
+
headers: {
|
|
63736
|
+
Accept: "application/json"
|
|
63737
|
+
}
|
|
63738
|
+
});
|
|
63739
|
+
if (!response.ok) {
|
|
63740
|
+
log19.warn(`Failed to fetch latest version: HTTP ${response.status}`);
|
|
63741
|
+
return null;
|
|
63742
|
+
}
|
|
63743
|
+
const data = await response.json();
|
|
63744
|
+
return data.version ?? null;
|
|
63745
|
+
} catch (err) {
|
|
63746
|
+
log19.warn(`Failed to fetch latest version: ${err}`);
|
|
63747
|
+
return null;
|
|
63748
|
+
}
|
|
63749
|
+
}
|
|
63750
|
+
|
|
63751
|
+
class UpdateChecker extends EventEmitter7 {
|
|
63752
|
+
config;
|
|
63753
|
+
checkInterval = null;
|
|
63754
|
+
lastCheck = null;
|
|
63755
|
+
lastUpdateInfo = null;
|
|
63756
|
+
isChecking = false;
|
|
63757
|
+
constructor(config) {
|
|
63758
|
+
super();
|
|
63759
|
+
this.config = config;
|
|
63760
|
+
}
|
|
63761
|
+
start() {
|
|
63762
|
+
if (!this.config.enabled) {
|
|
63763
|
+
log19.debug("Auto-update disabled, not starting checker");
|
|
63764
|
+
return;
|
|
63765
|
+
}
|
|
63766
|
+
setTimeout(() => {
|
|
63767
|
+
this.check().catch((err) => {
|
|
63768
|
+
log19.warn(`Initial update check failed: ${err}`);
|
|
63769
|
+
});
|
|
63770
|
+
}, 5000);
|
|
63771
|
+
const intervalMs = this.config.checkIntervalMinutes * 60 * 1000;
|
|
63772
|
+
this.checkInterval = setInterval(() => {
|
|
63773
|
+
this.check().catch((err) => {
|
|
63774
|
+
log19.warn(`Periodic update check failed: ${err}`);
|
|
63775
|
+
});
|
|
63776
|
+
}, intervalMs);
|
|
63777
|
+
log19.info(`\uD83D\uDD04 Update checker started (every ${this.config.checkIntervalMinutes} minutes)`);
|
|
63778
|
+
}
|
|
63779
|
+
stop() {
|
|
63780
|
+
if (this.checkInterval) {
|
|
63781
|
+
clearInterval(this.checkInterval);
|
|
63782
|
+
this.checkInterval = null;
|
|
63783
|
+
}
|
|
63784
|
+
log19.debug("Update checker stopped");
|
|
63785
|
+
}
|
|
63786
|
+
async check() {
|
|
63787
|
+
if (this.isChecking) {
|
|
63788
|
+
log19.debug("Check already in progress, skipping");
|
|
63789
|
+
return this.lastUpdateInfo;
|
|
63790
|
+
}
|
|
63791
|
+
this.isChecking = true;
|
|
63792
|
+
this.emit("check:start");
|
|
63793
|
+
try {
|
|
63794
|
+
log19.debug("Checking for updates...");
|
|
63795
|
+
const latestVersion2 = await fetchLatestVersion();
|
|
63796
|
+
if (!latestVersion2) {
|
|
63797
|
+
this.emit("check:complete", false);
|
|
63798
|
+
return null;
|
|
63799
|
+
}
|
|
63800
|
+
this.lastCheck = new Date;
|
|
63801
|
+
const currentVersion = VERSION;
|
|
63802
|
+
const hasUpdate = compareVersions(latestVersion2, currentVersion) > 0;
|
|
63803
|
+
if (hasUpdate) {
|
|
63804
|
+
const updateInfo = {
|
|
63805
|
+
available: true,
|
|
63806
|
+
currentVersion,
|
|
63807
|
+
latestVersion: latestVersion2,
|
|
63808
|
+
detectedAt: new Date
|
|
63809
|
+
};
|
|
63810
|
+
if (!this.lastUpdateInfo || this.lastUpdateInfo.latestVersion !== latestVersion2) {
|
|
63811
|
+
log19.info(`\uD83C\uDD95 Update available: v${currentVersion} \u2192 v${latestVersion2}`);
|
|
63812
|
+
this.lastUpdateInfo = updateInfo;
|
|
63813
|
+
this.emit("update", updateInfo);
|
|
63814
|
+
}
|
|
63815
|
+
this.emit("check:complete", true);
|
|
63816
|
+
return updateInfo;
|
|
63817
|
+
}
|
|
63818
|
+
log19.debug(`Up to date (v${currentVersion})`);
|
|
63819
|
+
this.emit("check:complete", false);
|
|
63820
|
+
return null;
|
|
63821
|
+
} catch (err) {
|
|
63822
|
+
log19.warn(`Update check failed: ${err}`);
|
|
63823
|
+
this.emit("check:error", err);
|
|
63824
|
+
return null;
|
|
63825
|
+
} finally {
|
|
63826
|
+
this.isChecking = false;
|
|
63827
|
+
}
|
|
63828
|
+
}
|
|
63829
|
+
getLastUpdateInfo() {
|
|
63830
|
+
return this.lastUpdateInfo;
|
|
63831
|
+
}
|
|
63832
|
+
getLastCheckTime() {
|
|
63833
|
+
return this.lastCheck;
|
|
63834
|
+
}
|
|
63835
|
+
updateConfig(config) {
|
|
63836
|
+
const oldInterval = this.config.checkIntervalMinutes;
|
|
63837
|
+
this.config = config;
|
|
63838
|
+
if (config.checkIntervalMinutes !== oldInterval && this.checkInterval) {
|
|
63839
|
+
this.stop();
|
|
63840
|
+
this.start();
|
|
63841
|
+
}
|
|
63842
|
+
}
|
|
63843
|
+
}
|
|
63844
|
+
|
|
63845
|
+
// src/auto-update/scheduler.ts
|
|
63846
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
63847
|
+
|
|
63848
|
+
// src/auto-update/types.ts
|
|
63849
|
+
var RESTART_EXIT_CODE = 42;
|
|
63850
|
+
var DEFAULT_CHECK_INTERVAL_MINUTES = 60;
|
|
63851
|
+
var DEFAULT_IDLE_TIMEOUT_MINUTES = 5;
|
|
63852
|
+
var DEFAULT_QUIET_TIMEOUT_MINUTES = 10;
|
|
63853
|
+
var DEFAULT_ASK_TIMEOUT_MINUTES = 30;
|
|
63854
|
+
var MIN_CHECK_INTERVAL_MINUTES = 5;
|
|
63855
|
+
var UPDATE_STATE_FILENAME = "update-state.json";
|
|
63856
|
+
var DEFAULT_AUTO_UPDATE_CONFIG = {
|
|
63857
|
+
enabled: true,
|
|
63858
|
+
checkIntervalMinutes: DEFAULT_CHECK_INTERVAL_MINUTES,
|
|
63859
|
+
autoRestartMode: "idle",
|
|
63860
|
+
idleTimeoutMinutes: DEFAULT_IDLE_TIMEOUT_MINUTES,
|
|
63861
|
+
quietTimeoutMinutes: DEFAULT_QUIET_TIMEOUT_MINUTES,
|
|
63862
|
+
scheduledWindow: {
|
|
63863
|
+
startHour: 2,
|
|
63864
|
+
endHour: 5
|
|
63865
|
+
},
|
|
63866
|
+
askTimeoutMinutes: DEFAULT_ASK_TIMEOUT_MINUTES
|
|
63867
|
+
};
|
|
63868
|
+
function mergeAutoUpdateConfig(userConfig) {
|
|
63869
|
+
if (!userConfig) {
|
|
63870
|
+
return { ...DEFAULT_AUTO_UPDATE_CONFIG };
|
|
63871
|
+
}
|
|
63872
|
+
return {
|
|
63873
|
+
enabled: userConfig.enabled ?? DEFAULT_AUTO_UPDATE_CONFIG.enabled,
|
|
63874
|
+
checkIntervalMinutes: Math.max(MIN_CHECK_INTERVAL_MINUTES, userConfig.checkIntervalMinutes ?? DEFAULT_AUTO_UPDATE_CONFIG.checkIntervalMinutes),
|
|
63875
|
+
autoRestartMode: userConfig.autoRestartMode ?? DEFAULT_AUTO_UPDATE_CONFIG.autoRestartMode,
|
|
63876
|
+
idleTimeoutMinutes: userConfig.idleTimeoutMinutes ?? DEFAULT_AUTO_UPDATE_CONFIG.idleTimeoutMinutes,
|
|
63877
|
+
quietTimeoutMinutes: userConfig.quietTimeoutMinutes ?? DEFAULT_AUTO_UPDATE_CONFIG.quietTimeoutMinutes,
|
|
63878
|
+
scheduledWindow: userConfig.scheduledWindow ?? DEFAULT_AUTO_UPDATE_CONFIG.scheduledWindow,
|
|
63879
|
+
askTimeoutMinutes: userConfig.askTimeoutMinutes ?? DEFAULT_AUTO_UPDATE_CONFIG.askTimeoutMinutes
|
|
63880
|
+
};
|
|
63881
|
+
}
|
|
63882
|
+
function isInScheduledWindow(window2) {
|
|
63883
|
+
const now = new Date;
|
|
63884
|
+
const hour = now.getHours();
|
|
63885
|
+
if (window2.startHour > window2.endHour) {
|
|
63886
|
+
return hour >= window2.startHour || hour < window2.endHour;
|
|
63887
|
+
}
|
|
63888
|
+
return hour >= window2.startHour && hour < window2.endHour;
|
|
63889
|
+
}
|
|
63890
|
+
|
|
63891
|
+
// src/auto-update/scheduler.ts
|
|
63892
|
+
var log20 = createLogger("scheduler");
|
|
63893
|
+
|
|
63894
|
+
class UpdateScheduler extends EventEmitter8 {
|
|
63895
|
+
config;
|
|
63896
|
+
getSessionActivity;
|
|
63897
|
+
getActiveThreadIds;
|
|
63898
|
+
postAskMessage;
|
|
63899
|
+
pendingUpdate = null;
|
|
63900
|
+
checkTimer = null;
|
|
63901
|
+
countdownTimer = null;
|
|
63902
|
+
idleStartTime = null;
|
|
63903
|
+
scheduledRestartAt = null;
|
|
63904
|
+
askApprovals = new Map;
|
|
63905
|
+
askStartTime = null;
|
|
63906
|
+
constructor(config, getSessionActivity, getActiveThreadIds, postAskMessage) {
|
|
63907
|
+
super();
|
|
63908
|
+
this.config = config;
|
|
63909
|
+
this.getSessionActivity = getSessionActivity;
|
|
63910
|
+
this.getActiveThreadIds = getActiveThreadIds;
|
|
63911
|
+
this.postAskMessage = postAskMessage;
|
|
63912
|
+
}
|
|
63913
|
+
scheduleUpdate(updateInfo) {
|
|
63914
|
+
this.pendingUpdate = updateInfo;
|
|
63915
|
+
if (this.config.autoRestartMode === "immediate") {
|
|
63916
|
+
log20.info("Immediate mode: triggering update now");
|
|
63917
|
+
this.emit("ready", updateInfo);
|
|
63918
|
+
return;
|
|
63919
|
+
}
|
|
63920
|
+
this.startChecking();
|
|
63921
|
+
}
|
|
63922
|
+
cancelSchedule() {
|
|
63923
|
+
this.stopChecking();
|
|
63924
|
+
this.pendingUpdate = null;
|
|
63925
|
+
this.idleStartTime = null;
|
|
63926
|
+
this.scheduledRestartAt = null;
|
|
63927
|
+
this.askApprovals.clear();
|
|
63928
|
+
this.askStartTime = null;
|
|
63929
|
+
log20.debug("Update schedule cancelled");
|
|
63930
|
+
}
|
|
63931
|
+
deferUpdate(minutes) {
|
|
63932
|
+
const deferUntil = new Date(Date.now() + minutes * 60 * 1000);
|
|
63933
|
+
this.scheduledRestartAt = null;
|
|
63934
|
+
this.idleStartTime = null;
|
|
63935
|
+
this.emit("deferred", deferUntil);
|
|
63936
|
+
log20.info(`Update deferred until ${deferUntil.toLocaleTimeString()}`);
|
|
63937
|
+
return deferUntil;
|
|
63938
|
+
}
|
|
63939
|
+
recordAskResponse(threadId, approved) {
|
|
63940
|
+
this.askApprovals.set(threadId, approved);
|
|
63941
|
+
log20.debug(`Thread ${threadId.substring(0, 8)} ${approved ? "approved" : "denied"} update`);
|
|
63942
|
+
this.checkAskCondition();
|
|
63943
|
+
}
|
|
63944
|
+
getScheduledRestartAt() {
|
|
63945
|
+
return this.scheduledRestartAt;
|
|
63946
|
+
}
|
|
63947
|
+
getPendingUpdate() {
|
|
63948
|
+
return this.pendingUpdate;
|
|
63949
|
+
}
|
|
63950
|
+
updateConfig(config) {
|
|
63951
|
+
this.config = config;
|
|
63952
|
+
}
|
|
63953
|
+
stop() {
|
|
63954
|
+
this.stopChecking();
|
|
63955
|
+
this.stopCountdown();
|
|
63956
|
+
}
|
|
63957
|
+
startChecking() {
|
|
63958
|
+
if (this.checkTimer)
|
|
63959
|
+
return;
|
|
63960
|
+
this.checkCondition();
|
|
63961
|
+
this.checkTimer = setInterval(() => this.checkCondition(), 1e4);
|
|
63962
|
+
log20.debug(`Started checking for ${this.config.autoRestartMode} condition`);
|
|
63963
|
+
}
|
|
63964
|
+
stopChecking() {
|
|
63965
|
+
if (this.checkTimer) {
|
|
63966
|
+
clearInterval(this.checkTimer);
|
|
63967
|
+
this.checkTimer = null;
|
|
63968
|
+
}
|
|
63969
|
+
}
|
|
63970
|
+
checkCondition() {
|
|
63971
|
+
if (!this.pendingUpdate)
|
|
63972
|
+
return;
|
|
63973
|
+
switch (this.config.autoRestartMode) {
|
|
63974
|
+
case "idle":
|
|
63975
|
+
this.checkIdleCondition();
|
|
63976
|
+
break;
|
|
63977
|
+
case "quiet":
|
|
63978
|
+
this.checkQuietCondition();
|
|
63979
|
+
break;
|
|
63980
|
+
case "scheduled":
|
|
63981
|
+
this.checkScheduledCondition();
|
|
63982
|
+
break;
|
|
63983
|
+
case "ask":
|
|
63984
|
+
this.checkAskCondition();
|
|
63985
|
+
break;
|
|
63986
|
+
}
|
|
63987
|
+
}
|
|
63988
|
+
checkIdleCondition() {
|
|
63989
|
+
const activity = this.getSessionActivity();
|
|
63990
|
+
if (activity.activeSessionCount === 0) {
|
|
63991
|
+
if (!this.idleStartTime) {
|
|
63992
|
+
this.idleStartTime = new Date;
|
|
63993
|
+
log20.debug("No active sessions, starting idle timer");
|
|
63994
|
+
}
|
|
63995
|
+
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
63996
|
+
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
63997
|
+
if (idleMs >= requiredMs) {
|
|
63998
|
+
log20.info(`Idle for ${this.config.idleTimeoutMinutes} minutes, triggering update`);
|
|
63999
|
+
this.triggerCountdown();
|
|
64000
|
+
}
|
|
64001
|
+
} else {
|
|
64002
|
+
if (this.idleStartTime) {
|
|
64003
|
+
log20.debug("Sessions became active, resetting idle timer");
|
|
64004
|
+
this.idleStartTime = null;
|
|
64005
|
+
}
|
|
64006
|
+
}
|
|
64007
|
+
}
|
|
64008
|
+
checkQuietCondition() {
|
|
64009
|
+
const activity = this.getSessionActivity();
|
|
64010
|
+
if (activity.lastActivityAt) {
|
|
64011
|
+
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
64012
|
+
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
64013
|
+
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
64014
|
+
log20.info(`Sessions quiet for ${this.config.quietTimeoutMinutes} minutes, triggering update`);
|
|
64015
|
+
this.triggerCountdown();
|
|
64016
|
+
}
|
|
64017
|
+
} else if (activity.activeSessionCount === 0) {
|
|
64018
|
+
if (!this.idleStartTime) {
|
|
64019
|
+
this.idleStartTime = new Date;
|
|
64020
|
+
}
|
|
64021
|
+
const idleMs = Date.now() - this.idleStartTime.getTime();
|
|
64022
|
+
const requiredMs = this.config.quietTimeoutMinutes * 60 * 1000;
|
|
64023
|
+
if (idleMs >= requiredMs) {
|
|
64024
|
+
log20.info("No sessions and quiet timeout reached, triggering update");
|
|
64025
|
+
this.triggerCountdown();
|
|
64026
|
+
}
|
|
64027
|
+
}
|
|
64028
|
+
}
|
|
64029
|
+
checkScheduledCondition() {
|
|
64030
|
+
if (!isInScheduledWindow(this.config.scheduledWindow)) {
|
|
64031
|
+
return;
|
|
64032
|
+
}
|
|
64033
|
+
const activity = this.getSessionActivity();
|
|
64034
|
+
if (activity.activeSessionCount === 0) {
|
|
64035
|
+
log20.info("Within scheduled window and no active sessions, triggering update");
|
|
64036
|
+
this.triggerCountdown();
|
|
64037
|
+
} else if (activity.lastActivityAt) {
|
|
64038
|
+
const quietMs = Date.now() - activity.lastActivityAt.getTime();
|
|
64039
|
+
const requiredMs = this.config.idleTimeoutMinutes * 60 * 1000;
|
|
64040
|
+
if (quietMs >= requiredMs && !activity.anySessionBusy) {
|
|
64041
|
+
log20.info("Within scheduled window and sessions quiet, triggering update");
|
|
64042
|
+
this.triggerCountdown();
|
|
64043
|
+
}
|
|
64044
|
+
}
|
|
64045
|
+
}
|
|
64046
|
+
checkAskCondition() {
|
|
64047
|
+
const threadIds = this.getActiveThreadIds();
|
|
64048
|
+
if (threadIds.length === 0) {
|
|
64049
|
+
log20.info("No active threads, proceeding with update");
|
|
64050
|
+
this.triggerCountdown();
|
|
64051
|
+
return;
|
|
64052
|
+
}
|
|
64053
|
+
if (!this.askStartTime && this.pendingUpdate) {
|
|
64054
|
+
this.askStartTime = new Date;
|
|
64055
|
+
this.postAskMessage(threadIds, this.pendingUpdate.latestVersion).catch((err) => {
|
|
64056
|
+
log20.warn(`Failed to post ask message: ${err}`);
|
|
64057
|
+
});
|
|
64058
|
+
return;
|
|
64059
|
+
}
|
|
64060
|
+
let approvals = 0;
|
|
64061
|
+
let denials = 0;
|
|
64062
|
+
for (const approved of this.askApprovals.values()) {
|
|
64063
|
+
if (approved)
|
|
64064
|
+
approvals++;
|
|
64065
|
+
else
|
|
64066
|
+
denials++;
|
|
64067
|
+
}
|
|
64068
|
+
if (approvals > threadIds.length / 2) {
|
|
64069
|
+
log20.info(`Majority approved (${approvals}/${threadIds.length}), triggering update`);
|
|
64070
|
+
this.triggerCountdown();
|
|
64071
|
+
return;
|
|
64072
|
+
}
|
|
64073
|
+
if (denials > threadIds.length / 2) {
|
|
64074
|
+
log20.info(`Majority denied (${denials}/${threadIds.length}), deferring update`);
|
|
64075
|
+
this.deferUpdate(60);
|
|
64076
|
+
return;
|
|
64077
|
+
}
|
|
64078
|
+
if (this.askStartTime) {
|
|
64079
|
+
const elapsedMs = Date.now() - this.askStartTime.getTime();
|
|
64080
|
+
const timeoutMs = this.config.askTimeoutMinutes * 60 * 1000;
|
|
64081
|
+
if (elapsedMs >= timeoutMs) {
|
|
64082
|
+
log20.info(`Ask timeout reached (${this.config.askTimeoutMinutes} min), triggering update`);
|
|
64083
|
+
this.triggerCountdown();
|
|
64084
|
+
}
|
|
64085
|
+
}
|
|
64086
|
+
}
|
|
64087
|
+
triggerCountdown() {
|
|
64088
|
+
if (!this.pendingUpdate)
|
|
64089
|
+
return;
|
|
64090
|
+
this.stopChecking();
|
|
64091
|
+
this.scheduledRestartAt = new Date(Date.now() + 60000);
|
|
64092
|
+
let secondsRemaining = 60;
|
|
64093
|
+
this.emit("countdown", secondsRemaining);
|
|
64094
|
+
this.countdownTimer = setInterval(() => {
|
|
64095
|
+
secondsRemaining--;
|
|
64096
|
+
this.emit("countdown", secondsRemaining);
|
|
64097
|
+
if (secondsRemaining <= 0) {
|
|
64098
|
+
this.stopCountdown();
|
|
64099
|
+
this.emit("ready", this.pendingUpdate);
|
|
64100
|
+
}
|
|
64101
|
+
}, 1000);
|
|
64102
|
+
log20.info("Update countdown started (60 seconds)");
|
|
64103
|
+
}
|
|
64104
|
+
stopCountdown() {
|
|
64105
|
+
if (this.countdownTimer) {
|
|
64106
|
+
clearInterval(this.countdownTimer);
|
|
64107
|
+
this.countdownTimer = null;
|
|
64108
|
+
}
|
|
64109
|
+
}
|
|
64110
|
+
}
|
|
64111
|
+
|
|
64112
|
+
// src/auto-update/installer.ts
|
|
64113
|
+
import { spawn as spawn5 } from "child_process";
|
|
64114
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
64115
|
+
import { dirname as dirname6, resolve as resolve6 } from "path";
|
|
64116
|
+
import { homedir as homedir4 } from "os";
|
|
64117
|
+
var log21 = createLogger("installer");
|
|
64118
|
+
var STATE_PATH = resolve6(homedir4(), ".config", "claude-threads", UPDATE_STATE_FILENAME);
|
|
64119
|
+
var PACKAGE_NAME3 = "claude-threads";
|
|
64120
|
+
function loadUpdateState() {
|
|
64121
|
+
try {
|
|
64122
|
+
if (existsSync11(STATE_PATH)) {
|
|
64123
|
+
const content = readFileSync8(STATE_PATH, "utf-8");
|
|
64124
|
+
return JSON.parse(content);
|
|
64125
|
+
}
|
|
64126
|
+
} catch (err) {
|
|
64127
|
+
log21.warn(`Failed to load update state: ${err}`);
|
|
64128
|
+
}
|
|
64129
|
+
return {};
|
|
64130
|
+
}
|
|
64131
|
+
function saveUpdateState(state) {
|
|
64132
|
+
try {
|
|
64133
|
+
const dir = dirname6(STATE_PATH);
|
|
64134
|
+
if (!existsSync11(dir)) {
|
|
64135
|
+
mkdirSync3(dir, { recursive: true });
|
|
64136
|
+
}
|
|
64137
|
+
writeFileSync4(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
64138
|
+
log21.debug("Update state saved");
|
|
64139
|
+
} catch (err) {
|
|
64140
|
+
log21.warn(`Failed to save update state: ${err}`);
|
|
64141
|
+
}
|
|
64142
|
+
}
|
|
64143
|
+
function clearUpdateState() {
|
|
64144
|
+
try {
|
|
64145
|
+
if (existsSync11(STATE_PATH)) {
|
|
64146
|
+
writeFileSync4(STATE_PATH, "{}", "utf-8");
|
|
64147
|
+
}
|
|
64148
|
+
} catch (err) {
|
|
64149
|
+
log21.warn(`Failed to clear update state: ${err}`);
|
|
64150
|
+
}
|
|
64151
|
+
}
|
|
64152
|
+
function checkJustUpdated() {
|
|
64153
|
+
const state = loadUpdateState();
|
|
64154
|
+
if (state.justUpdated && state.previousVersion) {
|
|
64155
|
+
saveUpdateState({
|
|
64156
|
+
...state,
|
|
64157
|
+
justUpdated: false
|
|
64158
|
+
});
|
|
64159
|
+
return {
|
|
64160
|
+
previousVersion: state.previousVersion,
|
|
64161
|
+
currentVersion: VERSION
|
|
64162
|
+
};
|
|
64163
|
+
}
|
|
64164
|
+
return null;
|
|
64165
|
+
}
|
|
64166
|
+
async function installVersion(version) {
|
|
64167
|
+
log21.info(`\uD83D\uDCE6 Installing ${PACKAGE_NAME3}@${version}...`);
|
|
64168
|
+
saveUpdateState({
|
|
64169
|
+
previousVersion: VERSION,
|
|
64170
|
+
targetVersion: version,
|
|
64171
|
+
startedAt: new Date().toISOString(),
|
|
64172
|
+
justUpdated: false
|
|
64173
|
+
});
|
|
64174
|
+
return new Promise((resolve7) => {
|
|
64175
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
64176
|
+
const child = spawn5(npmCmd, ["install", "-g", `${PACKAGE_NAME3}@${version}`], {
|
|
64177
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
64178
|
+
env: {
|
|
64179
|
+
...process.env,
|
|
64180
|
+
npm_config_progress: "false"
|
|
64181
|
+
}
|
|
64182
|
+
});
|
|
64183
|
+
let stdout = "";
|
|
64184
|
+
let stderr = "";
|
|
64185
|
+
child.stdout?.on("data", (data) => {
|
|
64186
|
+
stdout += data.toString();
|
|
64187
|
+
});
|
|
64188
|
+
child.stderr?.on("data", (data) => {
|
|
64189
|
+
stderr += data.toString();
|
|
64190
|
+
});
|
|
64191
|
+
child.on("close", (code) => {
|
|
64192
|
+
if (code === 0) {
|
|
64193
|
+
log21.info(`\u2705 Successfully installed ${PACKAGE_NAME3}@${version}`);
|
|
64194
|
+
saveUpdateState({
|
|
64195
|
+
previousVersion: VERSION,
|
|
64196
|
+
targetVersion: version,
|
|
64197
|
+
startedAt: new Date().toISOString(),
|
|
64198
|
+
justUpdated: true
|
|
64199
|
+
});
|
|
64200
|
+
resolve7({ success: true });
|
|
64201
|
+
} else {
|
|
64202
|
+
const errorMsg = stderr || stdout || `Exit code: ${code}`;
|
|
64203
|
+
log21.error(`\u274C Installation failed: ${errorMsg}`);
|
|
64204
|
+
clearUpdateState();
|
|
64205
|
+
resolve7({ success: false, error: errorMsg });
|
|
64206
|
+
}
|
|
64207
|
+
});
|
|
64208
|
+
child.on("error", (err) => {
|
|
64209
|
+
log21.error(`\u274C Failed to spawn npm: ${err}`);
|
|
64210
|
+
clearUpdateState();
|
|
64211
|
+
resolve7({ success: false, error: err.message });
|
|
64212
|
+
});
|
|
64213
|
+
setTimeout(() => {
|
|
64214
|
+
if (child.exitCode === null) {
|
|
64215
|
+
child.kill();
|
|
64216
|
+
log21.error("\u274C Installation timed out");
|
|
64217
|
+
clearUpdateState();
|
|
64218
|
+
resolve7({ success: false, error: "Installation timed out" });
|
|
64219
|
+
}
|
|
64220
|
+
}, 5 * 60 * 1000);
|
|
64221
|
+
});
|
|
64222
|
+
}
|
|
64223
|
+
function getRollbackInstructions(previousVersion) {
|
|
64224
|
+
return `To rollback to the previous version, run:
|
|
64225
|
+
npm install -g ${PACKAGE_NAME3}@${previousVersion}`;
|
|
64226
|
+
}
|
|
64227
|
+
|
|
64228
|
+
class UpdateInstaller {
|
|
64229
|
+
isInstalling = false;
|
|
64230
|
+
async install(updateInfo) {
|
|
64231
|
+
if (this.isInstalling) {
|
|
64232
|
+
return { success: false, error: "Installation already in progress" };
|
|
64233
|
+
}
|
|
64234
|
+
this.isInstalling = true;
|
|
64235
|
+
try {
|
|
64236
|
+
return await installVersion(updateInfo.latestVersion);
|
|
64237
|
+
} finally {
|
|
64238
|
+
this.isInstalling = false;
|
|
64239
|
+
}
|
|
64240
|
+
}
|
|
64241
|
+
isInProgress() {
|
|
64242
|
+
return this.isInstalling;
|
|
64243
|
+
}
|
|
64244
|
+
checkJustUpdated() {
|
|
64245
|
+
return checkJustUpdated();
|
|
64246
|
+
}
|
|
64247
|
+
getState() {
|
|
64248
|
+
return loadUpdateState();
|
|
64249
|
+
}
|
|
64250
|
+
clearState() {
|
|
64251
|
+
clearUpdateState();
|
|
64252
|
+
}
|
|
64253
|
+
}
|
|
64254
|
+
|
|
64255
|
+
// src/auto-update/manager.ts
|
|
64256
|
+
var log22 = createLogger("auto-update");
|
|
64257
|
+
|
|
64258
|
+
class AutoUpdateManager extends EventEmitter9 {
|
|
64259
|
+
config;
|
|
64260
|
+
callbacks;
|
|
64261
|
+
checker;
|
|
64262
|
+
scheduler;
|
|
64263
|
+
installer;
|
|
64264
|
+
state = {
|
|
64265
|
+
status: "idle"
|
|
64266
|
+
};
|
|
64267
|
+
deferredUntil = null;
|
|
64268
|
+
constructor(configOverride, callbacks) {
|
|
64269
|
+
super();
|
|
64270
|
+
this.config = mergeAutoUpdateConfig(configOverride);
|
|
64271
|
+
this.callbacks = callbacks;
|
|
64272
|
+
this.checker = new UpdateChecker(this.config);
|
|
64273
|
+
this.scheduler = new UpdateScheduler(this.config, callbacks.getSessionActivity, callbacks.getActiveThreadIds, callbacks.postAskMessage);
|
|
64274
|
+
this.installer = new UpdateInstaller;
|
|
64275
|
+
this.setupEventHandlers();
|
|
64276
|
+
}
|
|
64277
|
+
start() {
|
|
64278
|
+
if (!this.config.enabled) {
|
|
64279
|
+
log22.info("Auto-update is disabled");
|
|
64280
|
+
return;
|
|
64281
|
+
}
|
|
64282
|
+
const updateResult = this.installer.checkJustUpdated();
|
|
64283
|
+
if (updateResult) {
|
|
64284
|
+
log22.info(`\uD83C\uDF89 Updated from v${updateResult.previousVersion} to v${updateResult.currentVersion}`);
|
|
64285
|
+
this.callbacks.broadcastUpdate((fmt) => `\uD83C\uDF89 ${fmt.formatBold("Bot updated")} from v${updateResult.previousVersion} to v${updateResult.currentVersion}`).catch((err) => {
|
|
64286
|
+
log22.warn(`Failed to broadcast update notification: ${err}`);
|
|
64287
|
+
});
|
|
64288
|
+
}
|
|
64289
|
+
this.checker.start();
|
|
64290
|
+
log22.info(`\uD83D\uDD04 Auto-update manager started (mode: ${this.config.autoRestartMode})`);
|
|
64291
|
+
}
|
|
64292
|
+
stop() {
|
|
64293
|
+
this.checker.stop();
|
|
64294
|
+
this.scheduler.stop();
|
|
64295
|
+
log22.debug("Auto-update manager stopped");
|
|
64296
|
+
}
|
|
64297
|
+
getState() {
|
|
64298
|
+
return { ...this.state };
|
|
64299
|
+
}
|
|
64300
|
+
getConfig() {
|
|
64301
|
+
return { ...this.config };
|
|
64302
|
+
}
|
|
64303
|
+
async checkNow() {
|
|
64304
|
+
return this.checker.check();
|
|
64305
|
+
}
|
|
64306
|
+
async forceUpdate() {
|
|
64307
|
+
const updateInfo = this.state.updateInfo || await this.checker.check();
|
|
64308
|
+
if (!updateInfo) {
|
|
64309
|
+
log22.info("No update available");
|
|
64310
|
+
return;
|
|
64311
|
+
}
|
|
64312
|
+
log22.info("Forcing immediate update");
|
|
64313
|
+
await this.performUpdate(updateInfo);
|
|
64314
|
+
}
|
|
64315
|
+
deferUpdate(minutes = 60) {
|
|
64316
|
+
this.deferredUntil = this.scheduler.deferUpdate(minutes);
|
|
64317
|
+
this.updateStatus("deferred", `Deferred until ${this.deferredUntil.toLocaleTimeString()}`);
|
|
64318
|
+
}
|
|
64319
|
+
recordAskResponse(threadId, approved) {
|
|
64320
|
+
this.scheduler.recordAskResponse(threadId, approved);
|
|
64321
|
+
}
|
|
64322
|
+
isEnabled() {
|
|
64323
|
+
return this.config.enabled;
|
|
64324
|
+
}
|
|
64325
|
+
hasUpdate() {
|
|
64326
|
+
return this.state.updateInfo?.available ?? false;
|
|
64327
|
+
}
|
|
64328
|
+
getUpdateInfo() {
|
|
64329
|
+
return this.state.updateInfo;
|
|
64330
|
+
}
|
|
64331
|
+
getScheduledRestartAt() {
|
|
64332
|
+
return this.scheduler.getScheduledRestartAt();
|
|
64333
|
+
}
|
|
64334
|
+
setupEventHandlers() {
|
|
64335
|
+
this.checker.on("update", (info) => {
|
|
64336
|
+
this.state.updateInfo = info;
|
|
64337
|
+
this.updateStatus("available");
|
|
64338
|
+
this.emit("update:available", info);
|
|
64339
|
+
this.callbacks.refreshUI().catch(() => {});
|
|
64340
|
+
this.scheduler.scheduleUpdate(info);
|
|
64341
|
+
});
|
|
64342
|
+
this.scheduler.on("countdown", (seconds) => {
|
|
64343
|
+
this.emit("update:countdown", seconds);
|
|
64344
|
+
if (seconds === 60 || seconds === 30 || seconds === 10) {
|
|
64345
|
+
const latestVersion2 = this.state.updateInfo?.latestVersion;
|
|
64346
|
+
this.callbacks.broadcastUpdate((fmt) => `\u23F3 ${fmt.formatBold(`Restarting in ${seconds} seconds`)} for update to v${latestVersion2}`).catch(() => {});
|
|
64347
|
+
}
|
|
64348
|
+
});
|
|
64349
|
+
this.scheduler.on("ready", async (info) => {
|
|
64350
|
+
await this.performUpdate(info);
|
|
64351
|
+
});
|
|
64352
|
+
this.scheduler.on("deferred", (until) => {
|
|
64353
|
+
this.deferredUntil = until;
|
|
64354
|
+
this.updateStatus("deferred");
|
|
64355
|
+
});
|
|
64356
|
+
}
|
|
64357
|
+
async performUpdate(updateInfo) {
|
|
64358
|
+
this.updateStatus("installing");
|
|
64359
|
+
await this.callbacks.broadcastUpdate((fmt) => `\uD83D\uDCE6 ${fmt.formatBold("Installing update")} v${updateInfo.latestVersion}...`).catch(() => {});
|
|
64360
|
+
const result = await this.installer.install(updateInfo);
|
|
64361
|
+
if (result.success) {
|
|
64362
|
+
this.updateStatus("pending_restart");
|
|
64363
|
+
this.emit("update:restart", updateInfo.latestVersion);
|
|
64364
|
+
await this.callbacks.broadcastUpdate((fmt) => `\u2705 ${fmt.formatBold("Update installed")} - restarting now. ${fmt.formatItalic("Sessions will resume automatically.")}`).catch(() => {});
|
|
64365
|
+
await new Promise((resolve7) => setTimeout(resolve7, 1000));
|
|
64366
|
+
log22.info(`\uD83D\uDD04 Restarting for update to v${updateInfo.latestVersion}`);
|
|
64367
|
+
process.exit(RESTART_EXIT_CODE);
|
|
64368
|
+
} else {
|
|
64369
|
+
const errorMsg = result.error ?? "Unknown error";
|
|
64370
|
+
this.state.errorMessage = errorMsg;
|
|
64371
|
+
this.updateStatus("failed", errorMsg);
|
|
64372
|
+
this.emit("update:failed", errorMsg);
|
|
64373
|
+
const errorText = result.error;
|
|
64374
|
+
await this.callbacks.broadcastUpdate((fmt) => `\u274C ${fmt.formatBold("Update failed")}: ${errorText}
|
|
64375
|
+
${getRollbackInstructions(VERSION)}`).catch(() => {});
|
|
64376
|
+
}
|
|
64377
|
+
}
|
|
64378
|
+
updateStatus(status, message) {
|
|
64379
|
+
this.state.status = status;
|
|
64380
|
+
if (message) {
|
|
64381
|
+
this.state.errorMessage = status === "failed" ? message : undefined;
|
|
64382
|
+
}
|
|
64383
|
+
this.emit("update:status", status, message);
|
|
64384
|
+
this.callbacks.refreshUI().catch(() => {});
|
|
64385
|
+
}
|
|
64386
|
+
}
|
|
63538
64387
|
// src/index.ts
|
|
63539
64388
|
function createPlatformClient(config) {
|
|
63540
64389
|
switch (config.type) {
|
|
@@ -63572,12 +64421,56 @@ function wirePlatformEvents(platformId, client, session, ui) {
|
|
|
63572
64421
|
ui.addLog({ level: "error", component: platformId, message: String(e) });
|
|
63573
64422
|
});
|
|
63574
64423
|
}
|
|
63575
|
-
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();
|
|
64424
|
+
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").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").parse();
|
|
63576
64425
|
var opts = program.opts();
|
|
63577
64426
|
function hasRequiredCliArgs(args) {
|
|
63578
64427
|
return !!(args.url && args.token && args.channel);
|
|
63579
64428
|
}
|
|
63580
64429
|
async function main() {
|
|
64430
|
+
const shouldUseAutoRestart = async () => {
|
|
64431
|
+
if (opts.autoRestart === false)
|
|
64432
|
+
return false;
|
|
64433
|
+
if (opts.autoRestart === true)
|
|
64434
|
+
return true;
|
|
64435
|
+
if (await configExists()) {
|
|
64436
|
+
try {
|
|
64437
|
+
const config2 = loadConfigWithMigration();
|
|
64438
|
+
if (!config2)
|
|
64439
|
+
return false;
|
|
64440
|
+
return config2.autoUpdate?.enabled !== false;
|
|
64441
|
+
} catch {
|
|
64442
|
+
return false;
|
|
64443
|
+
}
|
|
64444
|
+
}
|
|
64445
|
+
return false;
|
|
64446
|
+
};
|
|
64447
|
+
if (await shouldUseAutoRestart()) {
|
|
64448
|
+
const { spawn: spawn6 } = await import("child_process");
|
|
64449
|
+
const { dirname: dirname7, resolve: resolve7 } = await import("path");
|
|
64450
|
+
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
64451
|
+
const __filename2 = fileURLToPath6(import.meta.url);
|
|
64452
|
+
const __dirname6 = dirname7(__filename2);
|
|
64453
|
+
const daemonPath = resolve7(__dirname6, "..", "bin", "claude-threads-daemon");
|
|
64454
|
+
const args = process.argv.slice(2).filter((arg) => arg !== "--auto-restart" && arg !== "--no-auto-restart").concat("--no-auto-restart");
|
|
64455
|
+
console.log("\uD83D\uDD04 Starting with auto-restart enabled...");
|
|
64456
|
+
console.log("");
|
|
64457
|
+
const binPath = __filename2;
|
|
64458
|
+
const child = spawn6(daemonPath, ["--restart-on-error", ...args], {
|
|
64459
|
+
stdio: "inherit",
|
|
64460
|
+
env: {
|
|
64461
|
+
...process.env,
|
|
64462
|
+
CLAUDE_THREADS_BIN: binPath
|
|
64463
|
+
}
|
|
64464
|
+
});
|
|
64465
|
+
child.on("error", (err) => {
|
|
64466
|
+
console.error(`Failed to start daemon: ${err.message}`);
|
|
64467
|
+
process.exit(1);
|
|
64468
|
+
});
|
|
64469
|
+
child.on("exit", (code) => {
|
|
64470
|
+
process.exit(code ?? 0);
|
|
64471
|
+
});
|
|
64472
|
+
return;
|
|
64473
|
+
}
|
|
63581
64474
|
checkForUpdates();
|
|
63582
64475
|
if (opts.debug) {
|
|
63583
64476
|
process.env.DEBUG = "1";
|
|
@@ -63634,6 +64527,7 @@ async function main() {
|
|
|
63634
64527
|
keepAliveEnabled
|
|
63635
64528
|
};
|
|
63636
64529
|
let sessionManager = null;
|
|
64530
|
+
let autoUpdateManager = null;
|
|
63637
64531
|
const ui = await startUI({
|
|
63638
64532
|
config: {
|
|
63639
64533
|
version: VERSION,
|
|
@@ -63748,6 +64642,31 @@ async function main() {
|
|
|
63748
64642
|
}
|
|
63749
64643
|
}));
|
|
63750
64644
|
await session.initialize();
|
|
64645
|
+
autoUpdateManager = new AutoUpdateManager(config.autoUpdate, {
|
|
64646
|
+
getSessionActivity: () => session.getActivityInfo(),
|
|
64647
|
+
getActiveThreadIds: () => session.getActiveThreadIds(),
|
|
64648
|
+
broadcastUpdate: (msg) => session.broadcastToAll(msg),
|
|
64649
|
+
postAskMessage: (ids, ver) => session.postUpdateAskMessage(ids, ver),
|
|
64650
|
+
refreshUI: () => session.updateAllStickyMessages()
|
|
64651
|
+
});
|
|
64652
|
+
session.setAutoUpdateManager(autoUpdateManager);
|
|
64653
|
+
autoUpdateManager.on("update:available", (info) => {
|
|
64654
|
+
ui.addLog({ level: "info", component: "update", message: `\uD83C\uDD95 Update available: v${info.currentVersion} \u2192 v${info.latestVersion}` });
|
|
64655
|
+
});
|
|
64656
|
+
autoUpdateManager.on("update:countdown", (seconds) => {
|
|
64657
|
+
if (seconds === 60 || seconds === 30 || seconds === 10 || seconds <= 5) {
|
|
64658
|
+
ui.addLog({ level: "info", component: "update", message: `\uD83D\uDD04 Restarting in ${seconds} seconds...` });
|
|
64659
|
+
}
|
|
64660
|
+
});
|
|
64661
|
+
autoUpdateManager.on("update:status", (status, message) => {
|
|
64662
|
+
if (message) {
|
|
64663
|
+
ui.addLog({ level: "info", component: "update", message: `\uD83D\uDD04 ${status}: ${message}` });
|
|
64664
|
+
}
|
|
64665
|
+
});
|
|
64666
|
+
autoUpdateManager.on("update:failed", (error) => {
|
|
64667
|
+
ui.addLog({ level: "error", component: "update", message: `\u274C Update failed: ${error}` });
|
|
64668
|
+
});
|
|
64669
|
+
autoUpdateManager.start();
|
|
63751
64670
|
ui.setReady();
|
|
63752
64671
|
let isShuttingDown2 = false;
|
|
63753
64672
|
const shutdown = async (_signal) => {
|
|
@@ -63755,7 +64674,7 @@ async function main() {
|
|
|
63755
64674
|
return;
|
|
63756
64675
|
isShuttingDown2 = true;
|
|
63757
64676
|
ui.setShuttingDown();
|
|
63758
|
-
await new Promise((
|
|
64677
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
63759
64678
|
session.setShuttingDown();
|
|
63760
64679
|
await session.updateAllStickyMessages();
|
|
63761
64680
|
const activeCount = session.getActiveThreadIds().length;
|
|
@@ -63764,6 +64683,7 @@ async function main() {
|
|
|
63764
64683
|
await session.postShutdownMessages();
|
|
63765
64684
|
}
|
|
63766
64685
|
await session.killAllSessions();
|
|
64686
|
+
autoUpdateManager?.stop();
|
|
63767
64687
|
for (const client of platforms.values()) {
|
|
63768
64688
|
client.disconnect();
|
|
63769
64689
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-threads",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"claude-threads": "./dist/index.js",
|
|
9
|
-
"claude-threads-mcp": "./dist/mcp/permission-server.js"
|
|
9
|
+
"claude-threads-mcp": "./dist/mcp/permission-server.js",
|
|
10
|
+
"claude-threads-daemon": "./bin/claude-threads-daemon"
|
|
10
11
|
},
|
|
11
12
|
"scripts": {
|
|
12
13
|
"dev": "bun --watch src/index.ts",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"homepage": "https://github.com/anneschuth/claude-threads#readme",
|
|
54
55
|
"files": [
|
|
55
56
|
"dist",
|
|
57
|
+
"bin",
|
|
56
58
|
"README.md",
|
|
57
59
|
"CHANGELOG.md",
|
|
58
60
|
"LICENSE",
|