chat-heimerdinger 0.1.16 → 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 +131 -36
- package/dist/daemon-entry.js +125 -34
- 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({
|
|
@@ -119895,7 +119915,14 @@ class SlackAdapter {
|
|
|
119895
119915
|
async start() {
|
|
119896
119916
|
consola.info("Slack: Starting Socket Mode...");
|
|
119897
119917
|
await this.app.start();
|
|
119898
|
-
|
|
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");
|
|
119899
119926
|
}
|
|
119900
119927
|
async stop() {
|
|
119901
119928
|
await this.app.stop();
|
|
@@ -119929,9 +119956,20 @@ class SlackAdapter {
|
|
|
119929
119956
|
setupEventHandlers() {
|
|
119930
119957
|
this.app.event("message", async ({ event, client }) => {
|
|
119931
119958
|
const msg = event;
|
|
119932
|
-
|
|
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}`);
|
|
119933
119970
|
return;
|
|
119934
|
-
|
|
119971
|
+
}
|
|
119972
|
+
consola.info(`[msg event] user=${msg.user} subtype=${msg.subtype} isDM=${msg.channel_type === "im"} text="${(msg.text || "").slice(0, 50)}"`);
|
|
119935
119973
|
const isDM = msg.channel_type === "im";
|
|
119936
119974
|
const hasMention = /<@[A-Z0-9]+>/i.test(msg.text || "");
|
|
119937
119975
|
const isFileShare = msg.subtype === "file_share";
|
|
@@ -119965,18 +120003,38 @@ class SlackAdapter {
|
|
|
119965
120003
|
const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
|
|
119966
120004
|
if (!text)
|
|
119967
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
|
+
}
|
|
119968
120011
|
for (const handler of this.messageHandlers) {
|
|
119969
120012
|
await handler({ text, context });
|
|
119970
120013
|
}
|
|
119971
120014
|
});
|
|
119972
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
|
+
}
|
|
119973
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
|
+
}
|
|
119974
120031
|
const context = {
|
|
119975
120032
|
channelId: event.channel,
|
|
119976
120033
|
userId: event.user || "",
|
|
119977
120034
|
threadTs: event.thread_ts || event.ts,
|
|
119978
120035
|
messageTs: event.ts
|
|
119979
120036
|
};
|
|
120037
|
+
this.processedMessages.add(event.ts);
|
|
119980
120038
|
for (const handler of this.messageHandlers) {
|
|
119981
120039
|
await handler({ text, context });
|
|
119982
120040
|
}
|
|
@@ -120415,32 +120473,56 @@ class MessageProcessor {
|
|
|
120415
120473
|
}
|
|
120416
120474
|
return;
|
|
120417
120475
|
}
|
|
120476
|
+
getChannelIds() {
|
|
120477
|
+
return [...this.userStates.keys()];
|
|
120478
|
+
}
|
|
120418
120479
|
async handleMessage(message, adapter) {
|
|
120419
120480
|
const { text, context } = message;
|
|
120420
120481
|
const command = text.trim().toLowerCase();
|
|
120421
|
-
consola.
|
|
120482
|
+
consola.info(`[handleMessage] text="${text}" command="${command}" channel=${context.channelId}`);
|
|
120422
120483
|
if (command === "help" || command === "/help") {
|
|
120484
|
+
consola.info("[handleMessage] -> help");
|
|
120423
120485
|
await this.sendHelp(adapter, context);
|
|
120424
120486
|
return;
|
|
120425
120487
|
}
|
|
120426
120488
|
if (command === "/projects" || command === "projects") {
|
|
120489
|
+
consola.info("[handleMessage] -> projects");
|
|
120427
120490
|
await this.sendProjectList(adapter, context);
|
|
120428
120491
|
return;
|
|
120429
120492
|
}
|
|
120430
120493
|
if (command === "/status" || command === "status") {
|
|
120494
|
+
consola.info("[handleMessage] -> status");
|
|
120431
120495
|
await this.sendStatus(adapter, context);
|
|
120432
120496
|
return;
|
|
120433
120497
|
}
|
|
120434
|
-
if (command
|
|
120435
|
-
|
|
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}`);
|
|
120436
120506
|
await this.selectProject(adapter, context, projectName);
|
|
120437
120507
|
return;
|
|
120438
120508
|
}
|
|
120439
|
-
if (command.startsWith("/session ")) {
|
|
120440
|
-
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}`);
|
|
120441
120512
|
await this.selectSession(adapter, context, sessionId);
|
|
120442
120513
|
return;
|
|
120443
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");
|
|
120444
120526
|
await this.handlePrompt(adapter, context, text);
|
|
120445
120527
|
}
|
|
120446
120528
|
async handleAudioMessage(message, adapter) {
|
|
@@ -120650,11 +120732,14 @@ _Cost: $${cost.toFixed(4)}_`;
|
|
|
120650
120732
|
const help = `*Heimerdinger - Claude Code Bridge*
|
|
120651
120733
|
|
|
120652
120734
|
Available commands:
|
|
120653
|
-
•
|
|
120654
|
-
•
|
|
120655
|
-
•
|
|
120656
|
-
•
|
|
120657
|
-
•
|
|
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
|
|
120658
120743
|
|
|
120659
120744
|
Or just send a message to start coding with Claude!`;
|
|
120660
120745
|
await adapter.sendMessage(context.channelId, help);
|
|
@@ -121419,6 +121504,12 @@ class HeimerdingerServer {
|
|
|
121419
121504
|
});
|
|
121420
121505
|
await adapter.start();
|
|
121421
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
|
+
}
|
|
121422
121513
|
} catch (error) {
|
|
121423
121514
|
consola.error("Failed to initialize Slack adapter:", error);
|
|
121424
121515
|
}
|
|
@@ -121525,13 +121616,17 @@ Shutting down...`);
|
|
|
121525
121616
|
if (!existsSync8(daemonScript)) {
|
|
121526
121617
|
throw new Error(`Daemon script not found at: ${daemonScript}`);
|
|
121527
121618
|
}
|
|
121619
|
+
const home = process.env.HOME || "";
|
|
121528
121620
|
const pathDirs = [
|
|
121621
|
+
process.env.NVM_BIN,
|
|
121529
121622
|
"/usr/local/bin",
|
|
121530
121623
|
"/usr/bin",
|
|
121531
121624
|
"/bin",
|
|
121532
|
-
`${
|
|
121625
|
+
`${home}/.local/bin`,
|
|
121626
|
+
`${home}/.local/share/pnpm`,
|
|
121627
|
+
`${home}/.bun/bin`,
|
|
121533
121628
|
...process.env.PATH?.split(":") || []
|
|
121534
|
-
];
|
|
121629
|
+
].filter(Boolean);
|
|
121535
121630
|
const fullPath = [...new Set(pathDirs)].join(":");
|
|
121536
121631
|
const proc = spawn4("sh", ["-c", `node "${daemonScript}" >> "${LOG_FILE}" 2>&1`], {
|
|
121537
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({
|
|
@@ -116328,7 +116330,14 @@ class SlackAdapter {
|
|
|
116328
116330
|
async start() {
|
|
116329
116331
|
consola.info("Slack: Starting Socket Mode...");
|
|
116330
116332
|
await this.app.start();
|
|
116331
|
-
|
|
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");
|
|
116332
116341
|
}
|
|
116333
116342
|
async stop() {
|
|
116334
116343
|
await this.app.stop();
|
|
@@ -116362,9 +116371,20 @@ class SlackAdapter {
|
|
|
116362
116371
|
setupEventHandlers() {
|
|
116363
116372
|
this.app.event("message", async ({ event, client }) => {
|
|
116364
116373
|
const msg = event;
|
|
116365
|
-
|
|
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}`);
|
|
116366
116381
|
return;
|
|
116367
|
-
|
|
116382
|
+
}
|
|
116383
|
+
if (msg.edited) {
|
|
116384
|
+
consola.info(`[msg filter] skip: edited msg from ${msg.user}`);
|
|
116385
|
+
return;
|
|
116386
|
+
}
|
|
116387
|
+
consola.info(`[msg event] user=${msg.user} subtype=${msg.subtype} isDM=${msg.channel_type === "im"} text="${(msg.text || "").slice(0, 50)}"`);
|
|
116368
116388
|
const isDM = msg.channel_type === "im";
|
|
116369
116389
|
const hasMention = /<@[A-Z0-9]+>/i.test(msg.text || "");
|
|
116370
116390
|
const isFileShare = msg.subtype === "file_share";
|
|
@@ -116398,18 +116418,38 @@ class SlackAdapter {
|
|
|
116398
116418
|
const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
|
|
116399
116419
|
if (!text)
|
|
116400
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
|
+
}
|
|
116401
116426
|
for (const handler of this.messageHandlers) {
|
|
116402
116427
|
await handler({ text, context });
|
|
116403
116428
|
}
|
|
116404
116429
|
});
|
|
116405
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
|
+
}
|
|
116406
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
|
+
}
|
|
116407
116446
|
const context = {
|
|
116408
116447
|
channelId: event.channel,
|
|
116409
116448
|
userId: event.user || "",
|
|
116410
116449
|
threadTs: event.thread_ts || event.ts,
|
|
116411
116450
|
messageTs: event.ts
|
|
116412
116451
|
};
|
|
116452
|
+
this.processedMessages.add(event.ts);
|
|
116413
116453
|
for (const handler of this.messageHandlers) {
|
|
116414
116454
|
await handler({ text, context });
|
|
116415
116455
|
}
|
|
@@ -116643,37 +116683,51 @@ import { basename, join as join2 } from "node:path";
|
|
|
116643
116683
|
class ClaudeCodeService {
|
|
116644
116684
|
claudeConfigPath;
|
|
116645
116685
|
projectsDir;
|
|
116646
|
-
claudeBinaryPath;
|
|
116686
|
+
claudeBinaryPath = null;
|
|
116647
116687
|
constructor(claudeConfigPath = CLAUDE_CONFIG_FILE) {
|
|
116648
116688
|
this.claudeConfigPath = claudeConfigPath;
|
|
116649
116689
|
this.projectsDir = CLAUDE_PROJECTS_DIR;
|
|
116690
|
+
}
|
|
116691
|
+
getClaudeBinary() {
|
|
116692
|
+
if (this.claudeBinaryPath)
|
|
116693
|
+
return this.claudeBinaryPath;
|
|
116650
116694
|
this.claudeBinaryPath = this.findClaudeBinary();
|
|
116695
|
+
return this.claudeBinaryPath;
|
|
116651
116696
|
}
|
|
116652
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(":");
|
|
116653
116710
|
try {
|
|
116654
|
-
const shellPaths = [
|
|
116655
|
-
"/usr/local/bin",
|
|
116656
|
-
"/usr/bin",
|
|
116657
|
-
"/bin",
|
|
116658
|
-
`${process.env.HOME}/.local/bin`,
|
|
116659
|
-
...process.env.PATH?.split(":") || []
|
|
116660
|
-
];
|
|
116661
116711
|
const path = execSync("which claude", {
|
|
116662
116712
|
encoding: "utf-8",
|
|
116663
|
-
env: { ...process.env, PATH:
|
|
116713
|
+
env: { ...process.env, PATH: dedupedPath }
|
|
116664
116714
|
}).trim();
|
|
116665
116715
|
if (path && existsSync2(path)) {
|
|
116666
|
-
consola.
|
|
116716
|
+
consola.info("Found claude binary at:", path);
|
|
116667
116717
|
return path;
|
|
116668
116718
|
}
|
|
116669
|
-
} catch {
|
|
116719
|
+
} catch {
|
|
116720
|
+
consola.warn("which claude failed, trying fallback paths...");
|
|
116721
|
+
}
|
|
116670
116722
|
const commonPaths = [
|
|
116671
116723
|
"/usr/local/bin/claude",
|
|
116672
116724
|
"/usr/bin/claude",
|
|
116673
|
-
`${
|
|
116674
|
-
`${
|
|
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`
|
|
116675
116729
|
];
|
|
116676
|
-
const nvmDir = process.env.NVM_DIR || `${
|
|
116730
|
+
const nvmDir = process.env.NVM_DIR || `${home}/.nvm`;
|
|
116677
116731
|
if (existsSync2(nvmDir)) {
|
|
116678
116732
|
try {
|
|
116679
116733
|
const nvmVersionsDir = join2(nvmDir, "versions", "node");
|
|
@@ -116687,11 +116741,11 @@ class ClaudeCodeService {
|
|
|
116687
116741
|
}
|
|
116688
116742
|
for (const p of commonPaths) {
|
|
116689
116743
|
if (existsSync2(p)) {
|
|
116690
|
-
consola.
|
|
116744
|
+
consola.info("Found claude binary at fallback path:", p);
|
|
116691
116745
|
return p;
|
|
116692
116746
|
}
|
|
116693
116747
|
}
|
|
116694
|
-
consola.
|
|
116748
|
+
consola.error("Could not find claude binary! Searched PATH:", dedupedPath, "and common paths:", commonPaths.join(", "));
|
|
116695
116749
|
return "claude";
|
|
116696
116750
|
}
|
|
116697
116751
|
async isAvailable() {
|
|
@@ -116780,10 +116834,14 @@ class ClaudeCodeService {
|
|
|
116780
116834
|
consola.info("Starting new session");
|
|
116781
116835
|
}
|
|
116782
116836
|
args.push(prompt2);
|
|
116783
|
-
|
|
116784
|
-
|
|
116785
|
-
|
|
116786
|
-
|
|
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, {
|
|
116787
116845
|
cwd: projectDir,
|
|
116788
116846
|
stdio: ["ignore", "pipe", "pipe"],
|
|
116789
116847
|
env: { ...process.env },
|
|
@@ -116846,7 +116904,7 @@ class ClaudeCodeService {
|
|
|
116846
116904
|
}
|
|
116847
116905
|
});
|
|
116848
116906
|
proc.on("error", (err) => {
|
|
116849
|
-
const errorMsg = `Claude process error: ${err.message}, binary path was: ${
|
|
116907
|
+
const errorMsg = `Claude process error: ${err.message}, binary path was: ${binaryPath}`;
|
|
116850
116908
|
consola.error(errorMsg);
|
|
116851
116909
|
console.error(errorMsg, err);
|
|
116852
116910
|
reject(err);
|
|
@@ -117153,32 +117211,56 @@ class MessageProcessor {
|
|
|
117153
117211
|
}
|
|
117154
117212
|
return;
|
|
117155
117213
|
}
|
|
117214
|
+
getChannelIds() {
|
|
117215
|
+
return [...this.userStates.keys()];
|
|
117216
|
+
}
|
|
117156
117217
|
async handleMessage(message, adapter) {
|
|
117157
117218
|
const { text, context } = message;
|
|
117158
117219
|
const command = text.trim().toLowerCase();
|
|
117159
|
-
consola.
|
|
117220
|
+
consola.info(`[handleMessage] text="${text}" command="${command}" channel=${context.channelId}`);
|
|
117160
117221
|
if (command === "help" || command === "/help") {
|
|
117222
|
+
consola.info("[handleMessage] -> help");
|
|
117161
117223
|
await this.sendHelp(adapter, context);
|
|
117162
117224
|
return;
|
|
117163
117225
|
}
|
|
117164
117226
|
if (command === "/projects" || command === "projects") {
|
|
117227
|
+
consola.info("[handleMessage] -> projects");
|
|
117165
117228
|
await this.sendProjectList(adapter, context);
|
|
117166
117229
|
return;
|
|
117167
117230
|
}
|
|
117168
117231
|
if (command === "/status" || command === "status") {
|
|
117232
|
+
consola.info("[handleMessage] -> status");
|
|
117169
117233
|
await this.sendStatus(adapter, context);
|
|
117170
117234
|
return;
|
|
117171
117235
|
}
|
|
117172
|
-
if (command
|
|
117173
|
-
|
|
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}`);
|
|
117174
117244
|
await this.selectProject(adapter, context, projectName);
|
|
117175
117245
|
return;
|
|
117176
117246
|
}
|
|
117177
|
-
if (command.startsWith("/session ")) {
|
|
117178
|
-
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}`);
|
|
117179
117250
|
await this.selectSession(adapter, context, sessionId);
|
|
117180
117251
|
return;
|
|
117181
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");
|
|
117182
117264
|
await this.handlePrompt(adapter, context, text);
|
|
117183
117265
|
}
|
|
117184
117266
|
async handleAudioMessage(message, adapter) {
|
|
@@ -117388,11 +117470,14 @@ _Cost: $${cost.toFixed(4)}_`;
|
|
|
117388
117470
|
const help = `*Heimerdinger - Claude Code Bridge*
|
|
117389
117471
|
|
|
117390
117472
|
Available commands:
|
|
117391
|
-
•
|
|
117392
|
-
•
|
|
117393
|
-
•
|
|
117394
|
-
•
|
|
117395
|
-
•
|
|
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
|
|
117396
117481
|
|
|
117397
117482
|
Or just send a message to start coding with Claude!`;
|
|
117398
117483
|
await adapter.sendMessage(context.channelId, help);
|
|
@@ -118157,6 +118242,12 @@ class HeimerdingerServer {
|
|
|
118157
118242
|
});
|
|
118158
118243
|
await adapter.start();
|
|
118159
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
|
+
}
|
|
118160
118251
|
} catch (error) {
|
|
118161
118252
|
consola.error("Failed to initialize Slack adapter:", error);
|
|
118162
118253
|
}
|