claude-threads 0.14.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/LICENSE +190 -21
- package/README.md +9 -5
- package/dist/index.js +20397 -387
- package/dist/mcp/permission-server.js +34039 -139
- package/package.json +16 -20
- package/dist/changelog.d.ts +0 -20
- package/dist/changelog.js +0 -134
- package/dist/claude/cli.d.ts +0 -50
- package/dist/claude/cli.js +0 -181
- package/dist/config/migration.d.ts +0 -45
- package/dist/config/migration.js +0 -35
- package/dist/config.d.ts +0 -21
- package/dist/config.js +0 -7
- package/dist/git/worktree.d.ts +0 -46
- package/dist/git/worktree.js +0 -228
- package/dist/index.d.ts +0 -2
- package/dist/logo.d.ts +0 -14
- package/dist/logo.js +0 -41
- package/dist/mattermost/api.d.ts +0 -85
- package/dist/mattermost/api.js +0 -124
- package/dist/mattermost/api.test.d.ts +0 -1
- package/dist/mattermost/api.test.js +0 -319
- package/dist/mcp/permission-server.d.ts +0 -2
- package/dist/onboarding.d.ts +0 -1
- package/dist/onboarding.js +0 -318
- package/dist/persistence/session-store.d.ts +0 -71
- package/dist/persistence/session-store.js +0 -152
- package/dist/platform/client.d.ts +0 -140
- package/dist/platform/client.js +0 -1
- package/dist/platform/formatter.d.ts +0 -74
- package/dist/platform/formatter.js +0 -1
- package/dist/platform/index.d.ts +0 -11
- package/dist/platform/index.js +0 -8
- package/dist/platform/mattermost/client.d.ts +0 -70
- package/dist/platform/mattermost/client.js +0 -404
- package/dist/platform/mattermost/formatter.d.ts +0 -20
- package/dist/platform/mattermost/formatter.js +0 -46
- package/dist/platform/mattermost/permission-api.d.ts +0 -10
- package/dist/platform/mattermost/permission-api.js +0 -139
- package/dist/platform/mattermost/types.d.ts +0 -71
- package/dist/platform/mattermost/types.js +0 -1
- package/dist/platform/permission-api-factory.d.ts +0 -11
- package/dist/platform/permission-api-factory.js +0 -21
- package/dist/platform/permission-api.d.ts +0 -67
- package/dist/platform/permission-api.js +0 -8
- package/dist/platform/types.d.ts +0 -70
- package/dist/platform/types.js +0 -7
- package/dist/session/commands.d.ts +0 -52
- package/dist/session/commands.js +0 -323
- package/dist/session/events.d.ts +0 -25
- package/dist/session/events.js +0 -368
- package/dist/session/index.d.ts +0 -7
- package/dist/session/index.js +0 -6
- package/dist/session/lifecycle.d.ts +0 -70
- package/dist/session/lifecycle.js +0 -456
- package/dist/session/manager.d.ts +0 -96
- package/dist/session/manager.js +0 -537
- package/dist/session/reactions.d.ts +0 -25
- package/dist/session/reactions.js +0 -151
- package/dist/session/streaming.d.ts +0 -47
- package/dist/session/streaming.js +0 -152
- package/dist/session/types.d.ts +0 -78
- package/dist/session/types.js +0 -9
- package/dist/session/worktree.d.ts +0 -56
- package/dist/session/worktree.js +0 -339
- package/dist/update-notifier.d.ts +0 -3
- package/dist/update-notifier.js +0 -41
- package/dist/utils/emoji.d.ts +0 -43
- package/dist/utils/emoji.js +0 -65
- package/dist/utils/emoji.test.d.ts +0 -1
- package/dist/utils/emoji.test.js +0 -131
- package/dist/utils/logger.d.ts +0 -34
- package/dist/utils/logger.js +0 -42
- package/dist/utils/logger.test.d.ts +0 -1
- package/dist/utils/logger.test.js +0 -121
- package/dist/utils/tool-formatter.d.ts +0 -53
- package/dist/utils/tool-formatter.js +0 -252
- package/dist/utils/tool-formatter.test.d.ts +0 -1
- package/dist/utils/tool-formatter.test.js +0 -372
package/dist/session/worktree.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git worktree management utilities
|
|
3
|
-
*
|
|
4
|
-
* Handles worktree prompts, creation, switching, and cleanup.
|
|
5
|
-
*/
|
|
6
|
-
import { isGitRepository, getRepositoryRoot, hasUncommittedChanges, listWorktrees as listGitWorktrees, createWorktree as createGitWorktree, removeWorktree as removeGitWorktree, getWorktreeDir, findWorktreeByBranch, isValidBranchName, } from '../git/worktree.js';
|
|
7
|
-
import { ClaudeCli } from '../claude/cli.js';
|
|
8
|
-
import { randomUUID } from 'crypto';
|
|
9
|
-
/**
|
|
10
|
-
* Check if we should prompt the user to create a worktree.
|
|
11
|
-
* Returns the reason for prompting, or null if we shouldn't prompt.
|
|
12
|
-
*/
|
|
13
|
-
export async function shouldPromptForWorktree(session, worktreeMode, hasOtherSessionInRepo) {
|
|
14
|
-
// Skip if worktree mode is off
|
|
15
|
-
if (worktreeMode === 'off')
|
|
16
|
-
return null;
|
|
17
|
-
// Skip if user disabled prompts for this session
|
|
18
|
-
if (session.worktreePromptDisabled)
|
|
19
|
-
return null;
|
|
20
|
-
// Skip if already in a worktree
|
|
21
|
-
if (session.worktreeInfo)
|
|
22
|
-
return null;
|
|
23
|
-
// Check if we're in a git repository
|
|
24
|
-
const isRepo = await isGitRepository(session.workingDir);
|
|
25
|
-
if (!isRepo)
|
|
26
|
-
return null;
|
|
27
|
-
// For 'require' mode, always prompt
|
|
28
|
-
if (worktreeMode === 'require') {
|
|
29
|
-
return 'require';
|
|
30
|
-
}
|
|
31
|
-
// For 'prompt' mode, check conditions
|
|
32
|
-
// Condition 1: uncommitted changes
|
|
33
|
-
const hasChanges = await hasUncommittedChanges(session.workingDir);
|
|
34
|
-
if (hasChanges)
|
|
35
|
-
return 'uncommitted';
|
|
36
|
-
// Condition 2: another session using the same repo
|
|
37
|
-
const repoRoot = await getRepositoryRoot(session.workingDir);
|
|
38
|
-
const hasConcurrent = hasOtherSessionInRepo(repoRoot, session.threadId);
|
|
39
|
-
if (hasConcurrent)
|
|
40
|
-
return 'concurrent';
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Post the worktree prompt message to the user.
|
|
45
|
-
*/
|
|
46
|
-
export async function postWorktreePrompt(session, reason, registerPost) {
|
|
47
|
-
let message;
|
|
48
|
-
switch (reason) {
|
|
49
|
-
case 'uncommitted':
|
|
50
|
-
message = `🌿 **This repo has uncommitted changes.**\n` +
|
|
51
|
-
`Reply with a branch name to work in an isolated worktree, or react with ❌ to continue in the main repo.`;
|
|
52
|
-
break;
|
|
53
|
-
case 'concurrent':
|
|
54
|
-
message = `⚠️ **Another session is already using this repo.**\n` +
|
|
55
|
-
`Reply with a branch name to work in an isolated worktree, or react with ❌ to continue anyway.`;
|
|
56
|
-
break;
|
|
57
|
-
case 'require':
|
|
58
|
-
message = `🌿 **This deployment requires working in a worktree.**\n` +
|
|
59
|
-
`Please reply with a branch name to continue.`;
|
|
60
|
-
break;
|
|
61
|
-
default:
|
|
62
|
-
message = `🌿 **Would you like to work in an isolated worktree?**\n` +
|
|
63
|
-
`Reply with a branch name, or react with ❌ to continue in the main repo.`;
|
|
64
|
-
}
|
|
65
|
-
// Create post with ❌ reaction option (except for 'require' mode)
|
|
66
|
-
// Use 'x' emoji name, not Unicode ❌ character
|
|
67
|
-
const reactionOptions = reason === 'require' ? [] : ['x'];
|
|
68
|
-
const post = await session.platform.createInteractivePost(message, reactionOptions, session.threadId);
|
|
69
|
-
// Track the post for reaction handling
|
|
70
|
-
session.worktreePromptPostId = post.id;
|
|
71
|
-
registerPost(post.id, session.threadId);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Handle user providing a branch name in response to worktree prompt.
|
|
75
|
-
* Returns true if handled (whether successful or not).
|
|
76
|
-
*/
|
|
77
|
-
export async function handleWorktreeBranchResponse(session, branchName, username, createAndSwitch) {
|
|
78
|
-
if (!session.pendingWorktreePrompt)
|
|
79
|
-
return false;
|
|
80
|
-
// Only session owner can respond
|
|
81
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
// Validate branch name
|
|
85
|
-
if (!isValidBranchName(branchName)) {
|
|
86
|
-
await session.platform.createPost(`❌ Invalid branch name: \`${branchName}\`. Please provide a valid git branch name.`, session.threadId);
|
|
87
|
-
return true; // We handled it, but need another response
|
|
88
|
-
}
|
|
89
|
-
// Create and switch to worktree
|
|
90
|
-
await createAndSwitch(session.threadId, branchName, username);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Handle ❌ reaction on worktree prompt - skip worktree and continue in main repo.
|
|
95
|
-
*/
|
|
96
|
-
export async function handleWorktreeSkip(session, username, persistSession, startTyping) {
|
|
97
|
-
if (!session.pendingWorktreePrompt)
|
|
98
|
-
return;
|
|
99
|
-
// Only session owner can skip
|
|
100
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
// Update the prompt post
|
|
104
|
-
if (session.worktreePromptPostId) {
|
|
105
|
-
try {
|
|
106
|
-
await session.platform.updatePost(session.worktreePromptPostId, `✅ Continuing in main repo (skipped by @${username})`);
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
console.error(' ⚠️ Failed to update worktree prompt:', err);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// Clear pending state
|
|
113
|
-
session.pendingWorktreePrompt = false;
|
|
114
|
-
session.worktreePromptPostId = undefined;
|
|
115
|
-
const queuedPrompt = session.queuedPrompt;
|
|
116
|
-
session.queuedPrompt = undefined;
|
|
117
|
-
// Persist updated state
|
|
118
|
-
persistSession(session);
|
|
119
|
-
// Now send the queued message to Claude
|
|
120
|
-
if (queuedPrompt && session.claude.isRunning()) {
|
|
121
|
-
session.claude.sendMessage(queuedPrompt);
|
|
122
|
-
startTyping(session);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Create a new worktree and switch the session to it.
|
|
127
|
-
*/
|
|
128
|
-
export async function createAndSwitchToWorktree(session, branch, username, options) {
|
|
129
|
-
// Only session owner or admins can manage worktrees
|
|
130
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
131
|
-
await session.platform.createPost(`⚠️ Only @${session.startedBy} or allowed users can manage worktrees`, session.threadId);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
// Check if we're in a git repo
|
|
135
|
-
const isRepo = await isGitRepository(session.workingDir);
|
|
136
|
-
if (!isRepo) {
|
|
137
|
-
await session.platform.createPost(`❌ Current directory is not a git repository`, session.threadId);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
// Get repo root
|
|
141
|
-
const repoRoot = await getRepositoryRoot(session.workingDir);
|
|
142
|
-
// Check if worktree already exists for this branch
|
|
143
|
-
const existing = await findWorktreeByBranch(repoRoot, branch);
|
|
144
|
-
if (existing && !existing.isMain) {
|
|
145
|
-
await session.platform.createPost(`⚠️ Worktree for branch \`${branch}\` already exists at \`${existing.path}\`. Use \`!worktree switch ${branch}\` to switch to it.`, session.threadId);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const shortId = session.threadId.substring(0, 8);
|
|
149
|
-
console.log(` 🌿 Session (${shortId}…) creating worktree for branch ${branch}`);
|
|
150
|
-
// Generate worktree path
|
|
151
|
-
const worktreePath = getWorktreeDir(repoRoot, branch);
|
|
152
|
-
try {
|
|
153
|
-
// Create the worktree
|
|
154
|
-
await createGitWorktree(repoRoot, branch, worktreePath);
|
|
155
|
-
// Update the prompt post if it exists
|
|
156
|
-
if (session.worktreePromptPostId) {
|
|
157
|
-
try {
|
|
158
|
-
await session.platform.updatePost(session.worktreePromptPostId, `✅ Created worktree for \`${branch}\``);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
console.error(' ⚠️ Failed to update worktree prompt:', err);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Clear pending state
|
|
165
|
-
const wasPending = session.pendingWorktreePrompt;
|
|
166
|
-
session.pendingWorktreePrompt = false;
|
|
167
|
-
session.worktreePromptPostId = undefined;
|
|
168
|
-
const queuedPrompt = session.queuedPrompt;
|
|
169
|
-
session.queuedPrompt = undefined;
|
|
170
|
-
// Store worktree info
|
|
171
|
-
session.worktreeInfo = {
|
|
172
|
-
repoRoot,
|
|
173
|
-
worktreePath,
|
|
174
|
-
branch,
|
|
175
|
-
};
|
|
176
|
-
// Update working directory
|
|
177
|
-
session.workingDir = worktreePath;
|
|
178
|
-
// If Claude is already running, restart it in the new directory
|
|
179
|
-
if (session.claude.isRunning()) {
|
|
180
|
-
options.stopTyping(session);
|
|
181
|
-
session.isRestarting = true;
|
|
182
|
-
session.claude.kill();
|
|
183
|
-
// Flush any pending content
|
|
184
|
-
await options.flush(session);
|
|
185
|
-
session.currentPostId = null;
|
|
186
|
-
session.pendingContent = '';
|
|
187
|
-
// Generate new session ID for fresh start in new directory
|
|
188
|
-
// (Claude CLI sessions are tied to working directory, can't resume across directories)
|
|
189
|
-
const newSessionId = randomUUID();
|
|
190
|
-
session.claudeSessionId = newSessionId;
|
|
191
|
-
// Create new CLI with new working directory
|
|
192
|
-
const cliOptions = {
|
|
193
|
-
workingDir: worktreePath,
|
|
194
|
-
threadId: session.threadId,
|
|
195
|
-
skipPermissions: options.skipPermissions || !session.forceInteractivePermissions,
|
|
196
|
-
sessionId: newSessionId,
|
|
197
|
-
resume: false, // Fresh start - can't resume across directories
|
|
198
|
-
chrome: options.chromeEnabled,
|
|
199
|
-
platformConfig: session.platform.getMcpConfig(),
|
|
200
|
-
};
|
|
201
|
-
session.claude = new ClaudeCli(cliOptions);
|
|
202
|
-
// Rebind event handlers (use sessionId which is the composite key)
|
|
203
|
-
session.claude.on('event', (e) => options.handleEvent(session.sessionId, e));
|
|
204
|
-
session.claude.on('exit', (code) => options.handleExit(session.sessionId, code));
|
|
205
|
-
// Start the new CLI
|
|
206
|
-
session.claude.start();
|
|
207
|
-
}
|
|
208
|
-
// Update session header
|
|
209
|
-
await options.updateSessionHeader(session);
|
|
210
|
-
// Post confirmation
|
|
211
|
-
const shortWorktreePath = worktreePath.replace(process.env.HOME || '', '~');
|
|
212
|
-
await session.platform.createPost(`✅ **Created worktree** for branch \`${branch}\`\n📁 Working directory: \`${shortWorktreePath}\`\n*Claude Code restarted in the new worktree*`, session.threadId);
|
|
213
|
-
// Update activity and persist
|
|
214
|
-
session.lastActivityAt = new Date();
|
|
215
|
-
session.timeoutWarningPosted = false;
|
|
216
|
-
options.persistSession(session);
|
|
217
|
-
// If there was a queued prompt (from initial session start), send it now
|
|
218
|
-
if (wasPending && queuedPrompt && session.claude.isRunning()) {
|
|
219
|
-
session.claude.sendMessage(queuedPrompt);
|
|
220
|
-
options.startTyping(session);
|
|
221
|
-
}
|
|
222
|
-
console.log(` 🌿 Session (${shortId}…) switched to worktree ${branch} at ${shortWorktreePath}`);
|
|
223
|
-
}
|
|
224
|
-
catch (err) {
|
|
225
|
-
console.error(` ❌ Failed to create worktree:`, err);
|
|
226
|
-
await session.platform.createPost(`❌ Failed to create worktree: ${err instanceof Error ? err.message : String(err)}`, session.threadId);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Switch to an existing worktree.
|
|
231
|
-
*/
|
|
232
|
-
export async function switchToWorktree(session, branchOrPath, username, changeDirectory) {
|
|
233
|
-
// Only session owner or admins can manage worktrees
|
|
234
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
235
|
-
await session.platform.createPost(`⚠️ Only @${session.startedBy} or allowed users can manage worktrees`, session.threadId);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
// Get current repo root
|
|
239
|
-
const repoRoot = session.worktreeInfo?.repoRoot || await getRepositoryRoot(session.workingDir);
|
|
240
|
-
// Find the worktree
|
|
241
|
-
const worktrees = await listGitWorktrees(repoRoot);
|
|
242
|
-
const target = worktrees.find(wt => wt.branch === branchOrPath ||
|
|
243
|
-
wt.path === branchOrPath ||
|
|
244
|
-
wt.path.endsWith(branchOrPath));
|
|
245
|
-
if (!target) {
|
|
246
|
-
await session.platform.createPost(`❌ Worktree not found: \`${branchOrPath}\`. Use \`!worktree list\` to see available worktrees.`, session.threadId);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
// Use changeDirectory logic to switch
|
|
250
|
-
await changeDirectory(session.threadId, target.path, username);
|
|
251
|
-
// Update worktree info
|
|
252
|
-
session.worktreeInfo = {
|
|
253
|
-
repoRoot,
|
|
254
|
-
worktreePath: target.path,
|
|
255
|
-
branch: target.branch,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* List all worktrees for the current repository.
|
|
260
|
-
*/
|
|
261
|
-
export async function listWorktreesCommand(session) {
|
|
262
|
-
// Check if we're in a git repo
|
|
263
|
-
const isRepo = await isGitRepository(session.workingDir);
|
|
264
|
-
if (!isRepo) {
|
|
265
|
-
await session.platform.createPost(`❌ Current directory is not a git repository`, session.threadId);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
// Get repo root (either from worktree info or current dir)
|
|
269
|
-
const repoRoot = session.worktreeInfo?.repoRoot || await getRepositoryRoot(session.workingDir);
|
|
270
|
-
const worktrees = await listGitWorktrees(repoRoot);
|
|
271
|
-
if (worktrees.length === 0) {
|
|
272
|
-
await session.platform.createPost(`📋 No worktrees found for this repository`, session.threadId);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const shortRepoRoot = repoRoot.replace(process.env.HOME || '', '~');
|
|
276
|
-
let message = `📋 **Worktrees for** \`${shortRepoRoot}\`:\n\n`;
|
|
277
|
-
for (const wt of worktrees) {
|
|
278
|
-
const shortPath = wt.path.replace(process.env.HOME || '', '~');
|
|
279
|
-
const isCurrent = session.workingDir === wt.path;
|
|
280
|
-
const marker = isCurrent ? ' ← current' : '';
|
|
281
|
-
const label = wt.isMain ? '(main repository)' : '';
|
|
282
|
-
message += `• \`${wt.branch}\` → \`${shortPath}\` ${label}${marker}\n`;
|
|
283
|
-
}
|
|
284
|
-
await session.platform.createPost(message, session.threadId);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Remove a worktree.
|
|
288
|
-
*/
|
|
289
|
-
export async function removeWorktreeCommand(session, branchOrPath, username) {
|
|
290
|
-
// Only session owner or admins can manage worktrees
|
|
291
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
292
|
-
await session.platform.createPost(`⚠️ Only @${session.startedBy} or allowed users can manage worktrees`, session.threadId);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
// Get current repo root
|
|
296
|
-
const repoRoot = session.worktreeInfo?.repoRoot || await getRepositoryRoot(session.workingDir);
|
|
297
|
-
// Find the worktree
|
|
298
|
-
const worktrees = await listGitWorktrees(repoRoot);
|
|
299
|
-
const target = worktrees.find(wt => wt.branch === branchOrPath ||
|
|
300
|
-
wt.path === branchOrPath ||
|
|
301
|
-
wt.path.endsWith(branchOrPath));
|
|
302
|
-
if (!target) {
|
|
303
|
-
await session.platform.createPost(`❌ Worktree not found: \`${branchOrPath}\`. Use \`!worktree list\` to see available worktrees.`, session.threadId);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// Can't remove the main repository
|
|
307
|
-
if (target.isMain) {
|
|
308
|
-
await session.platform.createPost(`❌ Cannot remove the main repository. Use \`!worktree remove\` only for worktrees.`, session.threadId);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
// Can't remove the current working directory
|
|
312
|
-
if (session.workingDir === target.path) {
|
|
313
|
-
await session.platform.createPost(`❌ Cannot remove the current working directory. Switch to another worktree first.`, session.threadId);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
await removeGitWorktree(repoRoot, target.path);
|
|
318
|
-
const shortPath = target.path.replace(process.env.HOME || '', '~');
|
|
319
|
-
await session.platform.createPost(`✅ Removed worktree \`${target.branch}\` at \`${shortPath}\``, session.threadId);
|
|
320
|
-
console.log(` 🗑️ Removed worktree ${target.branch} at ${shortPath}`);
|
|
321
|
-
}
|
|
322
|
-
catch (err) {
|
|
323
|
-
console.error(` ❌ Failed to remove worktree:`, err);
|
|
324
|
-
await session.platform.createPost(`❌ Failed to remove worktree: ${err instanceof Error ? err.message : String(err)}`, session.threadId);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Disable worktree prompts for a session.
|
|
329
|
-
*/
|
|
330
|
-
export async function disableWorktreePrompt(session, username, persistSession) {
|
|
331
|
-
// Only session owner or admins can manage worktrees
|
|
332
|
-
if (session.startedBy !== username && !session.platform.isUserAllowed(username)) {
|
|
333
|
-
await session.platform.createPost(`⚠️ Only @${session.startedBy} or allowed users can manage worktrees`, session.threadId);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
session.worktreePromptDisabled = true;
|
|
337
|
-
persistSession(session);
|
|
338
|
-
await session.platform.createPost(`✅ Worktree prompts disabled for this session`, session.threadId);
|
|
339
|
-
}
|
package/dist/update-notifier.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import updateNotifier from 'update-notifier';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import semver from 'semver';
|
|
6
|
-
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
7
|
-
let cachedUpdateInfo;
|
|
8
|
-
export function checkForUpdates() {
|
|
9
|
-
if (process.env.NO_UPDATE_NOTIFIER)
|
|
10
|
-
return;
|
|
11
|
-
try {
|
|
12
|
-
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
13
|
-
const notifier = updateNotifier({
|
|
14
|
-
pkg,
|
|
15
|
-
updateCheckInterval: 1000 * 60 * 30, // Check every 30 minutes
|
|
16
|
-
});
|
|
17
|
-
// Cache for Mattermost notifications
|
|
18
|
-
cachedUpdateInfo = notifier.update;
|
|
19
|
-
// Show CLI notification
|
|
20
|
-
notifier.notify({
|
|
21
|
-
message: `Update available: {currentVersion} → {latestVersion}
|
|
22
|
-
Run: npm install -g claude-threads`,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// Silently fail - update checking is not critical
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Returns update info if available, for posting to Mattermost
|
|
30
|
-
// Only returns if latest > current (handles stale cache edge case)
|
|
31
|
-
export function getUpdateInfo() {
|
|
32
|
-
if (!cachedUpdateInfo)
|
|
33
|
-
return undefined;
|
|
34
|
-
// Sanity check: only show update if latest is actually newer
|
|
35
|
-
const current = cachedUpdateInfo.current;
|
|
36
|
-
const latest = cachedUpdateInfo.latest;
|
|
37
|
-
if (current && latest && semver.gte(current, latest)) {
|
|
38
|
-
return undefined; // Current is same or newer, no update needed
|
|
39
|
-
}
|
|
40
|
-
return cachedUpdateInfo;
|
|
41
|
-
}
|
package/dist/utils/emoji.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emoji constants and helpers for chat platform reactions
|
|
3
|
-
*
|
|
4
|
-
* Platform-agnostic emoji utilities used across session management,
|
|
5
|
-
* permission handling, and user interactions.
|
|
6
|
-
*/
|
|
7
|
-
/** Emoji names that indicate approval */
|
|
8
|
-
export declare const APPROVAL_EMOJIS: readonly ["+1", "thumbsup"];
|
|
9
|
-
/** Emoji names that indicate denial */
|
|
10
|
-
export declare const DENIAL_EMOJIS: readonly ["-1", "thumbsdown"];
|
|
11
|
-
/** Emoji names that indicate "allow all" / invite / session-wide approval */
|
|
12
|
-
export declare const ALLOW_ALL_EMOJIS: readonly ["white_check_mark", "heavy_check_mark"];
|
|
13
|
-
/** Number emojis for multi-choice questions (1-4) */
|
|
14
|
-
export declare const NUMBER_EMOJIS: readonly ["one", "two", "three", "four"];
|
|
15
|
-
/** Emojis for canceling/killing a session */
|
|
16
|
-
export declare const CANCEL_EMOJIS: readonly ["x", "octagonal_sign", "stop_sign"];
|
|
17
|
-
/** Emojis for escaping/pausing a session */
|
|
18
|
-
export declare const ESCAPE_EMOJIS: readonly ["double_vertical_bar", "pause_button"];
|
|
19
|
-
/**
|
|
20
|
-
* Check if the emoji indicates approval (thumbs up)
|
|
21
|
-
*/
|
|
22
|
-
export declare function isApprovalEmoji(emoji: string): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Check if the emoji indicates denial (thumbs down)
|
|
25
|
-
*/
|
|
26
|
-
export declare function isDenialEmoji(emoji: string): boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Check if the emoji indicates "allow all" or invitation
|
|
29
|
-
*/
|
|
30
|
-
export declare function isAllowAllEmoji(emoji: string): boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Check if the emoji indicates session cancellation
|
|
33
|
-
*/
|
|
34
|
-
export declare function isCancelEmoji(emoji: string): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Check if the emoji indicates escape/pause
|
|
37
|
-
*/
|
|
38
|
-
export declare function isEscapeEmoji(emoji: string): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Get the index (0-based) for a number emoji, or -1 if not a number emoji
|
|
41
|
-
* Handles both text names ('one', 'two') and unicode variants ('1️⃣', '2️⃣')
|
|
42
|
-
*/
|
|
43
|
-
export declare function getNumberEmojiIndex(emoji: string): number;
|
package/dist/utils/emoji.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emoji constants and helpers for chat platform reactions
|
|
3
|
-
*
|
|
4
|
-
* Platform-agnostic emoji utilities used across session management,
|
|
5
|
-
* permission handling, and user interactions.
|
|
6
|
-
*/
|
|
7
|
-
/** Emoji names that indicate approval */
|
|
8
|
-
export const APPROVAL_EMOJIS = ['+1', 'thumbsup'];
|
|
9
|
-
/** Emoji names that indicate denial */
|
|
10
|
-
export const DENIAL_EMOJIS = ['-1', 'thumbsdown'];
|
|
11
|
-
/** Emoji names that indicate "allow all" / invite / session-wide approval */
|
|
12
|
-
export const ALLOW_ALL_EMOJIS = ['white_check_mark', 'heavy_check_mark'];
|
|
13
|
-
/** Number emojis for multi-choice questions (1-4) */
|
|
14
|
-
export const NUMBER_EMOJIS = ['one', 'two', 'three', 'four'];
|
|
15
|
-
/** Emojis for canceling/killing a session */
|
|
16
|
-
export const CANCEL_EMOJIS = ['x', 'octagonal_sign', 'stop_sign'];
|
|
17
|
-
/** Emojis for escaping/pausing a session */
|
|
18
|
-
export const ESCAPE_EMOJIS = ['double_vertical_bar', 'pause_button'];
|
|
19
|
-
/**
|
|
20
|
-
* Check if the emoji indicates approval (thumbs up)
|
|
21
|
-
*/
|
|
22
|
-
export function isApprovalEmoji(emoji) {
|
|
23
|
-
return APPROVAL_EMOJIS.includes(emoji);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Check if the emoji indicates denial (thumbs down)
|
|
27
|
-
*/
|
|
28
|
-
export function isDenialEmoji(emoji) {
|
|
29
|
-
return DENIAL_EMOJIS.includes(emoji);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Check if the emoji indicates "allow all" or invitation
|
|
33
|
-
*/
|
|
34
|
-
export function isAllowAllEmoji(emoji) {
|
|
35
|
-
return ALLOW_ALL_EMOJIS.includes(emoji);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Check if the emoji indicates session cancellation
|
|
39
|
-
*/
|
|
40
|
-
export function isCancelEmoji(emoji) {
|
|
41
|
-
return CANCEL_EMOJIS.includes(emoji);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Check if the emoji indicates escape/pause
|
|
45
|
-
*/
|
|
46
|
-
export function isEscapeEmoji(emoji) {
|
|
47
|
-
return ESCAPE_EMOJIS.includes(emoji);
|
|
48
|
-
}
|
|
49
|
-
/** Unicode number emoji variants that also map to indices */
|
|
50
|
-
const UNICODE_NUMBER_EMOJIS = {
|
|
51
|
-
'1️⃣': 0,
|
|
52
|
-
'2️⃣': 1,
|
|
53
|
-
'3️⃣': 2,
|
|
54
|
-
'4️⃣': 3,
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Get the index (0-based) for a number emoji, or -1 if not a number emoji
|
|
58
|
-
* Handles both text names ('one', 'two') and unicode variants ('1️⃣', '2️⃣')
|
|
59
|
-
*/
|
|
60
|
-
export function getNumberEmojiIndex(emoji) {
|
|
61
|
-
const textIndex = NUMBER_EMOJIS.indexOf(emoji);
|
|
62
|
-
if (textIndex >= 0)
|
|
63
|
-
return textIndex;
|
|
64
|
-
return UNICODE_NUMBER_EMOJIS[emoji] ?? -1;
|
|
65
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/emoji.test.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { isApprovalEmoji, isDenialEmoji, isAllowAllEmoji, isCancelEmoji, isEscapeEmoji, getNumberEmojiIndex, APPROVAL_EMOJIS, DENIAL_EMOJIS, ALLOW_ALL_EMOJIS, NUMBER_EMOJIS, CANCEL_EMOJIS, ESCAPE_EMOJIS, } from './emoji.js';
|
|
3
|
-
describe('emoji helpers', () => {
|
|
4
|
-
describe('isApprovalEmoji', () => {
|
|
5
|
-
it('returns true for +1', () => {
|
|
6
|
-
expect(isApprovalEmoji('+1')).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
it('returns true for thumbsup', () => {
|
|
9
|
-
expect(isApprovalEmoji('thumbsup')).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
it('returns false for other emojis', () => {
|
|
12
|
-
expect(isApprovalEmoji('heart')).toBe(false);
|
|
13
|
-
expect(isApprovalEmoji('-1')).toBe(false);
|
|
14
|
-
expect(isApprovalEmoji('x')).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
it('matches all APPROVAL_EMOJIS', () => {
|
|
17
|
-
for (const emoji of APPROVAL_EMOJIS) {
|
|
18
|
-
expect(isApprovalEmoji(emoji)).toBe(true);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
describe('isDenialEmoji', () => {
|
|
23
|
-
it('returns true for -1', () => {
|
|
24
|
-
expect(isDenialEmoji('-1')).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
it('returns true for thumbsdown', () => {
|
|
27
|
-
expect(isDenialEmoji('thumbsdown')).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
it('returns false for other emojis', () => {
|
|
30
|
-
expect(isDenialEmoji('heart')).toBe(false);
|
|
31
|
-
expect(isDenialEmoji('+1')).toBe(false);
|
|
32
|
-
expect(isDenialEmoji('thumbsup')).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
it('matches all DENIAL_EMOJIS', () => {
|
|
35
|
-
for (const emoji of DENIAL_EMOJIS) {
|
|
36
|
-
expect(isDenialEmoji(emoji)).toBe(true);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
describe('isAllowAllEmoji', () => {
|
|
41
|
-
it('returns true for white_check_mark', () => {
|
|
42
|
-
expect(isAllowAllEmoji('white_check_mark')).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
it('returns true for heavy_check_mark', () => {
|
|
45
|
-
expect(isAllowAllEmoji('heavy_check_mark')).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
it('returns false for other emojis', () => {
|
|
48
|
-
expect(isAllowAllEmoji('heart')).toBe(false);
|
|
49
|
-
expect(isAllowAllEmoji('+1')).toBe(false);
|
|
50
|
-
expect(isAllowAllEmoji('thumbsup')).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
it('matches all ALLOW_ALL_EMOJIS', () => {
|
|
53
|
-
for (const emoji of ALLOW_ALL_EMOJIS) {
|
|
54
|
-
expect(isAllowAllEmoji(emoji)).toBe(true);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe('isCancelEmoji', () => {
|
|
59
|
-
it('returns true for x', () => {
|
|
60
|
-
expect(isCancelEmoji('x')).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
it('returns true for octagonal_sign', () => {
|
|
63
|
-
expect(isCancelEmoji('octagonal_sign')).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
it('returns true for stop_sign', () => {
|
|
66
|
-
expect(isCancelEmoji('stop_sign')).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
it('returns false for other emojis', () => {
|
|
69
|
-
expect(isCancelEmoji('heart')).toBe(false);
|
|
70
|
-
expect(isCancelEmoji('-1')).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
it('matches all CANCEL_EMOJIS', () => {
|
|
73
|
-
for (const emoji of CANCEL_EMOJIS) {
|
|
74
|
-
expect(isCancelEmoji(emoji)).toBe(true);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe('isEscapeEmoji', () => {
|
|
79
|
-
it('returns true for double_vertical_bar', () => {
|
|
80
|
-
expect(isEscapeEmoji('double_vertical_bar')).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
it('returns true for pause_button', () => {
|
|
83
|
-
expect(isEscapeEmoji('pause_button')).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
it('returns false for other emojis', () => {
|
|
86
|
-
expect(isEscapeEmoji('heart')).toBe(false);
|
|
87
|
-
expect(isEscapeEmoji('x')).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
it('matches all ESCAPE_EMOJIS', () => {
|
|
90
|
-
for (const emoji of ESCAPE_EMOJIS) {
|
|
91
|
-
expect(isEscapeEmoji(emoji)).toBe(true);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe('getNumberEmojiIndex', () => {
|
|
96
|
-
it('returns 0 for "one"', () => {
|
|
97
|
-
expect(getNumberEmojiIndex('one')).toBe(0);
|
|
98
|
-
});
|
|
99
|
-
it('returns 1 for "two"', () => {
|
|
100
|
-
expect(getNumberEmojiIndex('two')).toBe(1);
|
|
101
|
-
});
|
|
102
|
-
it('returns 2 for "three"', () => {
|
|
103
|
-
expect(getNumberEmojiIndex('three')).toBe(2);
|
|
104
|
-
});
|
|
105
|
-
it('returns 3 for "four"', () => {
|
|
106
|
-
expect(getNumberEmojiIndex('four')).toBe(3);
|
|
107
|
-
});
|
|
108
|
-
it('returns 0 for "1️⃣" (unicode)', () => {
|
|
109
|
-
expect(getNumberEmojiIndex('1️⃣')).toBe(0);
|
|
110
|
-
});
|
|
111
|
-
it('returns 1 for "2️⃣" (unicode)', () => {
|
|
112
|
-
expect(getNumberEmojiIndex('2️⃣')).toBe(1);
|
|
113
|
-
});
|
|
114
|
-
it('returns 2 for "3️⃣" (unicode)', () => {
|
|
115
|
-
expect(getNumberEmojiIndex('3️⃣')).toBe(2);
|
|
116
|
-
});
|
|
117
|
-
it('returns 3 for "4️⃣" (unicode)', () => {
|
|
118
|
-
expect(getNumberEmojiIndex('4️⃣')).toBe(3);
|
|
119
|
-
});
|
|
120
|
-
it('returns -1 for non-number emojis', () => {
|
|
121
|
-
expect(getNumberEmojiIndex('heart')).toBe(-1);
|
|
122
|
-
expect(getNumberEmojiIndex('five')).toBe(-1);
|
|
123
|
-
expect(getNumberEmojiIndex('+1')).toBe(-1);
|
|
124
|
-
});
|
|
125
|
-
it('returns correct index for all NUMBER_EMOJIS', () => {
|
|
126
|
-
for (let i = 0; i < NUMBER_EMOJIS.length; i++) {
|
|
127
|
-
expect(getNumberEmojiIndex(NUMBER_EMOJIS[i])).toBe(i);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
package/dist/utils/logger.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple debug logging utility
|
|
3
|
-
*
|
|
4
|
-
* Provides consistent logging across the codebase with:
|
|
5
|
-
* - Configurable prefix for different components
|
|
6
|
-
* - DEBUG environment variable check
|
|
7
|
-
* - stdout vs stderr routing option
|
|
8
|
-
*/
|
|
9
|
-
export interface Logger {
|
|
10
|
-
/** Log a debug message (only when DEBUG=1) */
|
|
11
|
-
debug: (msg: string) => void;
|
|
12
|
-
/** Log an info message (always shown) */
|
|
13
|
-
info: (msg: string) => void;
|
|
14
|
-
/** Log an error message (always shown, to stderr) */
|
|
15
|
-
error: (msg: string) => void;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Create a logger with a specific prefix
|
|
19
|
-
*
|
|
20
|
-
* @param prefix - Prefix to add to all messages (e.g., '[MCP]', '[ws]')
|
|
21
|
-
* @param useStderr - If true, use stderr for all output (default: false)
|
|
22
|
-
* @returns Logger object with debug, info, and error methods
|
|
23
|
-
*/
|
|
24
|
-
export declare function createLogger(prefix: string, useStderr?: boolean): Logger;
|
|
25
|
-
/**
|
|
26
|
-
* Pre-configured logger for MCP permission server
|
|
27
|
-
* Uses stderr (required for MCP stdio communication)
|
|
28
|
-
*/
|
|
29
|
-
export declare const mcpLogger: Logger;
|
|
30
|
-
/**
|
|
31
|
-
* Pre-configured logger for WebSocket client
|
|
32
|
-
* Uses stdout with indentation for visual hierarchy
|
|
33
|
-
*/
|
|
34
|
-
export declare const wsLogger: Logger;
|