chat-heimerdinger 0.1.15 → 0.1.17

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/dist/cli.js CHANGED
@@ -118686,37 +118686,51 @@ import { basename, join as join2 } from "node:path";
118686
118686
  class ClaudeCodeService {
118687
118687
  claudeConfigPath;
118688
118688
  projectsDir;
118689
- claudeBinaryPath;
118689
+ claudeBinaryPath = null;
118690
118690
  constructor(claudeConfigPath = CLAUDE_CONFIG_FILE) {
118691
118691
  this.claudeConfigPath = claudeConfigPath;
118692
118692
  this.projectsDir = CLAUDE_PROJECTS_DIR;
118693
+ }
118694
+ getClaudeBinary() {
118695
+ if (this.claudeBinaryPath)
118696
+ return this.claudeBinaryPath;
118693
118697
  this.claudeBinaryPath = this.findClaudeBinary();
118698
+ return this.claudeBinaryPath;
118694
118699
  }
118695
118700
  findClaudeBinary() {
118701
+ const home = process.env.HOME || "";
118702
+ const extraPaths = [
118703
+ process.env.NVM_BIN,
118704
+ "/usr/local/bin",
118705
+ "/usr/bin",
118706
+ "/bin",
118707
+ `${home}/.local/bin`,
118708
+ `${home}/.local/share/pnpm`,
118709
+ `${home}/.bun/bin`
118710
+ ].filter(Boolean);
118711
+ const fullPath = [...extraPaths, ...process.env.PATH?.split(":") || []];
118712
+ const dedupedPath = [...new Set(fullPath)].join(":");
118696
118713
  try {
118697
- const shellPaths = [
118698
- "/usr/local/bin",
118699
- "/usr/bin",
118700
- "/bin",
118701
- `${process.env.HOME}/.local/bin`,
118702
- ...process.env.PATH?.split(":") || []
118703
- ];
118704
118714
  const path = execSync("which claude", {
118705
118715
  encoding: "utf-8",
118706
- env: { ...process.env, PATH: shellPaths.join(":") }
118716
+ env: { ...process.env, PATH: dedupedPath }
118707
118717
  }).trim();
118708
118718
  if (path && existsSync3(path)) {
118709
- consola.debug("Found claude binary at:", path);
118719
+ consola.info("Found claude binary at:", path);
118710
118720
  return path;
118711
118721
  }
118712
- } catch {}
118722
+ } catch {
118723
+ consola.warn("which claude failed, trying fallback paths...");
118724
+ }
118713
118725
  const commonPaths = [
118714
118726
  "/usr/local/bin/claude",
118715
118727
  "/usr/bin/claude",
118716
- `${process.env.HOME}/.local/bin/claude`,
118717
- `${process.env.HOME}/.nvm/versions/node/${process.version}/bin/claude`
118728
+ `${home}/.local/bin/claude`,
118729
+ `${home}/.local/share/pnpm/claude`,
118730
+ `${home}/.bun/bin/claude`,
118731
+ `${home}/.nvm/versions/node/${process.version}/bin/claude`
118718
118732
  ];
118719
- const nvmDir = process.env.NVM_DIR || `${process.env.HOME}/.nvm`;
118733
+ const nvmDir = process.env.NVM_DIR || `${home}/.nvm`;
118720
118734
  if (existsSync3(nvmDir)) {
118721
118735
  try {
118722
118736
  const nvmVersionsDir = join2(nvmDir, "versions", "node");
@@ -118730,11 +118744,11 @@ class ClaudeCodeService {
118730
118744
  }
118731
118745
  for (const p2 of commonPaths) {
118732
118746
  if (existsSync3(p2)) {
118733
- consola.debug("Found claude binary at:", p2);
118747
+ consola.info("Found claude binary at fallback path:", p2);
118734
118748
  return p2;
118735
118749
  }
118736
118750
  }
118737
- consola.warn('Could not find claude binary, using "claude" and relying on PATH');
118751
+ consola.error("Could not find claude binary! Searched PATH:", dedupedPath, "and common paths:", commonPaths.join(", "));
118738
118752
  return "claude";
118739
118753
  }
118740
118754
  async isAvailable() {
@@ -118823,10 +118837,14 @@ class ClaudeCodeService {
118823
118837
  consola.info("Starting new session");
118824
118838
  }
118825
118839
  args.push(prompt2);
118826
- const spawnInfo = `Spawning claude: binary=${this.claudeBinaryPath}, cwd=${projectDir}`;
118827
- consola.info(spawnInfo);
118828
- console.log(spawnInfo);
118829
- const proc = spawn2(this.claudeBinaryPath, args, {
118840
+ let binaryPath = this.getClaudeBinary();
118841
+ if (binaryPath !== "claude" && !existsSync3(binaryPath)) {
118842
+ consola.warn(`Claude binary no longer exists at ${binaryPath}, re-finding...`);
118843
+ this.claudeBinaryPath = null;
118844
+ binaryPath = this.getClaudeBinary();
118845
+ }
118846
+ consola.info(`Spawning claude: binary=${binaryPath}, cwd=${projectDir}`);
118847
+ const proc = spawn2(binaryPath, args, {
118830
118848
  cwd: projectDir,
118831
118849
  stdio: ["ignore", "pipe", "pipe"],
118832
118850
  env: { ...process.env },
@@ -118889,7 +118907,7 @@ class ClaudeCodeService {
118889
118907
  }
118890
118908
  });
118891
118909
  proc.on("error", (err) => {
118892
- const errorMsg = `Claude process error: ${err.message}, binary path was: ${this.claudeBinaryPath}`;
118910
+ const errorMsg = `Claude process error: ${err.message}, binary path was: ${binaryPath}`;
118893
118911
  consola.error(errorMsg);
118894
118912
  console.error(errorMsg, err);
118895
118913
  reject(err);
@@ -119826,9 +119844,11 @@ class SlackAdapter {
119826
119844
  name = "slack";
119827
119845
  app;
119828
119846
  config;
119847
+ botUserId = null;
119829
119848
  messageHandlers = [];
119830
119849
  audioMessageHandlers = [];
119831
119850
  interactionHandlers = [];
119851
+ processedMessages = new Set;
119832
119852
  constructor(config) {
119833
119853
  this.config = config;
119834
119854
  this.app = new import_bolt.App({
@@ -119837,6 +119857,9 @@ class SlackAdapter {
119837
119857
  socketMode: config.socketMode,
119838
119858
  appToken: config.appToken
119839
119859
  });
119860
+ this.app.error(async (error) => {
119861
+ consola.error("Slack Bolt error:", error);
119862
+ });
119840
119863
  this.setupEventHandlers();
119841
119864
  }
119842
119865
  async init() {}
@@ -119892,7 +119915,14 @@ class SlackAdapter {
119892
119915
  async start() {
119893
119916
  consola.info("Slack: Starting Socket Mode...");
119894
119917
  await this.app.start();
119895
- consola.success("Slack adapter connected");
119918
+ try {
119919
+ const authResult = await this.app.client.auth.test();
119920
+ this.botUserId = authResult.user_id || null;
119921
+ consola.info(`Bot user ID: ${this.botUserId}`);
119922
+ } catch (error) {
119923
+ consola.warn("Failed to get bot user ID:", error);
119924
+ }
119925
+ consola.success("Slack adapter connected and ready");
119896
119926
  }
119897
119927
  async stop() {
119898
119928
  await this.app.stop();
@@ -119926,9 +119956,20 @@ class SlackAdapter {
119926
119956
  setupEventHandlers() {
119927
119957
  this.app.event("message", async ({ event, client }) => {
119928
119958
  const msg = event;
119929
- if (msg.bot_id)
119959
+ console.log(`[slack:message] ts=${msg.ts} user=${msg.user} channel=${msg.channel} channel_type=${msg.channel_type} subtype=${msg.subtype} bot_id=${msg.bot_id} text="${(msg.text || "").slice(0, 40)}"`);
119960
+ if (msg.bot_id) {
119961
+ consola.info(`[msg filter] skip: bot_id=${msg.bot_id}`);
119962
+ return;
119963
+ }
119964
+ if (this.botUserId && msg.user === this.botUserId) {
119965
+ consola.info(`[msg filter] skip: bot user ${msg.user}`);
119966
+ return;
119967
+ }
119968
+ if (msg.edited) {
119969
+ consola.info(`[msg filter] skip: edited msg from ${msg.user}`);
119930
119970
  return;
119931
- consola.debug(`Message event: subtype=${msg.subtype}, has_files=${!!msg.files?.length}`);
119971
+ }
119972
+ consola.info(`[msg event] user=${msg.user} subtype=${msg.subtype} isDM=${msg.channel_type === "im"} text="${(msg.text || "").slice(0, 50)}"`);
119932
119973
  const isDM = msg.channel_type === "im";
119933
119974
  const hasMention = /<@[A-Z0-9]+>/i.test(msg.text || "");
119934
119975
  const isFileShare = msg.subtype === "file_share";
@@ -119962,18 +120003,38 @@ class SlackAdapter {
119962
120003
  const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
119963
120004
  if (!text)
119964
120005
  return;
120006
+ this.processedMessages.add(msg.ts);
120007
+ if (this.processedMessages.size > 100) {
120008
+ const entries = [...this.processedMessages];
120009
+ this.processedMessages = new Set(entries.slice(-50));
120010
+ }
119965
120011
  for (const handler of this.messageHandlers) {
119966
120012
  await handler({ text, context });
119967
120013
  }
119968
120014
  });
119969
120015
  this.app.event("app_mention", async ({ event }) => {
120016
+ console.log(`[slack:app_mention] ts=${event.ts} user=${event.user} channel=${event.channel} text="${(event.text || "").slice(0, 40)}"`);
120017
+ consola.info(`[app_mention] user=${event.user} channel=${event.channel} text="${(event.text || "").slice(0, 50)}"`);
120018
+ if (this.processedMessages.has(event.ts)) {
120019
+ consola.info(`[app_mention] skip: already processed by message handler ts=${event.ts}`);
120020
+ return;
120021
+ }
120022
+ if (this.botUserId && event.user === this.botUserId) {
120023
+ consola.info(`[app_mention] skip: bot user ${event.user}`);
120024
+ return;
120025
+ }
119970
120026
  const text = (event.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
120027
+ if (!text) {
120028
+ consola.info("[app_mention] skip: empty text after removing mentions");
120029
+ return;
120030
+ }
119971
120031
  const context = {
119972
120032
  channelId: event.channel,
119973
120033
  userId: event.user || "",
119974
120034
  threadTs: event.thread_ts || event.ts,
119975
120035
  messageTs: event.ts
119976
120036
  };
120037
+ this.processedMessages.add(event.ts);
119977
120038
  for (const handler of this.messageHandlers) {
119978
120039
  await handler({ text, context });
119979
120040
  }
@@ -120002,35 +120063,47 @@ class SlackAdapter {
120002
120063
  });
120003
120064
  this.app.command("/project", async ({ command, ack }) => {
120004
120065
  await ack();
120005
- consola.debug(`Slash command /project from channel: ${command.channel_id}`);
120006
- const context = {
120007
- channelId: command.channel_id,
120008
- userId: command.user_id
120009
- };
120010
- for (const handler of this.interactionHandlers) {
120011
- await handler("show_project_selector", "", context);
120066
+ consola.info(`Slash command /project from channel: ${command.channel_id}`);
120067
+ try {
120068
+ const context = {
120069
+ channelId: command.channel_id,
120070
+ userId: command.user_id
120071
+ };
120072
+ for (const handler of this.interactionHandlers) {
120073
+ await handler("show_project_selector", "", context);
120074
+ }
120075
+ } catch (error) {
120076
+ consola.error("Error handling /project command:", error);
120012
120077
  }
120013
120078
  });
120014
120079
  this.app.command("/stop", async ({ command, ack }) => {
120015
120080
  await ack();
120016
- consola.debug(`Slash command /stop from channel: ${command.channel_id}`);
120017
- const context = {
120018
- channelId: command.channel_id,
120019
- userId: command.user_id
120020
- };
120021
- for (const handler of this.interactionHandlers) {
120022
- await handler("stop_execution", "", context);
120081
+ consola.info(`Slash command /stop from channel: ${command.channel_id}`);
120082
+ try {
120083
+ const context = {
120084
+ channelId: command.channel_id,
120085
+ userId: command.user_id
120086
+ };
120087
+ for (const handler of this.interactionHandlers) {
120088
+ await handler("stop_execution", "", context);
120089
+ }
120090
+ } catch (error) {
120091
+ consola.error("Error handling /stop command:", error);
120023
120092
  }
120024
120093
  });
120025
120094
  this.app.command("/clear", async ({ command, ack }) => {
120026
120095
  await ack();
120027
- consola.debug(`Slash command /clear from channel: ${command.channel_id}`);
120028
- const context = {
120029
- channelId: command.channel_id,
120030
- userId: command.user_id
120031
- };
120032
- for (const handler of this.interactionHandlers) {
120033
- await handler("clear_session", "", context);
120096
+ consola.info(`Slash command /clear from channel: ${command.channel_id}`);
120097
+ try {
120098
+ const context = {
120099
+ channelId: command.channel_id,
120100
+ userId: command.user_id
120101
+ };
120102
+ for (const handler of this.interactionHandlers) {
120103
+ await handler("clear_session", "", context);
120104
+ }
120105
+ } catch (error) {
120106
+ consola.error("Error handling /clear command:", error);
120034
120107
  }
120035
120108
  });
120036
120109
  }
@@ -120400,32 +120473,56 @@ class MessageProcessor {
120400
120473
  }
120401
120474
  return;
120402
120475
  }
120476
+ getChannelIds() {
120477
+ return [...this.userStates.keys()];
120478
+ }
120403
120479
  async handleMessage(message, adapter) {
120404
120480
  const { text, context } = message;
120405
120481
  const command = text.trim().toLowerCase();
120406
- consola.debug(`Received message: ${text}`);
120482
+ consola.info(`[handleMessage] text="${text}" command="${command}" channel=${context.channelId}`);
120407
120483
  if (command === "help" || command === "/help") {
120484
+ consola.info("[handleMessage] -> help");
120408
120485
  await this.sendHelp(adapter, context);
120409
120486
  return;
120410
120487
  }
120411
120488
  if (command === "/projects" || command === "projects") {
120489
+ consola.info("[handleMessage] -> projects");
120412
120490
  await this.sendProjectList(adapter, context);
120413
120491
  return;
120414
120492
  }
120415
120493
  if (command === "/status" || command === "status") {
120494
+ consola.info("[handleMessage] -> status");
120416
120495
  await this.sendStatus(adapter, context);
120417
120496
  return;
120418
120497
  }
120419
- if (command.startsWith("/project ")) {
120420
- const projectName = text.slice(9).trim();
120498
+ if (command === "/project" || command === "project") {
120499
+ consola.info("[handleMessage] -> showProjectSelector");
120500
+ await this.showProjectSelector(adapter, context);
120501
+ return;
120502
+ }
120503
+ if (command.startsWith("/project ") || command.startsWith("project ")) {
120504
+ const projectName = command.startsWith("/") ? text.slice(9).trim() : text.slice(8).trim();
120505
+ consola.info(`[handleMessage] -> selectProject: ${projectName}`);
120421
120506
  await this.selectProject(adapter, context, projectName);
120422
120507
  return;
120423
120508
  }
120424
- if (command.startsWith("/session ")) {
120425
- const sessionId = text.slice(9).trim();
120509
+ if (command.startsWith("/session ") || command.startsWith("session ")) {
120510
+ const sessionId = command.startsWith("/") ? text.slice(9).trim() : text.slice(8).trim();
120511
+ consola.info(`[handleMessage] -> selectSession: ${sessionId}`);
120426
120512
  await this.selectSession(adapter, context, sessionId);
120427
120513
  return;
120428
120514
  }
120515
+ if (command === "/stop" || command === "stop") {
120516
+ consola.info("[handleMessage] -> stop");
120517
+ await this.handleStopExecution(adapter, context);
120518
+ return;
120519
+ }
120520
+ if (command === "/clear" || command === "clear") {
120521
+ consola.info("[handleMessage] -> clear");
120522
+ await this.handleClearSession(adapter, context);
120523
+ return;
120524
+ }
120525
+ consola.info("[handleMessage] -> handlePrompt");
120429
120526
  await this.handlePrompt(adapter, context, text);
120430
120527
  }
120431
120528
  async handleAudioMessage(message, adapter) {
@@ -120635,11 +120732,14 @@ _Cost: $${cost.toFixed(4)}_`;
120635
120732
  const help = `*Heimerdinger - Claude Code Bridge*
120636
120733
 
120637
120734
  Available commands:
120638
- \`/projects\` - List available projects
120639
- \`/project <name>\` - Select a project
120640
- \`/session <id>\` - Resume a session
120641
- \`/status\` - Show current status
120642
- \`/help\` - Show this help
120735
+ • \`project\` - Select a project (interactive)
120736
+ \`project <name>\` - Select a project by name
120737
+ \`projects\` - List available projects
120738
+ • \`session <id>\` - Resume a session
120739
+ • \`stop\` - Stop current execution
120740
+ • \`clear\` - Clear session
120741
+ • \`status\` - Show current status
120742
+ • \`help\` - Show this help
120643
120743
 
120644
120744
  Or just send a message to start coding with Claude!`;
120645
120745
  await adapter.sendMessage(context.channelId, help);
@@ -121404,6 +121504,12 @@ class HeimerdingerServer {
121404
121504
  });
121405
121505
  await adapter.start();
121406
121506
  consola.success("Slack adapter started");
121507
+ const channelStates = this.messageProcessor.getChannelIds();
121508
+ for (const channelId of channelStates) {
121509
+ try {
121510
+ await adapter.sendMessage(channelId, `\uD83D\uDFE2 Heimerdinger online (${new Date().toLocaleTimeString()})`);
121511
+ } catch {}
121512
+ }
121407
121513
  } catch (error) {
121408
121514
  consola.error("Failed to initialize Slack adapter:", error);
121409
121515
  }
@@ -121510,13 +121616,17 @@ Shutting down...`);
121510
121616
  if (!existsSync8(daemonScript)) {
121511
121617
  throw new Error(`Daemon script not found at: ${daemonScript}`);
121512
121618
  }
121619
+ const home = process.env.HOME || "";
121513
121620
  const pathDirs = [
121621
+ process.env.NVM_BIN,
121514
121622
  "/usr/local/bin",
121515
121623
  "/usr/bin",
121516
121624
  "/bin",
121517
- `${process.env.HOME}/.local/bin`,
121625
+ `${home}/.local/bin`,
121626
+ `${home}/.local/share/pnpm`,
121627
+ `${home}/.bun/bin`,
121518
121628
  ...process.env.PATH?.split(":") || []
121519
- ];
121629
+ ].filter(Boolean);
121520
121630
  const fullPath = [...new Set(pathDirs)].join(":");
121521
121631
  const proc = spawn4("sh", ["-c", `node "${daemonScript}" >> "${LOG_FILE}" 2>&1`], {
121522
121632
  cwd: process.cwd(),
@@ -116259,9 +116259,11 @@ class SlackAdapter {
116259
116259
  name = "slack";
116260
116260
  app;
116261
116261
  config;
116262
+ botUserId = null;
116262
116263
  messageHandlers = [];
116263
116264
  audioMessageHandlers = [];
116264
116265
  interactionHandlers = [];
116266
+ processedMessages = new Set;
116265
116267
  constructor(config) {
116266
116268
  this.config = config;
116267
116269
  this.app = new import_bolt.App({
@@ -116270,6 +116272,9 @@ class SlackAdapter {
116270
116272
  socketMode: config.socketMode,
116271
116273
  appToken: config.appToken
116272
116274
  });
116275
+ this.app.error(async (error) => {
116276
+ consola.error("Slack Bolt error:", error);
116277
+ });
116273
116278
  this.setupEventHandlers();
116274
116279
  }
116275
116280
  async init() {}
@@ -116325,7 +116330,14 @@ class SlackAdapter {
116325
116330
  async start() {
116326
116331
  consola.info("Slack: Starting Socket Mode...");
116327
116332
  await this.app.start();
116328
- consola.success("Slack adapter connected");
116333
+ try {
116334
+ const authResult = await this.app.client.auth.test();
116335
+ this.botUserId = authResult.user_id || null;
116336
+ consola.info(`Bot user ID: ${this.botUserId}`);
116337
+ } catch (error) {
116338
+ consola.warn("Failed to get bot user ID:", error);
116339
+ }
116340
+ consola.success("Slack adapter connected and ready");
116329
116341
  }
116330
116342
  async stop() {
116331
116343
  await this.app.stop();
@@ -116359,9 +116371,20 @@ class SlackAdapter {
116359
116371
  setupEventHandlers() {
116360
116372
  this.app.event("message", async ({ event, client }) => {
116361
116373
  const msg = event;
116362
- if (msg.bot_id)
116374
+ console.log(`[slack:message] ts=${msg.ts} user=${msg.user} channel=${msg.channel} channel_type=${msg.channel_type} subtype=${msg.subtype} bot_id=${msg.bot_id} text="${(msg.text || "").slice(0, 40)}"`);
116375
+ if (msg.bot_id) {
116376
+ consola.info(`[msg filter] skip: bot_id=${msg.bot_id}`);
116377
+ return;
116378
+ }
116379
+ if (this.botUserId && msg.user === this.botUserId) {
116380
+ consola.info(`[msg filter] skip: bot user ${msg.user}`);
116381
+ return;
116382
+ }
116383
+ if (msg.edited) {
116384
+ consola.info(`[msg filter] skip: edited msg from ${msg.user}`);
116363
116385
  return;
116364
- consola.debug(`Message event: subtype=${msg.subtype}, has_files=${!!msg.files?.length}`);
116386
+ }
116387
+ consola.info(`[msg event] user=${msg.user} subtype=${msg.subtype} isDM=${msg.channel_type === "im"} text="${(msg.text || "").slice(0, 50)}"`);
116365
116388
  const isDM = msg.channel_type === "im";
116366
116389
  const hasMention = /<@[A-Z0-9]+>/i.test(msg.text || "");
116367
116390
  const isFileShare = msg.subtype === "file_share";
@@ -116395,18 +116418,38 @@ class SlackAdapter {
116395
116418
  const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
116396
116419
  if (!text)
116397
116420
  return;
116421
+ this.processedMessages.add(msg.ts);
116422
+ if (this.processedMessages.size > 100) {
116423
+ const entries = [...this.processedMessages];
116424
+ this.processedMessages = new Set(entries.slice(-50));
116425
+ }
116398
116426
  for (const handler of this.messageHandlers) {
116399
116427
  await handler({ text, context });
116400
116428
  }
116401
116429
  });
116402
116430
  this.app.event("app_mention", async ({ event }) => {
116431
+ console.log(`[slack:app_mention] ts=${event.ts} user=${event.user} channel=${event.channel} text="${(event.text || "").slice(0, 40)}"`);
116432
+ consola.info(`[app_mention] user=${event.user} channel=${event.channel} text="${(event.text || "").slice(0, 50)}"`);
116433
+ if (this.processedMessages.has(event.ts)) {
116434
+ consola.info(`[app_mention] skip: already processed by message handler ts=${event.ts}`);
116435
+ return;
116436
+ }
116437
+ if (this.botUserId && event.user === this.botUserId) {
116438
+ consola.info(`[app_mention] skip: bot user ${event.user}`);
116439
+ return;
116440
+ }
116403
116441
  const text = (event.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
116442
+ if (!text) {
116443
+ consola.info("[app_mention] skip: empty text after removing mentions");
116444
+ return;
116445
+ }
116404
116446
  const context = {
116405
116447
  channelId: event.channel,
116406
116448
  userId: event.user || "",
116407
116449
  threadTs: event.thread_ts || event.ts,
116408
116450
  messageTs: event.ts
116409
116451
  };
116452
+ this.processedMessages.add(event.ts);
116410
116453
  for (const handler of this.messageHandlers) {
116411
116454
  await handler({ text, context });
116412
116455
  }
@@ -116435,35 +116478,47 @@ class SlackAdapter {
116435
116478
  });
116436
116479
  this.app.command("/project", async ({ command, ack }) => {
116437
116480
  await ack();
116438
- consola.debug(`Slash command /project from channel: ${command.channel_id}`);
116439
- const context = {
116440
- channelId: command.channel_id,
116441
- userId: command.user_id
116442
- };
116443
- for (const handler of this.interactionHandlers) {
116444
- await handler("show_project_selector", "", context);
116481
+ consola.info(`Slash command /project from channel: ${command.channel_id}`);
116482
+ try {
116483
+ const context = {
116484
+ channelId: command.channel_id,
116485
+ userId: command.user_id
116486
+ };
116487
+ for (const handler of this.interactionHandlers) {
116488
+ await handler("show_project_selector", "", context);
116489
+ }
116490
+ } catch (error) {
116491
+ consola.error("Error handling /project command:", error);
116445
116492
  }
116446
116493
  });
116447
116494
  this.app.command("/stop", async ({ command, ack }) => {
116448
116495
  await ack();
116449
- consola.debug(`Slash command /stop from channel: ${command.channel_id}`);
116450
- const context = {
116451
- channelId: command.channel_id,
116452
- userId: command.user_id
116453
- };
116454
- for (const handler of this.interactionHandlers) {
116455
- await handler("stop_execution", "", context);
116496
+ consola.info(`Slash command /stop from channel: ${command.channel_id}`);
116497
+ try {
116498
+ const context = {
116499
+ channelId: command.channel_id,
116500
+ userId: command.user_id
116501
+ };
116502
+ for (const handler of this.interactionHandlers) {
116503
+ await handler("stop_execution", "", context);
116504
+ }
116505
+ } catch (error) {
116506
+ consola.error("Error handling /stop command:", error);
116456
116507
  }
116457
116508
  });
116458
116509
  this.app.command("/clear", async ({ command, ack }) => {
116459
116510
  await ack();
116460
- consola.debug(`Slash command /clear from channel: ${command.channel_id}`);
116461
- const context = {
116462
- channelId: command.channel_id,
116463
- userId: command.user_id
116464
- };
116465
- for (const handler of this.interactionHandlers) {
116466
- await handler("clear_session", "", context);
116511
+ consola.info(`Slash command /clear from channel: ${command.channel_id}`);
116512
+ try {
116513
+ const context = {
116514
+ channelId: command.channel_id,
116515
+ userId: command.user_id
116516
+ };
116517
+ for (const handler of this.interactionHandlers) {
116518
+ await handler("clear_session", "", context);
116519
+ }
116520
+ } catch (error) {
116521
+ consola.error("Error handling /clear command:", error);
116467
116522
  }
116468
116523
  });
116469
116524
  }
@@ -116628,37 +116683,51 @@ import { basename, join as join2 } from "node:path";
116628
116683
  class ClaudeCodeService {
116629
116684
  claudeConfigPath;
116630
116685
  projectsDir;
116631
- claudeBinaryPath;
116686
+ claudeBinaryPath = null;
116632
116687
  constructor(claudeConfigPath = CLAUDE_CONFIG_FILE) {
116633
116688
  this.claudeConfigPath = claudeConfigPath;
116634
116689
  this.projectsDir = CLAUDE_PROJECTS_DIR;
116690
+ }
116691
+ getClaudeBinary() {
116692
+ if (this.claudeBinaryPath)
116693
+ return this.claudeBinaryPath;
116635
116694
  this.claudeBinaryPath = this.findClaudeBinary();
116695
+ return this.claudeBinaryPath;
116636
116696
  }
116637
116697
  findClaudeBinary() {
116698
+ const home = process.env.HOME || "";
116699
+ const extraPaths = [
116700
+ process.env.NVM_BIN,
116701
+ "/usr/local/bin",
116702
+ "/usr/bin",
116703
+ "/bin",
116704
+ `${home}/.local/bin`,
116705
+ `${home}/.local/share/pnpm`,
116706
+ `${home}/.bun/bin`
116707
+ ].filter(Boolean);
116708
+ const fullPath = [...extraPaths, ...process.env.PATH?.split(":") || []];
116709
+ const dedupedPath = [...new Set(fullPath)].join(":");
116638
116710
  try {
116639
- const shellPaths = [
116640
- "/usr/local/bin",
116641
- "/usr/bin",
116642
- "/bin",
116643
- `${process.env.HOME}/.local/bin`,
116644
- ...process.env.PATH?.split(":") || []
116645
- ];
116646
116711
  const path = execSync("which claude", {
116647
116712
  encoding: "utf-8",
116648
- env: { ...process.env, PATH: shellPaths.join(":") }
116713
+ env: { ...process.env, PATH: dedupedPath }
116649
116714
  }).trim();
116650
116715
  if (path && existsSync2(path)) {
116651
- consola.debug("Found claude binary at:", path);
116716
+ consola.info("Found claude binary at:", path);
116652
116717
  return path;
116653
116718
  }
116654
- } catch {}
116719
+ } catch {
116720
+ consola.warn("which claude failed, trying fallback paths...");
116721
+ }
116655
116722
  const commonPaths = [
116656
116723
  "/usr/local/bin/claude",
116657
116724
  "/usr/bin/claude",
116658
- `${process.env.HOME}/.local/bin/claude`,
116659
- `${process.env.HOME}/.nvm/versions/node/${process.version}/bin/claude`
116725
+ `${home}/.local/bin/claude`,
116726
+ `${home}/.local/share/pnpm/claude`,
116727
+ `${home}/.bun/bin/claude`,
116728
+ `${home}/.nvm/versions/node/${process.version}/bin/claude`
116660
116729
  ];
116661
- const nvmDir = process.env.NVM_DIR || `${process.env.HOME}/.nvm`;
116730
+ const nvmDir = process.env.NVM_DIR || `${home}/.nvm`;
116662
116731
  if (existsSync2(nvmDir)) {
116663
116732
  try {
116664
116733
  const nvmVersionsDir = join2(nvmDir, "versions", "node");
@@ -116672,11 +116741,11 @@ class ClaudeCodeService {
116672
116741
  }
116673
116742
  for (const p of commonPaths) {
116674
116743
  if (existsSync2(p)) {
116675
- consola.debug("Found claude binary at:", p);
116744
+ consola.info("Found claude binary at fallback path:", p);
116676
116745
  return p;
116677
116746
  }
116678
116747
  }
116679
- consola.warn('Could not find claude binary, using "claude" and relying on PATH');
116748
+ consola.error("Could not find claude binary! Searched PATH:", dedupedPath, "and common paths:", commonPaths.join(", "));
116680
116749
  return "claude";
116681
116750
  }
116682
116751
  async isAvailable() {
@@ -116765,10 +116834,14 @@ class ClaudeCodeService {
116765
116834
  consola.info("Starting new session");
116766
116835
  }
116767
116836
  args.push(prompt2);
116768
- const spawnInfo = `Spawning claude: binary=${this.claudeBinaryPath}, cwd=${projectDir}`;
116769
- consola.info(spawnInfo);
116770
- console.log(spawnInfo);
116771
- const proc = spawn(this.claudeBinaryPath, args, {
116837
+ let binaryPath = this.getClaudeBinary();
116838
+ if (binaryPath !== "claude" && !existsSync2(binaryPath)) {
116839
+ consola.warn(`Claude binary no longer exists at ${binaryPath}, re-finding...`);
116840
+ this.claudeBinaryPath = null;
116841
+ binaryPath = this.getClaudeBinary();
116842
+ }
116843
+ consola.info(`Spawning claude: binary=${binaryPath}, cwd=${projectDir}`);
116844
+ const proc = spawn(binaryPath, args, {
116772
116845
  cwd: projectDir,
116773
116846
  stdio: ["ignore", "pipe", "pipe"],
116774
116847
  env: { ...process.env },
@@ -116831,7 +116904,7 @@ class ClaudeCodeService {
116831
116904
  }
116832
116905
  });
116833
116906
  proc.on("error", (err) => {
116834
- const errorMsg = `Claude process error: ${err.message}, binary path was: ${this.claudeBinaryPath}`;
116907
+ const errorMsg = `Claude process error: ${err.message}, binary path was: ${binaryPath}`;
116835
116908
  consola.error(errorMsg);
116836
116909
  console.error(errorMsg, err);
116837
116910
  reject(err);
@@ -117138,32 +117211,56 @@ class MessageProcessor {
117138
117211
  }
117139
117212
  return;
117140
117213
  }
117214
+ getChannelIds() {
117215
+ return [...this.userStates.keys()];
117216
+ }
117141
117217
  async handleMessage(message, adapter) {
117142
117218
  const { text, context } = message;
117143
117219
  const command = text.trim().toLowerCase();
117144
- consola.debug(`Received message: ${text}`);
117220
+ consola.info(`[handleMessage] text="${text}" command="${command}" channel=${context.channelId}`);
117145
117221
  if (command === "help" || command === "/help") {
117222
+ consola.info("[handleMessage] -> help");
117146
117223
  await this.sendHelp(adapter, context);
117147
117224
  return;
117148
117225
  }
117149
117226
  if (command === "/projects" || command === "projects") {
117227
+ consola.info("[handleMessage] -> projects");
117150
117228
  await this.sendProjectList(adapter, context);
117151
117229
  return;
117152
117230
  }
117153
117231
  if (command === "/status" || command === "status") {
117232
+ consola.info("[handleMessage] -> status");
117154
117233
  await this.sendStatus(adapter, context);
117155
117234
  return;
117156
117235
  }
117157
- if (command.startsWith("/project ")) {
117158
- const projectName = text.slice(9).trim();
117236
+ if (command === "/project" || command === "project") {
117237
+ consola.info("[handleMessage] -> showProjectSelector");
117238
+ await this.showProjectSelector(adapter, context);
117239
+ return;
117240
+ }
117241
+ if (command.startsWith("/project ") || command.startsWith("project ")) {
117242
+ const projectName = command.startsWith("/") ? text.slice(9).trim() : text.slice(8).trim();
117243
+ consola.info(`[handleMessage] -> selectProject: ${projectName}`);
117159
117244
  await this.selectProject(adapter, context, projectName);
117160
117245
  return;
117161
117246
  }
117162
- if (command.startsWith("/session ")) {
117163
- const sessionId = text.slice(9).trim();
117247
+ if (command.startsWith("/session ") || command.startsWith("session ")) {
117248
+ const sessionId = command.startsWith("/") ? text.slice(9).trim() : text.slice(8).trim();
117249
+ consola.info(`[handleMessage] -> selectSession: ${sessionId}`);
117164
117250
  await this.selectSession(adapter, context, sessionId);
117165
117251
  return;
117166
117252
  }
117253
+ if (command === "/stop" || command === "stop") {
117254
+ consola.info("[handleMessage] -> stop");
117255
+ await this.handleStopExecution(adapter, context);
117256
+ return;
117257
+ }
117258
+ if (command === "/clear" || command === "clear") {
117259
+ consola.info("[handleMessage] -> clear");
117260
+ await this.handleClearSession(adapter, context);
117261
+ return;
117262
+ }
117263
+ consola.info("[handleMessage] -> handlePrompt");
117167
117264
  await this.handlePrompt(adapter, context, text);
117168
117265
  }
117169
117266
  async handleAudioMessage(message, adapter) {
@@ -117373,11 +117470,14 @@ _Cost: $${cost.toFixed(4)}_`;
117373
117470
  const help = `*Heimerdinger - Claude Code Bridge*
117374
117471
 
117375
117472
  Available commands:
117376
- \`/projects\` - List available projects
117377
- \`/project <name>\` - Select a project
117378
- \`/session <id>\` - Resume a session
117379
- \`/status\` - Show current status
117380
- \`/help\` - Show this help
117473
+ • \`project\` - Select a project (interactive)
117474
+ \`project <name>\` - Select a project by name
117475
+ \`projects\` - List available projects
117476
+ • \`session <id>\` - Resume a session
117477
+ • \`stop\` - Stop current execution
117478
+ • \`clear\` - Clear session
117479
+ • \`status\` - Show current status
117480
+ • \`help\` - Show this help
117381
117481
 
117382
117482
  Or just send a message to start coding with Claude!`;
117383
117483
  await adapter.sendMessage(context.channelId, help);
@@ -118142,6 +118242,12 @@ class HeimerdingerServer {
118142
118242
  });
118143
118243
  await adapter.start();
118144
118244
  consola.success("Slack adapter started");
118245
+ const channelStates = this.messageProcessor.getChannelIds();
118246
+ for (const channelId of channelStates) {
118247
+ try {
118248
+ await adapter.sendMessage(channelId, `\uD83D\uDFE2 Heimerdinger online (${new Date().toLocaleTimeString()})`);
118249
+ } catch {}
118250
+ }
118145
118251
  } catch (error) {
118146
118252
  consola.error("Failed to initialize Slack adapter:", error);
118147
118253
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-heimerdinger",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Bridge IM tools (Slack/Lark) with Claude Code for vibe coding",
5
5
  "type": "module",
6
6
  "bin": {