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 +167 -57
- package/dist/daemon-entry.js +161 -55
- package/package.json +1 -1
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:
|
|
118716
|
+
env: { ...process.env, PATH: dedupedPath }
|
|
118707
118717
|
}).trim();
|
|
118708
118718
|
if (path && existsSync3(path)) {
|
|
118709
|
-
consola.
|
|
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
|
-
`${
|
|
118717
|
-
`${
|
|
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 || `${
|
|
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.
|
|
118747
|
+
consola.info("Found claude binary at fallback path:", p2);
|
|
118734
118748
|
return p2;
|
|
118735
118749
|
}
|
|
118736
118750
|
}
|
|
118737
|
-
consola.
|
|
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
|
-
|
|
118827
|
-
|
|
118828
|
-
|
|
118829
|
-
|
|
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: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
120006
|
-
|
|
120007
|
-
|
|
120008
|
-
|
|
120009
|
-
|
|
120010
|
-
|
|
120011
|
-
|
|
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.
|
|
120017
|
-
|
|
120018
|
-
|
|
120019
|
-
|
|
120020
|
-
|
|
120021
|
-
|
|
120022
|
-
|
|
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.
|
|
120028
|
-
|
|
120029
|
-
|
|
120030
|
-
|
|
120031
|
-
|
|
120032
|
-
|
|
120033
|
-
|
|
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.
|
|
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
|
|
120420
|
-
|
|
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
|
-
•
|
|
120639
|
-
•
|
|
120640
|
-
•
|
|
120641
|
-
•
|
|
120642
|
-
•
|
|
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
|
-
`${
|
|
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(),
|
package/dist/daemon-entry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
116439
|
-
|
|
116440
|
-
|
|
116441
|
-
|
|
116442
|
-
|
|
116443
|
-
|
|
116444
|
-
|
|
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.
|
|
116450
|
-
|
|
116451
|
-
|
|
116452
|
-
|
|
116453
|
-
|
|
116454
|
-
|
|
116455
|
-
|
|
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.
|
|
116461
|
-
|
|
116462
|
-
|
|
116463
|
-
|
|
116464
|
-
|
|
116465
|
-
|
|
116466
|
-
|
|
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:
|
|
116713
|
+
env: { ...process.env, PATH: dedupedPath }
|
|
116649
116714
|
}).trim();
|
|
116650
116715
|
if (path && existsSync2(path)) {
|
|
116651
|
-
consola.
|
|
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
|
-
`${
|
|
116659
|
-
`${
|
|
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 || `${
|
|
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.
|
|
116744
|
+
consola.info("Found claude binary at fallback path:", p);
|
|
116676
116745
|
return p;
|
|
116677
116746
|
}
|
|
116678
116747
|
}
|
|
116679
|
-
consola.
|
|
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
|
-
|
|
116769
|
-
|
|
116770
|
-
|
|
116771
|
-
|
|
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: ${
|
|
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.
|
|
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
|
|
117158
|
-
|
|
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
|
-
•
|
|
117377
|
-
•
|
|
117378
|
-
•
|
|
117379
|
-
•
|
|
117380
|
-
•
|
|
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
|
}
|