claude-threads 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.5] - 2026-01-18
9
+
10
+ ### Fixed
11
+ - **Worktree switch with prompt** - `!worktree switch branch prompt text` now switches to existing worktree and starts session with the prompt (#242)
12
+
13
+ ## [1.4.4] - 2026-01-18
14
+
15
+ ### Fixed
16
+ - **Worktree commands in root messages** - `!worktree list` now works without a session, `!worktree branch-name` now starts session without requiring additional prompt (#241)
17
+
8
18
  ## [1.4.3] - 2026-01-18
9
19
 
10
20
  ### Added
package/dist/index.js CHANGED
@@ -8822,6 +8822,80 @@ var require_semver2 = __commonJS((exports, module) => {
8822
8822
  };
8823
8823
  });
8824
8824
 
8825
+ // src/utils/logger.ts
8826
+ function setLogHandler(handler) {
8827
+ globalLogHandler = handler;
8828
+ }
8829
+ function createLogger(component, useStderr = false, sessionId) {
8830
+ const isDebug = () => process.env.DEBUG === "1";
8831
+ const consoleLog = useStderr ? console.error : console.log;
8832
+ const paddedComponent = component.length > COMPONENT_WIDTH ? component.substring(0, COMPONENT_WIDTH) : component.padEnd(COMPONENT_WIDTH);
8833
+ const formatMessage = (msg, args) => {
8834
+ if (args.length === 0)
8835
+ return msg;
8836
+ return `${msg} ${args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}`;
8837
+ };
8838
+ const DEFAULT_JSON_MAX_LEN = 60;
8839
+ return {
8840
+ debug: (msg, ...args) => {
8841
+ if (isDebug()) {
8842
+ const fullMsg = formatMessage(msg, args);
8843
+ if (globalLogHandler) {
8844
+ globalLogHandler("debug", paddedComponent, fullMsg, sessionId);
8845
+ } else {
8846
+ consoleLog(`[${paddedComponent}] ${fullMsg}`);
8847
+ }
8848
+ }
8849
+ },
8850
+ debugJson: (label, data, maxLen = DEFAULT_JSON_MAX_LEN) => {
8851
+ if (isDebug()) {
8852
+ const json2 = JSON.stringify(data);
8853
+ const truncated = json2.length > maxLen ? `${json2.substring(0, maxLen)}…` : json2;
8854
+ const fullMsg = `${label}: ${truncated}`;
8855
+ if (globalLogHandler) {
8856
+ globalLogHandler("debug", paddedComponent, fullMsg, sessionId);
8857
+ } else {
8858
+ consoleLog(`[${paddedComponent}] ${fullMsg}`);
8859
+ }
8860
+ }
8861
+ },
8862
+ info: (msg, ...args) => {
8863
+ const fullMsg = formatMessage(msg, args);
8864
+ if (globalLogHandler) {
8865
+ globalLogHandler("info", paddedComponent, fullMsg, sessionId);
8866
+ } else {
8867
+ consoleLog(`[${paddedComponent}] ${fullMsg}`);
8868
+ }
8869
+ },
8870
+ warn: (msg, ...args) => {
8871
+ const fullMsg = formatMessage(msg, args);
8872
+ if (globalLogHandler) {
8873
+ globalLogHandler("warn", paddedComponent, fullMsg, sessionId);
8874
+ } else {
8875
+ console.warn(`[${paddedComponent}] ⚠️ ${fullMsg}`);
8876
+ }
8877
+ },
8878
+ error: (msg, err) => {
8879
+ const fullMsg = err && isDebug() ? `${msg}
8880
+ ${err.stack || err.message}` : msg;
8881
+ if (globalLogHandler) {
8882
+ globalLogHandler("error", paddedComponent, fullMsg, sessionId);
8883
+ } else {
8884
+ console.error(`[${paddedComponent}] ❌ ${msg}`);
8885
+ if (err && isDebug()) {
8886
+ console.error(err);
8887
+ }
8888
+ }
8889
+ },
8890
+ forSession: (sid) => createLogger(component, useStderr, sid)
8891
+ };
8892
+ }
8893
+ var globalLogHandler = null, COMPONENT_WIDTH = 10, mcpLogger, wsLogger;
8894
+ var init_logger = __esm(() => {
8895
+ mcpLogger = createLogger("MCP", true);
8896
+ wsLogger = createLogger("ws", false);
8897
+ });
8898
+
8825
8899
  // src/utils/emoji.ts
8826
8900
  var exports_emoji = {};
8827
8901
  __export(exports_emoji, {
@@ -8892,6 +8966,334 @@ var init_emoji = __esm(() => {
8892
8966
  };
8893
8967
  });
8894
8968
 
8969
+ // src/git/worktree.ts
8970
+ var exports_worktree = {};
8971
+ __export(exports_worktree, {
8972
+ writeWorktreeMetadata: () => writeWorktreeMetadata,
8973
+ updateWorktreeActivity: () => updateWorktreeActivity,
8974
+ removeWorktreeMetadata: () => removeWorktreeMetadata,
8975
+ removeWorktree: () => removeWorktree,
8976
+ readWorktreeMetadata: () => readWorktreeMetadata,
8977
+ listWorktrees: () => listWorktrees,
8978
+ isValidWorktreePath: () => isValidWorktreePath,
8979
+ isValidBranchName: () => isValidBranchName,
8980
+ isGitRepository: () => isGitRepository,
8981
+ isBranchMerged: () => isBranchMerged,
8982
+ hasUncommittedChanges: () => hasUncommittedChanges,
8983
+ getWorktreesDir: () => getWorktreesDir,
8984
+ getWorktreeDir: () => getWorktreeDir,
8985
+ getRepositoryRoot: () => getRepositoryRoot,
8986
+ getDefaultBranch: () => getDefaultBranch,
8987
+ getCurrentBranch: () => getCurrentBranch,
8988
+ findWorktreeByBranch: () => findWorktreeByBranch,
8989
+ detectWorktreeInfo: () => detectWorktreeInfo,
8990
+ createWorktree: () => createWorktree
8991
+ });
8992
+ import { spawn as spawn2 } from "child_process";
8993
+ import { randomUUID } from "crypto";
8994
+ import * as path from "path";
8995
+ import * as fs from "fs/promises";
8996
+ import { homedir as homedir4 } from "os";
8997
+ async function execGit(args, cwd) {
8998
+ const cmd = `git ${args.join(" ")}`;
8999
+ log7.debug(`Executing: ${cmd}`);
9000
+ return new Promise((resolve3, reject) => {
9001
+ const proc = spawn2("git", args, { cwd });
9002
+ let stdout = "";
9003
+ let stderr = "";
9004
+ proc.stdout.on("data", (data) => {
9005
+ stdout += data.toString();
9006
+ });
9007
+ proc.stderr.on("data", (data) => {
9008
+ stderr += data.toString();
9009
+ });
9010
+ proc.on("close", (code) => {
9011
+ if (code === 0) {
9012
+ log7.debug(`${cmd} → success`);
9013
+ resolve3(stdout.trim());
9014
+ } else {
9015
+ log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
9016
+ reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
9017
+ }
9018
+ });
9019
+ proc.on("error", (err) => {
9020
+ log7.warn(`${cmd} → error: ${err}`);
9021
+ reject(err);
9022
+ });
9023
+ });
9024
+ }
9025
+ async function isGitRepository(dir) {
9026
+ try {
9027
+ await execGit(["rev-parse", "--git-dir"], dir);
9028
+ return true;
9029
+ } catch (err) {
9030
+ log7.debug(`Not a git repository: ${dir} (${err})`);
9031
+ return false;
9032
+ }
9033
+ }
9034
+ async function getRepositoryRoot(dir) {
9035
+ return execGit(["rev-parse", "--show-toplevel"], dir);
9036
+ }
9037
+ async function getCurrentBranch(dir) {
9038
+ try {
9039
+ const branch = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], dir);
9040
+ return branch === "HEAD" ? null : branch;
9041
+ } catch {
9042
+ return null;
9043
+ }
9044
+ }
9045
+ async function getDefaultBranch(repoRoot) {
9046
+ try {
9047
+ const remoteHead = await execGit(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
9048
+ return remoteHead.replace("origin/", "");
9049
+ } catch {
9050
+ try {
9051
+ await execGit(["rev-parse", "--verify", "main"], repoRoot);
9052
+ return "main";
9053
+ } catch {
9054
+ try {
9055
+ await execGit(["rev-parse", "--verify", "master"], repoRoot);
9056
+ return "master";
9057
+ } catch {
9058
+ return "main";
9059
+ }
9060
+ }
9061
+ }
9062
+ }
9063
+ async function isBranchMerged(repoRoot, branchName) {
9064
+ try {
9065
+ const defaultBranch = await getDefaultBranch(repoRoot);
9066
+ if (branchName === defaultBranch) {
9067
+ return false;
9068
+ }
9069
+ await execGit(["fetch", "origin", defaultBranch], repoRoot).catch(() => {});
9070
+ const branchCommit = await execGit(["rev-parse", branchName], repoRoot);
9071
+ const defaultCommit = await execGit(["rev-parse", `origin/${defaultBranch}`], repoRoot);
9072
+ if (branchCommit === defaultCommit) {
9073
+ return false;
9074
+ }
9075
+ await execGit(["merge-base", "--is-ancestor", branchName, `origin/${defaultBranch}`], repoRoot);
9076
+ return true;
9077
+ } catch {
9078
+ return false;
9079
+ }
9080
+ }
9081
+ async function hasUncommittedChanges(dir) {
9082
+ try {
9083
+ const staged = await execGit(["diff", "--cached", "--quiet"], dir).catch(() => "changes");
9084
+ if (staged === "changes")
9085
+ return true;
9086
+ const unstaged = await execGit(["diff", "--quiet"], dir).catch(() => "changes");
9087
+ if (unstaged === "changes")
9088
+ return true;
9089
+ const untracked = await execGit(["ls-files", "--others", "--exclude-standard"], dir);
9090
+ return untracked.length > 0;
9091
+ } catch {
9092
+ return false;
9093
+ }
9094
+ }
9095
+ async function listWorktrees(repoRoot) {
9096
+ const output = await execGit(["worktree", "list", "--porcelain"], repoRoot);
9097
+ const worktrees = [];
9098
+ if (!output)
9099
+ return worktrees;
9100
+ const blocks = output.split(`
9101
+
9102
+ `).filter(Boolean);
9103
+ for (const block of blocks) {
9104
+ const lines = block.split(`
9105
+ `);
9106
+ const worktree = {};
9107
+ for (const line of lines) {
9108
+ if (line.startsWith("worktree ")) {
9109
+ worktree.path = line.slice(9);
9110
+ } else if (line.startsWith("HEAD ")) {
9111
+ worktree.commit = line.slice(5);
9112
+ } else if (line.startsWith("branch ")) {
9113
+ worktree.branch = line.slice(7).replace("refs/heads/", "");
9114
+ } else if (line === "bare") {
9115
+ worktree.isBare = true;
9116
+ } else if (line === "detached") {
9117
+ worktree.branch = "(detached)";
9118
+ }
9119
+ }
9120
+ if (worktree.path) {
9121
+ worktrees.push({
9122
+ path: worktree.path,
9123
+ branch: worktree.branch || "(unknown)",
9124
+ commit: worktree.commit || "",
9125
+ isMain: worktrees.length === 0,
9126
+ isBare: worktree.isBare || false
9127
+ });
9128
+ }
9129
+ }
9130
+ return worktrees;
9131
+ }
9132
+ async function branchExists(repoRoot, branch) {
9133
+ try {
9134
+ await execGit(["rev-parse", "--verify", `refs/heads/${branch}`], repoRoot);
9135
+ return true;
9136
+ } catch {
9137
+ try {
9138
+ await execGit(["rev-parse", "--verify", `refs/remotes/origin/${branch}`], repoRoot);
9139
+ return true;
9140
+ } catch {
9141
+ return false;
9142
+ }
9143
+ }
9144
+ }
9145
+ function getWorktreeDir(repoRoot, branch) {
9146
+ const repoName = repoRoot.replace(/\//g, "-").replace(/^-/, "");
9147
+ const sanitizedBranch = branch.replace(/\//g, "-").replace(/[^a-zA-Z0-9-_]/g, "");
9148
+ const shortUuid = randomUUID().slice(0, 8);
9149
+ return path.join(WORKTREES_DIR, `${repoName}--${sanitizedBranch}-${shortUuid}`);
9150
+ }
9151
+ function isValidWorktreePath(worktreePath) {
9152
+ return worktreePath.startsWith(WORKTREES_DIR + path.sep);
9153
+ }
9154
+ function getWorktreesDir() {
9155
+ return WORKTREES_DIR;
9156
+ }
9157
+ async function detectWorktreeInfo(workingDir) {
9158
+ if (!isValidWorktreePath(workingDir)) {
9159
+ return null;
9160
+ }
9161
+ try {
9162
+ const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
9163
+ const branch = branchOutput?.trim();
9164
+ if (!branch) {
9165
+ log7.debug(`Could not detect branch for worktree at ${workingDir}`);
9166
+ return null;
9167
+ }
9168
+ const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
9169
+ let repoRoot = gitDirOutput?.trim();
9170
+ if (repoRoot) {
9171
+ if (repoRoot.endsWith("/.git")) {
9172
+ repoRoot = repoRoot.slice(0, -5);
9173
+ } else if (repoRoot.endsWith(".git")) {
9174
+ repoRoot = repoRoot.slice(0, -4);
9175
+ }
9176
+ }
9177
+ log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
9178
+ return {
9179
+ worktreePath: workingDir,
9180
+ branch,
9181
+ repoRoot: repoRoot || workingDir
9182
+ };
9183
+ } catch (err) {
9184
+ log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
9185
+ return null;
9186
+ }
9187
+ }
9188
+ async function createWorktree(repoRoot, branch, targetDir) {
9189
+ log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
9190
+ const parentDir = path.dirname(targetDir);
9191
+ log7.debug(`Creating parent directory: ${parentDir}`);
9192
+ await fs.mkdir(parentDir, { recursive: true });
9193
+ const exists = await branchExists(repoRoot, branch);
9194
+ if (exists) {
9195
+ log7.debug(`Branch '${branch}' exists, adding worktree`);
9196
+ await execGit(["worktree", "add", targetDir, branch], repoRoot);
9197
+ } else {
9198
+ log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
9199
+ await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
9200
+ }
9201
+ log7.info(`Worktree created successfully: ${targetDir}`);
9202
+ return targetDir;
9203
+ }
9204
+ async function removeWorktree(repoRoot, worktreePath) {
9205
+ log7.info(`Removing worktree: ${worktreePath}`);
9206
+ try {
9207
+ await execGit(["worktree", "remove", worktreePath], repoRoot);
9208
+ log7.debug("Worktree removed cleanly");
9209
+ } catch (err) {
9210
+ log7.debug(`Clean remove failed (${err}), trying force remove`);
9211
+ await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
9212
+ }
9213
+ log7.debug("Pruning stale worktree references");
9214
+ await execGit(["worktree", "prune"], repoRoot);
9215
+ log7.info("Worktree removed and pruned successfully");
9216
+ }
9217
+ async function findWorktreeByBranch(repoRoot, branch) {
9218
+ const worktrees = await listWorktrees(repoRoot);
9219
+ return worktrees.find((wt) => wt.branch === branch) || null;
9220
+ }
9221
+ function isValidBranchName(name) {
9222
+ if (!name || name.length === 0)
9223
+ return false;
9224
+ if (name.startsWith("/") || name.endsWith("/"))
9225
+ return false;
9226
+ if (name.includes(".."))
9227
+ return false;
9228
+ if (/[\s~^:?*[\]\\]/.test(name))
9229
+ return false;
9230
+ if (name.startsWith("-"))
9231
+ return false;
9232
+ if (name.endsWith(".lock"))
9233
+ return false;
9234
+ if (name.includes("@{"))
9235
+ return false;
9236
+ if (name === "@")
9237
+ return false;
9238
+ if (/\.\./.test(name))
9239
+ return false;
9240
+ return true;
9241
+ }
9242
+ async function readMetadataStore() {
9243
+ try {
9244
+ const content = await fs.readFile(METADATA_STORE_PATH, "utf-8");
9245
+ return JSON.parse(content);
9246
+ } catch {
9247
+ return {};
9248
+ }
9249
+ }
9250
+ async function writeMetadataStore(store) {
9251
+ try {
9252
+ await fs.mkdir(path.dirname(METADATA_STORE_PATH), { recursive: true });
9253
+ await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
9254
+ await fs.chmod(METADATA_STORE_PATH, 384);
9255
+ } catch (err) {
9256
+ log7.warn(`Failed to write worktree metadata store: ${err}`);
9257
+ }
9258
+ }
9259
+ async function writeWorktreeMetadata(worktreePath, metadata) {
9260
+ const store = await readMetadataStore();
9261
+ store[worktreePath] = metadata;
9262
+ await writeMetadataStore(store);
9263
+ log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
9264
+ }
9265
+ async function readWorktreeMetadata(worktreePath) {
9266
+ const store = await readMetadataStore();
9267
+ return store[worktreePath] || null;
9268
+ }
9269
+ async function updateWorktreeActivity(worktreePath, sessionId) {
9270
+ const store = await readMetadataStore();
9271
+ const existing = store[worktreePath];
9272
+ if (!existing)
9273
+ return;
9274
+ existing.lastActivityAt = new Date().toISOString();
9275
+ if (sessionId !== undefined) {
9276
+ existing.sessionId = sessionId;
9277
+ }
9278
+ store[worktreePath] = existing;
9279
+ await writeMetadataStore(store);
9280
+ }
9281
+ async function removeWorktreeMetadata(worktreePath) {
9282
+ const store = await readMetadataStore();
9283
+ if (store[worktreePath]) {
9284
+ delete store[worktreePath];
9285
+ await writeMetadataStore(store);
9286
+ log7.debug(`Removed worktree metadata for: ${worktreePath}`);
9287
+ }
9288
+ }
9289
+ var log7, WORKTREES_DIR, METADATA_STORE_PATH;
9290
+ var init_worktree = __esm(() => {
9291
+ init_logger();
9292
+ log7 = createLogger("git-wt");
9293
+ WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
9294
+ METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
9295
+ });
9296
+
8895
9297
  // node_modules/pend/index.js
8896
9298
  var require_pend = __commonJS((exports, module) => {
8897
9299
  module.exports = Pend;
@@ -50840,82 +51242,8 @@ async function setupSlackPlatform(id, existing) {
50840
51242
  }
50841
51243
 
50842
51244
  // src/platform/base-client.ts
51245
+ init_logger();
50843
51246
  import { EventEmitter } from "events";
50844
-
50845
- // src/utils/logger.ts
50846
- var globalLogHandler = null;
50847
- function setLogHandler(handler) {
50848
- globalLogHandler = handler;
50849
- }
50850
- var COMPONENT_WIDTH = 10;
50851
- function createLogger(component, useStderr = false, sessionId) {
50852
- const isDebug = () => process.env.DEBUG === "1";
50853
- const consoleLog = useStderr ? console.error : console.log;
50854
- const paddedComponent = component.length > COMPONENT_WIDTH ? component.substring(0, COMPONENT_WIDTH) : component.padEnd(COMPONENT_WIDTH);
50855
- const formatMessage = (msg, args) => {
50856
- if (args.length === 0)
50857
- return msg;
50858
- return `${msg} ${args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}`;
50859
- };
50860
- const DEFAULT_JSON_MAX_LEN = 60;
50861
- return {
50862
- debug: (msg, ...args) => {
50863
- if (isDebug()) {
50864
- const fullMsg = formatMessage(msg, args);
50865
- if (globalLogHandler) {
50866
- globalLogHandler("debug", paddedComponent, fullMsg, sessionId);
50867
- } else {
50868
- consoleLog(`[${paddedComponent}] ${fullMsg}`);
50869
- }
50870
- }
50871
- },
50872
- debugJson: (label, data, maxLen = DEFAULT_JSON_MAX_LEN) => {
50873
- if (isDebug()) {
50874
- const json2 = JSON.stringify(data);
50875
- const truncated = json2.length > maxLen ? `${json2.substring(0, maxLen)}…` : json2;
50876
- const fullMsg = `${label}: ${truncated}`;
50877
- if (globalLogHandler) {
50878
- globalLogHandler("debug", paddedComponent, fullMsg, sessionId);
50879
- } else {
50880
- consoleLog(`[${paddedComponent}] ${fullMsg}`);
50881
- }
50882
- }
50883
- },
50884
- info: (msg, ...args) => {
50885
- const fullMsg = formatMessage(msg, args);
50886
- if (globalLogHandler) {
50887
- globalLogHandler("info", paddedComponent, fullMsg, sessionId);
50888
- } else {
50889
- consoleLog(`[${paddedComponent}] ${fullMsg}`);
50890
- }
50891
- },
50892
- warn: (msg, ...args) => {
50893
- const fullMsg = formatMessage(msg, args);
50894
- if (globalLogHandler) {
50895
- globalLogHandler("warn", paddedComponent, fullMsg, sessionId);
50896
- } else {
50897
- console.warn(`[${paddedComponent}] ⚠️ ${fullMsg}`);
50898
- }
50899
- },
50900
- error: (msg, err) => {
50901
- const fullMsg = err && isDebug() ? `${msg}
50902
- ${err.stack || err.message}` : msg;
50903
- if (globalLogHandler) {
50904
- globalLogHandler("error", paddedComponent, fullMsg, sessionId);
50905
- } else {
50906
- console.error(`[${paddedComponent}] ❌ ${msg}`);
50907
- if (err && isDebug()) {
50908
- console.error(err);
50909
- }
50910
- }
50911
- },
50912
- forSession: (sid) => createLogger(component, useStderr, sid)
50913
- };
50914
- }
50915
- var mcpLogger = createLogger("MCP", true);
50916
- var wsLogger = createLogger("ws", false);
50917
-
50918
- // src/platform/base-client.ts
50919
51247
  var log = createLogger("base-client");
50920
51248
 
50921
51249
  class BasePlatformClient extends EventEmitter {
@@ -51038,6 +51366,9 @@ class BasePlatformClient extends EventEmitter {
51038
51366
  this.lastMessageAt = Date.now();
51039
51367
  }
51040
51368
  }
51369
+ // src/platform/mattermost/client.ts
51370
+ init_logger();
51371
+
51041
51372
  // src/version.ts
51042
51373
  import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
51043
51374
  import { dirname as dirname3, resolve as resolve2 } from "path";
@@ -51797,6 +52128,9 @@ class MattermostClient extends BasePlatformClient {
51797
52128
  }));
51798
52129
  }
51799
52130
  }
52131
+ // src/platform/slack/client.ts
52132
+ init_logger();
52133
+
51800
52134
  // src/platform/slack/formatter.ts
51801
52135
  class SlackFormatter {
51802
52136
  formatBold(text) {
@@ -52510,6 +52844,7 @@ class SlackClient extends BasePlatformClient {
52510
52844
  }
52511
52845
  }
52512
52846
  // src/mattermost/api.ts
52847
+ init_logger();
52513
52848
  var log4 = createLogger("mm-api");
52514
52849
  async function mattermostApi(config, method, path, body) {
52515
52850
  const url = `${config.url}/api/v4${path}`;
@@ -52579,6 +52914,7 @@ async function createInteractivePost(config, channelId, message, reactions, root
52579
52914
  }
52580
52915
 
52581
52916
  // src/platform/mattermost/permission-api.ts
52917
+ init_logger();
52582
52918
  class MattermostPermissionApi {
52583
52919
  apiConfig;
52584
52920
  config;
@@ -52707,6 +53043,7 @@ class MattermostPermissionApi {
52707
53043
  }
52708
53044
 
52709
53045
  // src/platform/slack/permission-api.ts
53046
+ init_logger();
52710
53047
  var SLACK_API_BASE = "https://slack.com/api";
52711
53048
  async function slackApi(method, token, body) {
52712
53049
  const url = `${SLACK_API_BASE}/${method}`;
@@ -52919,6 +53256,7 @@ class SlackPermissionApi {
52919
53256
  import { EventEmitter as EventEmitter4 } from "events";
52920
53257
 
52921
53258
  // src/persistence/session-store.ts
53259
+ init_logger();
52922
53260
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync, chmodSync as chmodSync2 } from "fs";
52923
53261
  import { homedir as homedir2 } from "os";
52924
53262
  import { join as join2 } from "path";
@@ -53145,11 +53483,13 @@ class SessionStore {
53145
53483
  init_emoji();
53146
53484
 
53147
53485
  // src/cleanup/scheduler.ts
53486
+ init_logger();
53148
53487
  import { existsSync as existsSync7 } from "fs";
53149
53488
  import { readdir, rm } from "fs/promises";
53150
53489
  import { join as join5 } from "path";
53151
53490
 
53152
53491
  // src/persistence/thread-logger.ts
53492
+ init_logger();
53153
53493
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, appendFileSync, readdirSync, statSync, unlinkSync, rmdirSync, readFileSync as readFileSync5, chmodSync as chmodSync3 } from "fs";
53154
53494
  import { homedir as homedir3 } from "os";
53155
53495
  import { join as join3, dirname as dirname4 } from "path";
@@ -53424,309 +53764,8 @@ function readRecentLogEntries(platformId, sessionId, maxLines = 50) {
53424
53764
  }
53425
53765
  }
53426
53766
 
53427
- // src/git/worktree.ts
53428
- import { spawn as spawn2 } from "child_process";
53429
- import { randomUUID } from "crypto";
53430
- import * as path from "path";
53431
- import * as fs from "fs/promises";
53432
- import { homedir as homedir4 } from "os";
53433
- var log7 = createLogger("git-wt");
53434
- var WORKTREES_DIR = path.join(homedir4(), ".claude-threads", "worktrees");
53435
- async function execGit(args, cwd) {
53436
- const cmd = `git ${args.join(" ")}`;
53437
- log7.debug(`Executing: ${cmd}`);
53438
- return new Promise((resolve3, reject) => {
53439
- const proc = spawn2("git", args, { cwd });
53440
- let stdout = "";
53441
- let stderr = "";
53442
- proc.stdout.on("data", (data) => {
53443
- stdout += data.toString();
53444
- });
53445
- proc.stderr.on("data", (data) => {
53446
- stderr += data.toString();
53447
- });
53448
- proc.on("close", (code) => {
53449
- if (code === 0) {
53450
- log7.debug(`${cmd} → success`);
53451
- resolve3(stdout.trim());
53452
- } else {
53453
- log7.debug(`${cmd} → failed (code=${code}): ${stderr.substring(0, 100) || stdout.substring(0, 100)}`);
53454
- reject(new Error(`git ${args.join(" ")} failed: ${stderr || stdout}`));
53455
- }
53456
- });
53457
- proc.on("error", (err) => {
53458
- log7.warn(`${cmd} → error: ${err}`);
53459
- reject(err);
53460
- });
53461
- });
53462
- }
53463
- async function isGitRepository(dir) {
53464
- try {
53465
- await execGit(["rev-parse", "--git-dir"], dir);
53466
- return true;
53467
- } catch (err) {
53468
- log7.debug(`Not a git repository: ${dir} (${err})`);
53469
- return false;
53470
- }
53471
- }
53472
- async function getRepositoryRoot(dir) {
53473
- return execGit(["rev-parse", "--show-toplevel"], dir);
53474
- }
53475
- async function getCurrentBranch(dir) {
53476
- try {
53477
- const branch = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], dir);
53478
- return branch === "HEAD" ? null : branch;
53479
- } catch {
53480
- return null;
53481
- }
53482
- }
53483
- async function getDefaultBranch(repoRoot) {
53484
- try {
53485
- const remoteHead = await execGit(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
53486
- return remoteHead.replace("origin/", "");
53487
- } catch {
53488
- try {
53489
- await execGit(["rev-parse", "--verify", "main"], repoRoot);
53490
- return "main";
53491
- } catch {
53492
- try {
53493
- await execGit(["rev-parse", "--verify", "master"], repoRoot);
53494
- return "master";
53495
- } catch {
53496
- return "main";
53497
- }
53498
- }
53499
- }
53500
- }
53501
- async function isBranchMerged(repoRoot, branchName) {
53502
- try {
53503
- const defaultBranch = await getDefaultBranch(repoRoot);
53504
- if (branchName === defaultBranch) {
53505
- return false;
53506
- }
53507
- await execGit(["fetch", "origin", defaultBranch], repoRoot).catch(() => {});
53508
- const branchCommit = await execGit(["rev-parse", branchName], repoRoot);
53509
- const defaultCommit = await execGit(["rev-parse", `origin/${defaultBranch}`], repoRoot);
53510
- if (branchCommit === defaultCommit) {
53511
- return false;
53512
- }
53513
- await execGit(["merge-base", "--is-ancestor", branchName, `origin/${defaultBranch}`], repoRoot);
53514
- return true;
53515
- } catch {
53516
- return false;
53517
- }
53518
- }
53519
- async function hasUncommittedChanges(dir) {
53520
- try {
53521
- const staged = await execGit(["diff", "--cached", "--quiet"], dir).catch(() => "changes");
53522
- if (staged === "changes")
53523
- return true;
53524
- const unstaged = await execGit(["diff", "--quiet"], dir).catch(() => "changes");
53525
- if (unstaged === "changes")
53526
- return true;
53527
- const untracked = await execGit(["ls-files", "--others", "--exclude-standard"], dir);
53528
- return untracked.length > 0;
53529
- } catch {
53530
- return false;
53531
- }
53532
- }
53533
- async function listWorktrees(repoRoot) {
53534
- const output = await execGit(["worktree", "list", "--porcelain"], repoRoot);
53535
- const worktrees = [];
53536
- if (!output)
53537
- return worktrees;
53538
- const blocks = output.split(`
53539
-
53540
- `).filter(Boolean);
53541
- for (const block of blocks) {
53542
- const lines = block.split(`
53543
- `);
53544
- const worktree = {};
53545
- for (const line of lines) {
53546
- if (line.startsWith("worktree ")) {
53547
- worktree.path = line.slice(9);
53548
- } else if (line.startsWith("HEAD ")) {
53549
- worktree.commit = line.slice(5);
53550
- } else if (line.startsWith("branch ")) {
53551
- worktree.branch = line.slice(7).replace("refs/heads/", "");
53552
- } else if (line === "bare") {
53553
- worktree.isBare = true;
53554
- } else if (line === "detached") {
53555
- worktree.branch = "(detached)";
53556
- }
53557
- }
53558
- if (worktree.path) {
53559
- worktrees.push({
53560
- path: worktree.path,
53561
- branch: worktree.branch || "(unknown)",
53562
- commit: worktree.commit || "",
53563
- isMain: worktrees.length === 0,
53564
- isBare: worktree.isBare || false
53565
- });
53566
- }
53567
- }
53568
- return worktrees;
53569
- }
53570
- async function branchExists(repoRoot, branch) {
53571
- try {
53572
- await execGit(["rev-parse", "--verify", `refs/heads/${branch}`], repoRoot);
53573
- return true;
53574
- } catch {
53575
- try {
53576
- await execGit(["rev-parse", "--verify", `refs/remotes/origin/${branch}`], repoRoot);
53577
- return true;
53578
- } catch {
53579
- return false;
53580
- }
53581
- }
53582
- }
53583
- function getWorktreeDir(repoRoot, branch) {
53584
- const repoName = repoRoot.replace(/\//g, "-").replace(/^-/, "");
53585
- const sanitizedBranch = branch.replace(/\//g, "-").replace(/[^a-zA-Z0-9-_]/g, "");
53586
- const shortUuid = randomUUID().slice(0, 8);
53587
- return path.join(WORKTREES_DIR, `${repoName}--${sanitizedBranch}-${shortUuid}`);
53588
- }
53589
- function isValidWorktreePath(worktreePath) {
53590
- return worktreePath.startsWith(WORKTREES_DIR + path.sep);
53591
- }
53592
- function getWorktreesDir() {
53593
- return WORKTREES_DIR;
53594
- }
53595
- async function detectWorktreeInfo(workingDir) {
53596
- if (!isValidWorktreePath(workingDir)) {
53597
- return null;
53598
- }
53599
- try {
53600
- const branchOutput = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], workingDir);
53601
- const branch = branchOutput?.trim();
53602
- if (!branch) {
53603
- log7.debug(`Could not detect branch for worktree at ${workingDir}`);
53604
- return null;
53605
- }
53606
- const gitDirOutput = await execGit(["rev-parse", "--git-common-dir"], workingDir);
53607
- let repoRoot = gitDirOutput?.trim();
53608
- if (repoRoot) {
53609
- if (repoRoot.endsWith("/.git")) {
53610
- repoRoot = repoRoot.slice(0, -5);
53611
- } else if (repoRoot.endsWith(".git")) {
53612
- repoRoot = repoRoot.slice(0, -4);
53613
- }
53614
- }
53615
- log7.debug(`Detected worktree: path=${workingDir}, branch=${branch}, repoRoot=${repoRoot}`);
53616
- return {
53617
- worktreePath: workingDir,
53618
- branch,
53619
- repoRoot: repoRoot || workingDir
53620
- };
53621
- } catch (err) {
53622
- log7.debug(`Failed to detect worktree info for ${workingDir}: ${err}`);
53623
- return null;
53624
- }
53625
- }
53626
- async function createWorktree(repoRoot, branch, targetDir) {
53627
- log7.info(`Creating worktree for branch '${branch}' at ${targetDir}`);
53628
- const parentDir = path.dirname(targetDir);
53629
- log7.debug(`Creating parent directory: ${parentDir}`);
53630
- await fs.mkdir(parentDir, { recursive: true });
53631
- const exists = await branchExists(repoRoot, branch);
53632
- if (exists) {
53633
- log7.debug(`Branch '${branch}' exists, adding worktree`);
53634
- await execGit(["worktree", "add", targetDir, branch], repoRoot);
53635
- } else {
53636
- log7.debug(`Branch '${branch}' does not exist, creating with worktree`);
53637
- await execGit(["worktree", "add", "-b", branch, targetDir], repoRoot);
53638
- }
53639
- log7.info(`Worktree created successfully: ${targetDir}`);
53640
- return targetDir;
53641
- }
53642
- async function removeWorktree(repoRoot, worktreePath) {
53643
- log7.info(`Removing worktree: ${worktreePath}`);
53644
- try {
53645
- await execGit(["worktree", "remove", worktreePath], repoRoot);
53646
- log7.debug("Worktree removed cleanly");
53647
- } catch (err) {
53648
- log7.debug(`Clean remove failed (${err}), trying force remove`);
53649
- await execGit(["worktree", "remove", "--force", worktreePath], repoRoot);
53650
- }
53651
- log7.debug("Pruning stale worktree references");
53652
- await execGit(["worktree", "prune"], repoRoot);
53653
- log7.info("Worktree removed and pruned successfully");
53654
- }
53655
- async function findWorktreeByBranch(repoRoot, branch) {
53656
- const worktrees = await listWorktrees(repoRoot);
53657
- return worktrees.find((wt) => wt.branch === branch) || null;
53658
- }
53659
- function isValidBranchName(name) {
53660
- if (!name || name.length === 0)
53661
- return false;
53662
- if (name.startsWith("/") || name.endsWith("/"))
53663
- return false;
53664
- if (name.includes(".."))
53665
- return false;
53666
- if (/[\s~^:?*[\]\\]/.test(name))
53667
- return false;
53668
- if (name.startsWith("-"))
53669
- return false;
53670
- if (name.endsWith(".lock"))
53671
- return false;
53672
- if (name.includes("@{"))
53673
- return false;
53674
- if (name === "@")
53675
- return false;
53676
- if (/\.\./.test(name))
53677
- return false;
53678
- return true;
53679
- }
53680
- var METADATA_STORE_PATH = path.join(homedir4(), ".claude-threads", "worktree-metadata.json");
53681
- async function readMetadataStore() {
53682
- try {
53683
- const content = await fs.readFile(METADATA_STORE_PATH, "utf-8");
53684
- return JSON.parse(content);
53685
- } catch {
53686
- return {};
53687
- }
53688
- }
53689
- async function writeMetadataStore(store) {
53690
- try {
53691
- await fs.mkdir(path.dirname(METADATA_STORE_PATH), { recursive: true });
53692
- await fs.writeFile(METADATA_STORE_PATH, JSON.stringify(store, null, 2), { encoding: "utf-8", mode: 384 });
53693
- await fs.chmod(METADATA_STORE_PATH, 384);
53694
- } catch (err) {
53695
- log7.warn(`Failed to write worktree metadata store: ${err}`);
53696
- }
53697
- }
53698
- async function writeWorktreeMetadata(worktreePath, metadata) {
53699
- const store = await readMetadataStore();
53700
- store[worktreePath] = metadata;
53701
- await writeMetadataStore(store);
53702
- log7.debug(`Wrote worktree metadata for: ${worktreePath}`);
53703
- }
53704
- async function readWorktreeMetadata(worktreePath) {
53705
- const store = await readMetadataStore();
53706
- return store[worktreePath] || null;
53707
- }
53708
- async function updateWorktreeActivity(worktreePath, sessionId) {
53709
- const store = await readMetadataStore();
53710
- const existing = store[worktreePath];
53711
- if (!existing)
53712
- return;
53713
- existing.lastActivityAt = new Date().toISOString();
53714
- if (sessionId !== undefined) {
53715
- existing.sessionId = sessionId;
53716
- }
53717
- store[worktreePath] = existing;
53718
- await writeMetadataStore(store);
53719
- }
53720
- async function removeWorktreeMetadata(worktreePath) {
53721
- const store = await readMetadataStore();
53722
- if (store[worktreePath]) {
53723
- delete store[worktreePath];
53724
- await writeMetadataStore(store);
53725
- log7.debug(`Removed worktree metadata for: ${worktreePath}`);
53726
- }
53727
- }
53728
-
53729
53767
  // src/cleanup/scheduler.ts
53768
+ init_worktree();
53730
53769
  var log8 = createLogger("cleanup");
53731
53770
  var DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
53732
53771
  var MAX_WORKTREE_AGE_MS = 24 * 60 * 60 * 1000;
@@ -53900,6 +53939,9 @@ class CleanupScheduler {
53900
53939
  return result;
53901
53940
  }
53902
53941
  }
53942
+ // src/operations/monitor/handler.ts
53943
+ init_logger();
53944
+
53903
53945
  // src/session/timer-manager.ts
53904
53946
  function createSessionTimers() {
53905
53947
  return {
@@ -53961,6 +54003,7 @@ function getSessionStatus(session) {
53961
54003
  }
53962
54004
 
53963
54005
  // src/claude/cli.ts
54006
+ init_logger();
53964
54007
  import { spawn as spawn3 } from "child_process";
53965
54008
  import { EventEmitter as EventEmitter2 } from "events";
53966
54009
  import { resolve as resolve3, dirname as dirname6 } from "path";
@@ -54845,15 +54888,35 @@ var handleWorktree = async (ctx, args) => {
54845
54888
  }
54846
54889
  switch (subcommandOrBranch) {
54847
54890
  case "list":
54848
- await ctx.sessionManager.listWorktreesCommand(ctx.threadId, ctx.username);
54891
+ if (ctx.commandContext === "first-message") {
54892
+ await ctx.sessionManager.listWorktreesWithoutSession(ctx.client.platformId, ctx.threadId);
54893
+ } else {
54894
+ await ctx.sessionManager.listWorktreesCommand(ctx.threadId, ctx.username);
54895
+ }
54849
54896
  return { handled: true };
54850
- case "switch":
54897
+ case "switch": {
54851
54898
  if (!subArgs) {
54852
54899
  await ctx.client.createPost(`❌ Usage: ${ctx.formatter.formatCode("!worktree switch <branch>")}`, ctx.threadId);
54853
54900
  return { handled: true };
54854
54901
  }
54855
- await ctx.sessionManager.switchToWorktree(ctx.threadId, subArgs, ctx.username);
54902
+ const switchParts = subArgs.split(/\s+/);
54903
+ const branchName = switchParts[0];
54904
+ const remainingPrompt = switchParts.slice(1).join(" ").trim();
54905
+ if (ctx.commandContext === "first-message") {
54906
+ if (remainingPrompt) {
54907
+ return {
54908
+ worktreeBranch: branchName,
54909
+ continueProcessing: false,
54910
+ remainingText: remainingPrompt,
54911
+ sessionOptions: { switchToExisting: true }
54912
+ };
54913
+ }
54914
+ await ctx.sessionManager.switchToWorktreeWithoutSession(ctx.client.platformId, ctx.threadId, branchName);
54915
+ return { handled: true };
54916
+ }
54917
+ await ctx.sessionManager.switchToWorktree(ctx.threadId, branchName, ctx.username);
54856
54918
  return { handled: true };
54919
+ }
54857
54920
  case "remove":
54858
54921
  if (!subArgs) {
54859
54922
  await ctx.client.createPost(`❌ Usage: ${ctx.formatter.formatCode("!worktree remove <branch>")}`, ctx.threadId);
@@ -55065,6 +55128,7 @@ import { randomUUID as randomUUID4 } from "crypto";
55065
55128
  import { existsSync as existsSync11 } from "fs";
55066
55129
 
55067
55130
  // src/utils/keep-alive.ts
55131
+ init_logger();
55068
55132
  import { spawn as spawn4 } from "child_process";
55069
55133
  var log10 = createLogger("keepalive");
55070
55134
 
@@ -55253,6 +55317,7 @@ class KeepAliveManager {
55253
55317
  var keepAlive = new KeepAliveManager;
55254
55318
 
55255
55319
  // src/utils/error-handler/index.ts
55320
+ init_logger();
55256
55321
  var log11 = createLogger("error");
55257
55322
 
55258
55323
  class SessionError extends Error {
@@ -55321,6 +55386,9 @@ function logSilentError(context, error) {
55321
55386
  log11.debug(`[${context}] Silently caught: ${message}`);
55322
55387
  }
55323
55388
 
55389
+ // src/session/lifecycle.ts
55390
+ init_logger();
55391
+
55324
55392
  // src/utils/session-log.ts
55325
55393
  function createSessionLog(baseLog) {
55326
55394
  return (session) => {
@@ -55332,7 +55400,9 @@ function createSessionLog(baseLog) {
55332
55400
  }
55333
55401
 
55334
55402
  // src/operations/post-helpers/index.ts
55403
+ init_logger();
55335
55404
  init_emoji();
55405
+ init_worktree();
55336
55406
  var log12 = createLogger("helpers");
55337
55407
  var sessionLog = createSessionLog(log12);
55338
55408
  var POST_TYPES = {
@@ -55416,6 +55486,7 @@ function updateLastMessage(session, post2) {
55416
55486
  }
55417
55487
 
55418
55488
  // src/claude/quick-query.ts
55489
+ init_logger();
55419
55490
  import { spawn as spawn5 } from "child_process";
55420
55491
  var log13 = createLogger("query");
55421
55492
  async function quickQuery(options2) {
@@ -55500,6 +55571,7 @@ async function quickQuery(options2) {
55500
55571
  }
55501
55572
 
55502
55573
  // src/operations/suggestions/title.ts
55574
+ init_logger();
55503
55575
  var log14 = createLogger("title");
55504
55576
  var SUGGESTION_TIMEOUT = 15000;
55505
55577
  var MIN_TITLE_LENGTH = 3;
@@ -55612,6 +55684,7 @@ async function suggestSessionMetadata(context) {
55612
55684
  }
55613
55685
 
55614
55686
  // src/operations/suggestions/tag.ts
55687
+ init_logger();
55615
55688
  var log15 = createLogger("tags");
55616
55689
  var SUGGESTION_TIMEOUT2 = 15000;
55617
55690
  var MAX_TAGS = 3;
@@ -59200,7 +59273,11 @@ class BugReportExecutor extends BaseExecutor {
59200
59273
  }
59201
59274
  // src/operations/executors/worktree-prompt.ts
59202
59275
  init_emoji();
59276
+ init_logger();
59203
59277
  var log16 = createLogger("wt-prompt");
59278
+ // src/operations/message-manager.ts
59279
+ init_logger();
59280
+
59204
59281
  // src/operations/message-manager-events.ts
59205
59282
  import { EventEmitter as EventEmitter3 } from "events";
59206
59283
 
@@ -59229,6 +59306,7 @@ function createMessageManagerEvents() {
59229
59306
  }
59230
59307
 
59231
59308
  // src/operations/streaming/handler.ts
59309
+ init_logger();
59232
59310
  var import_yauzl = __toESM(require_yauzl(), 1);
59233
59311
  import { createGunzip } from "zlib";
59234
59312
  import { pipeline } from "stream/promises";
@@ -60515,6 +60593,9 @@ function formatUptime(startedAt) {
60515
60593
  return `${minutes}m`;
60516
60594
  }
60517
60595
 
60596
+ // src/operations/sticky-message/handler.ts
60597
+ init_logger();
60598
+
60518
60599
  // src/utils/pr-detector.ts
60519
60600
  var PR_PATTERNS = [
60520
60601
  {
@@ -65162,6 +65243,8 @@ function getUpdateInfo() {
65162
65243
 
65163
65244
  // src/operations/commands/handler.ts
65164
65245
  init_emoji();
65246
+ init_logger();
65247
+ init_worktree();
65165
65248
  var log20 = createLogger("commands");
65166
65249
  var sessionLog2 = createSessionLog(log20);
65167
65250
  async function restartClaudeSession(session, cliOptions, ctx, actionName) {
@@ -65676,6 +65759,8 @@ async function handleBugReportApproval(session, isApproved, username) {
65676
65759
  session.messageManager?.clearPendingBugReport();
65677
65760
  }
65678
65761
  // src/operations/suggestions/branch.ts
65762
+ init_worktree();
65763
+ init_logger();
65679
65764
  import { exec as exec3 } from "child_process";
65680
65765
  import { promisify as promisify3 } from "util";
65681
65766
  var execAsync2 = promisify3(exec3);
@@ -65755,7 +65840,9 @@ async function suggestBranchNames(workingDir, userMessage) {
65755
65840
  }
65756
65841
 
65757
65842
  // src/operations/worktree/handler.ts
65843
+ init_worktree();
65758
65844
  import { randomUUID as randomUUID3 } from "crypto";
65845
+ init_logger();
65759
65846
  var log22 = createLogger("worktree");
65760
65847
  var sessionLog3 = createSessionLog(log22);
65761
65848
  function parseWorktreeError(error) {
@@ -66183,33 +66270,40 @@ async function switchToWorktree(session, branchOrPath, username, changeDirectory
66183
66270
  session.messageManager?.setWorktreeInfo(target.path, target.branch);
66184
66271
  session.isWorktreeOwner = false;
66185
66272
  }
66186
- async function buildWorktreeListMessage(session) {
66187
- const isRepo = await isGitRepository(session.workingDir);
66273
+ async function buildWorktreeListMessageFromDir(workingDir, formatter, currentWorkingDir) {
66274
+ const isRepo = await isGitRepository(workingDir);
66188
66275
  if (!isRepo) {
66189
- sessionLog3(session).warn(`\uD83C\uDF3F Not a git repository: ${session.workingDir}`);
66190
66276
  return null;
66191
66277
  }
66192
- const repoRoot = session.worktreeInfo?.repoRoot || await getRepositoryRoot(session.workingDir);
66278
+ const repoRoot = await getRepositoryRoot(workingDir);
66193
66279
  const worktrees = await listWorktrees(repoRoot);
66194
66280
  if (worktrees.length === 0) {
66195
- sessionLog3(session).debug(`\uD83C\uDF3F No worktrees found`);
66196
66281
  return "No worktrees found for this repository";
66197
66282
  }
66198
66283
  const shortRepoRoot = repoRoot.replace(process.env.HOME || "", "~");
66199
- const fmt = session.platform.getFormatter();
66200
- let message = `\uD83D\uDCCB ${fmt.formatBold("Worktrees for")} ${fmt.formatCode(shortRepoRoot)}:
66284
+ let message = `\uD83D\uDCCB ${formatter.formatBold("Worktrees for")} ${formatter.formatCode(shortRepoRoot)}:
66201
66285
 
66202
66286
  `;
66203
66287
  for (const wt of worktrees) {
66204
66288
  const shortPath = wt.isMain ? wt.path.replace(process.env.HOME || "", "~") : shortenPath(wt.path, undefined, { path: wt.path, branch: wt.branch });
66205
- const isCurrent = session.workingDir === wt.path;
66289
+ const isCurrent = currentWorkingDir === wt.path;
66206
66290
  const marker = isCurrent ? " ← current" : "";
66207
66291
  const label = wt.isMain ? "(main repository)" : "";
66208
- message += `• ${fmt.formatCode(wt.branch)} → ${fmt.formatCode(shortPath)} ${label}${marker}
66292
+ message += `• ${formatter.formatCode(wt.branch)} → ${formatter.formatCode(shortPath)} ${label}${marker}
66209
66293
  `;
66210
66294
  }
66211
66295
  return message;
66212
66296
  }
66297
+ async function buildWorktreeListMessage(session) {
66298
+ const repoRoot = session.worktreeInfo?.repoRoot;
66299
+ const isRepo = await isGitRepository(session.workingDir);
66300
+ if (!isRepo) {
66301
+ sessionLog3(session).warn(`\uD83C\uDF3F Not a git repository: ${session.workingDir}`);
66302
+ return null;
66303
+ }
66304
+ const workingDirForList = repoRoot || session.workingDir;
66305
+ return buildWorktreeListMessageFromDir(workingDirForList, session.platform.getFormatter(), session.workingDir);
66306
+ }
66213
66307
  async function listWorktreesCommand(session) {
66214
66308
  const message = await buildWorktreeListMessage(session);
66215
66309
  if (message === null) {
@@ -66299,6 +66393,7 @@ async function cleanupWorktreeCommand(session, username, hasOtherSessionsUsingWo
66299
66393
  }
66300
66394
  }
66301
66395
  // src/operations/events/handler.ts
66396
+ init_logger();
66302
66397
  var log23 = createLogger("events");
66303
66398
  var sessionLog4 = createSessionLog(log23);
66304
66399
  function detectAndExecuteClaudeCommands(text, session, ctx) {
@@ -66547,6 +66642,7 @@ function createSessionContext(config, state, ops) {
66547
66642
  }
66548
66643
  // src/operations/context-prompt/handler.ts
66549
66644
  init_emoji();
66645
+ init_logger();
66550
66646
  var log24 = createLogger("context");
66551
66647
  var sessionLog5 = createSessionLog(log24);
66552
66648
  var CONTEXT_PROMPT_TIMEOUT_MS = 30000;
@@ -66794,6 +66890,7 @@ function formatRelativeTime(date) {
66794
66890
  return `${diffMin} min ago`;
66795
66891
  }
66796
66892
  // src/session/lifecycle.ts
66893
+ init_worktree();
66797
66894
  var log25 = createLogger("lifecycle");
66798
66895
  var sessionLog6 = createSessionLog(log25);
66799
66896
  function mutableSessions(ctx) {
@@ -67719,6 +67816,7 @@ class SessionMonitor {
67719
67816
  }
67720
67817
  // src/operations/plugin/handler.ts
67721
67818
  import { spawn as spawn7 } from "child_process";
67819
+ init_logger();
67722
67820
  var log27 = createLogger("plugin");
67723
67821
  var sessionLog7 = createSessionLog(log27);
67724
67822
  async function runPluginCommand(args, cwd, timeout2 = 60000) {
@@ -67939,6 +68037,7 @@ class SessionRegistry {
67939
68037
  }
67940
68038
 
67941
68039
  // src/session/manager.ts
68040
+ init_logger();
67942
68041
  var log28 = createLogger("manager");
67943
68042
 
67944
68043
  class SessionManager extends EventEmitter4 {
@@ -68710,6 +68809,40 @@ class SessionManager extends EventEmitter4 {
68710
68809
  return;
68711
68810
  await listWorktreesCommand(session);
68712
68811
  }
68812
+ async listWorktreesWithoutSession(platformId, threadId) {
68813
+ const platform = this.platforms.get(platformId);
68814
+ if (!platform)
68815
+ return;
68816
+ const formatter = platform.getFormatter();
68817
+ const message = await buildWorktreeListMessageFromDir(this.workingDir, formatter, this.workingDir);
68818
+ if (message === null) {
68819
+ await platform.createPost(`❌ Current directory is not a git repository`, threadId);
68820
+ return;
68821
+ }
68822
+ await platform.createPost(message, threadId);
68823
+ }
68824
+ async switchToWorktreeWithoutSession(platformId, threadId, branchOrPath) {
68825
+ const platform = this.platforms.get(platformId);
68826
+ if (!platform)
68827
+ return;
68828
+ const formatter = platform.getFormatter();
68829
+ const { listWorktrees: listWorktrees2, getRepositoryRoot: getRepositoryRoot2, isGitRepository: isGitRepository2 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
68830
+ const isRepo = await isGitRepository2(this.workingDir);
68831
+ if (!isRepo) {
68832
+ await platform.createPost(`❌ Current directory is not a git repository`, threadId);
68833
+ return;
68834
+ }
68835
+ const repoRoot = await getRepositoryRoot2(this.workingDir);
68836
+ const worktrees = await listWorktrees2(repoRoot);
68837
+ const target = worktrees.find((wt) => wt.branch === branchOrPath || wt.path === branchOrPath || wt.path.endsWith(`/${branchOrPath}`));
68838
+ if (!target) {
68839
+ await platform.createPost(`❌ No worktree found for ${formatter.formatCode(branchOrPath)}`, threadId);
68840
+ return;
68841
+ }
68842
+ await platform.createPost(`✅ Switched to worktree ${formatter.formatCode(target.branch)} at ${formatter.formatCode(target.path)}
68843
+
68844
+ Mention me to start a session in this worktree.`, threadId);
68845
+ }
68713
68846
  async removeWorktreeCommand(threadId, branchOrPath, username) {
68714
68847
  const session = this.findSessionByThreadId(threadId);
68715
68848
  if (!session)
@@ -68782,7 +68915,7 @@ class SessionManager extends EventEmitter4 {
68782
68915
  applySideConversationLimits(session) {
68783
68916
  const MAX_COUNT = 5;
68784
68917
  const MAX_TOTAL_CHARS = 2000;
68785
- const MAX_AGE_MS = 30 * 60 * 1000;
68918
+ const MAX_AGE_MS = 1800000;
68786
68919
  const now = Date.now();
68787
68920
  let convs = session.pendingSideConversations || [];
68788
68921
  convs = convs.filter((c) => now - c.timestamp.getTime() < MAX_AGE_MS);
@@ -68804,7 +68937,11 @@ class SessionManager extends EventEmitter4 {
68804
68937
  const threadId = replyToPostId || "";
68805
68938
  const session = this.registry.find(platformId, threadId);
68806
68939
  if (session) {
68807
- await this.createAndSwitchToWorktree(session.threadId, branch, username);
68940
+ if (initialOptions?.switchToExisting) {
68941
+ await this.switchToWorktree(session.threadId, branch, username);
68942
+ } else {
68943
+ await this.createAndSwitchToWorktree(session.threadId, branch, username);
68944
+ }
68808
68945
  }
68809
68946
  }
68810
68947
  setShuttingDown() {
@@ -79307,6 +79444,9 @@ async function startUI(options2) {
79307
79444
  return createUIProvider(options2);
79308
79445
  }
79309
79446
 
79447
+ // src/index.ts
79448
+ init_logger();
79449
+
79310
79450
  // src/message-handler.ts
79311
79451
  async function handleMessage(client, session, post2, user, options2) {
79312
79452
  const { platformId, logger, onKill } = options2;
@@ -79485,7 +79625,7 @@ async function handleMessage(client, session, post2, user, options2) {
79485
79625
  prompt = prompt.replace(/on branch\s+\S+/i, "").trim();
79486
79626
  }
79487
79627
  }
79488
- if (!prompt.trim() && !files?.length) {
79628
+ if (!prompt.trim() && !files?.length && !worktreeBranch) {
79489
79629
  await client.createPost(`Mention me with your request`, threadRoot);
79490
79630
  return;
79491
79631
  }
@@ -79506,9 +79646,11 @@ async function handleMessage(client, session, post2, user, options2) {
79506
79646
  }
79507
79647
 
79508
79648
  // src/auto-update/manager.ts
79649
+ init_logger();
79509
79650
  import { EventEmitter as EventEmitter9 } from "events";
79510
79651
 
79511
79652
  // src/auto-update/checker.ts
79653
+ init_logger();
79512
79654
  import { EventEmitter as EventEmitter7 } from "events";
79513
79655
  var log29 = createLogger("checker");
79514
79656
  var PACKAGE_NAME = "claude-threads";
@@ -79639,6 +79781,7 @@ class UpdateChecker extends EventEmitter7 {
79639
79781
  }
79640
79782
 
79641
79783
  // src/auto-update/scheduler.ts
79784
+ init_logger();
79642
79785
  import { EventEmitter as EventEmitter8 } from "events";
79643
79786
 
79644
79787
  // src/auto-update/types.ts
@@ -79906,6 +80049,7 @@ class UpdateScheduler extends EventEmitter8 {
79906
80049
  }
79907
80050
 
79908
80051
  // src/auto-update/installer.ts
80052
+ init_logger();
79909
80053
  import { spawn as spawn8, spawnSync } from "child_process";
79910
80054
  import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
79911
80055
  import { dirname as dirname8, resolve as resolve6 } from "path";
@@ -38568,15 +38568,35 @@ var handleWorktree = async (ctx, args) => {
38568
38568
  }
38569
38569
  switch (subcommandOrBranch) {
38570
38570
  case "list":
38571
- await ctx.sessionManager.listWorktreesCommand(ctx.threadId, ctx.username);
38571
+ if (ctx.commandContext === "first-message") {
38572
+ await ctx.sessionManager.listWorktreesWithoutSession(ctx.client.platformId, ctx.threadId);
38573
+ } else {
38574
+ await ctx.sessionManager.listWorktreesCommand(ctx.threadId, ctx.username);
38575
+ }
38572
38576
  return { handled: true };
38573
- case "switch":
38577
+ case "switch": {
38574
38578
  if (!subArgs) {
38575
38579
  await ctx.client.createPost(`❌ Usage: ${ctx.formatter.formatCode("!worktree switch <branch>")}`, ctx.threadId);
38576
38580
  return { handled: true };
38577
38581
  }
38578
- await ctx.sessionManager.switchToWorktree(ctx.threadId, subArgs, ctx.username);
38582
+ const switchParts = subArgs.split(/\s+/);
38583
+ const branchName = switchParts[0];
38584
+ const remainingPrompt = switchParts.slice(1).join(" ").trim();
38585
+ if (ctx.commandContext === "first-message") {
38586
+ if (remainingPrompt) {
38587
+ return {
38588
+ worktreeBranch: branchName,
38589
+ continueProcessing: false,
38590
+ remainingText: remainingPrompt,
38591
+ sessionOptions: { switchToExisting: true }
38592
+ };
38593
+ }
38594
+ await ctx.sessionManager.switchToWorktreeWithoutSession(ctx.client.platformId, ctx.threadId, branchName);
38595
+ return { handled: true };
38596
+ }
38597
+ await ctx.sessionManager.switchToWorktree(ctx.threadId, branchName, ctx.username);
38579
38598
  return { handled: true };
38599
+ }
38580
38600
  case "remove":
38581
38601
  if (!subArgs) {
38582
38602
  await ctx.client.createPost(`❌ Usage: ${ctx.formatter.formatCode("!worktree remove <branch>")}`, ctx.threadId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
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",