alvin-bot 4.4.1
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/.env.example +43 -0
- package/BACKLOG.md +223 -0
- package/CHANGELOG.md +63 -0
- package/CLAUDE.example.md +152 -0
- package/CODE_OF_CONDUCT.md +52 -0
- package/CONTRIBUTING.md +72 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/SECURITY.md +38 -0
- package/SOUL.example.md +60 -0
- package/TOOLS.example.md +42 -0
- package/alvin-bot.config.example.json +24 -0
- package/bin/cli.js +1088 -0
- package/dist/.metadata_never_index +0 -0
- package/dist/claude.js +102 -0
- package/dist/config.js +65 -0
- package/dist/engine.js +90 -0
- package/dist/find-claude-binary.js +98 -0
- package/dist/handlers/commands.js +1489 -0
- package/dist/handlers/document.js +187 -0
- package/dist/handlers/message.js +200 -0
- package/dist/handlers/photo.js +154 -0
- package/dist/handlers/platform-message.js +275 -0
- package/dist/handlers/video.js +237 -0
- package/dist/handlers/voice.js +148 -0
- package/dist/i18n.js +299 -0
- package/dist/index.js +442 -0
- package/dist/init-data-dir.js +81 -0
- package/dist/middleware/auth.js +215 -0
- package/dist/migrate.js +139 -0
- package/dist/paths.js +87 -0
- package/dist/platforms/discord.js +161 -0
- package/dist/platforms/index.js +130 -0
- package/dist/platforms/signal.js +205 -0
- package/dist/platforms/slack.js +318 -0
- package/dist/platforms/telegram.js +111 -0
- package/dist/platforms/types.js +8 -0
- package/dist/platforms/whatsapp.js +648 -0
- package/dist/providers/claude-sdk-provider.js +173 -0
- package/dist/providers/codex-cli-provider.js +121 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/openai-compatible.js +388 -0
- package/dist/providers/registry.js +209 -0
- package/dist/providers/tool-executor.js +450 -0
- package/dist/providers/types.js +205 -0
- package/dist/services/access.js +144 -0
- package/dist/services/asset-index.js +230 -0
- package/dist/services/browser-manager.js +161 -0
- package/dist/services/browser.js +121 -0
- package/dist/services/compaction.js +129 -0
- package/dist/services/cron.js +462 -0
- package/dist/services/custom-tools.js +317 -0
- package/dist/services/delivery-queue.js +154 -0
- package/dist/services/elevenlabs.js +58 -0
- package/dist/services/embeddings.js +386 -0
- package/dist/services/exec-guard.js +46 -0
- package/dist/services/fallback-order.js +151 -0
- package/dist/services/heartbeat.js +192 -0
- package/dist/services/hooks.js +44 -0
- package/dist/services/imagegen.js +72 -0
- package/dist/services/language-detect.js +144 -0
- package/dist/services/markdown.js +63 -0
- package/dist/services/mcp.js +252 -0
- package/dist/services/memory.js +133 -0
- package/dist/services/personality.js +227 -0
- package/dist/services/plugins.js +171 -0
- package/dist/services/reminders.js +97 -0
- package/dist/services/restart.js +48 -0
- package/dist/services/security-audit.js +66 -0
- package/dist/services/self-search.js +129 -0
- package/dist/services/session.js +93 -0
- package/dist/services/skills.js +287 -0
- package/dist/services/standing-orders.js +29 -0
- package/dist/services/subagents.js +142 -0
- package/dist/services/sudo.js +243 -0
- package/dist/services/telegram.js +113 -0
- package/dist/services/tool-discovery.js +214 -0
- package/dist/services/usage-tracker.js +137 -0
- package/dist/services/users.js +199 -0
- package/dist/services/voice.js +95 -0
- package/dist/tui/index.js +507 -0
- package/dist/web/canvas.js +30 -0
- package/dist/web/doctor-api.js +606 -0
- package/dist/web/openai-compat.js +252 -0
- package/dist/web/server.js +1351 -0
- package/dist/web/setup-api.js +1078 -0
- package/docs/mcp.example.json +16 -0
- package/docs/screenshots/00-Login.png +0 -0
- package/docs/screenshots/01-Chat-Dark-Conversation.png +0 -0
- package/docs/screenshots/02-Chat.png +0 -0
- package/docs/screenshots/03-Dashboard-Overview.png +0 -0
- package/docs/screenshots/04-AI-Models-and-Providers.png +0 -0
- package/docs/screenshots/05-Personality-Editor.png +0 -0
- package/docs/screenshots/06-Memory-Manager.png +0 -0
- package/docs/screenshots/07-Active-Sessions.png +0 -0
- package/docs/screenshots/08-File-Browser.png +0 -0
- package/docs/screenshots/09-Scheduled-Jobs.png +0 -0
- package/docs/screenshots/10-Custom-Tools.png +0 -0
- package/docs/screenshots/11-Plugins-and-MCP.png +0 -0
- package/docs/screenshots/12-Messaging-Platforms.png +0 -0
- package/docs/screenshots/12.1-Messaging-Platforms-WhatsApp-Groups-List.png +0 -0
- package/docs/screenshots/12.2-Messaging-Platforms-WA-Group-Details.png +0 -0
- package/docs/screenshots/13-User-Management.png +0 -0
- package/docs/screenshots/14-Web-Terminal.png +0 -0
- package/docs/screenshots/15-Maintenance-and-Health.png +0 -0
- package/docs/screenshots/16-Settings-and-Env.png +0 -0
- package/docs/screenshots/TG-commands.png +0 -0
- package/docs/screenshots/TG.png +0 -0
- package/docs/screenshots/_Mac-Installer.png +0 -0
- package/docs/tools.example.json +33 -0
- package/install.sh +165 -0
- package/package.json +190 -0
- package/plugins/calendar/index.js +270 -0
- package/plugins/email/index.js +231 -0
- package/plugins/finance/index.js +254 -0
- package/plugins/notes/index.js +227 -0
- package/plugins/smarthome/index.js +230 -0
- package/plugins/weather/index.js +122 -0
- package/skills/apple-notes/SKILL.md +31 -0
- package/skills/browse/SKILL.md +136 -0
- package/skills/code-project/SKILL.md +43 -0
- package/skills/data-analysis/SKILL.md +39 -0
- package/skills/document-creation/SKILL.md +48 -0
- package/skills/email-summary/SKILL.md +46 -0
- package/skills/github/SKILL.md +42 -0
- package/skills/summarize/SKILL.md +28 -0
- package/skills/system-admin/SKILL.md +39 -0
- package/skills/weather/SKILL.md +34 -0
- package/skills/web-research/SKILL.md +35 -0
- package/web/public/canvas.html +52 -0
- package/web/public/css/style.css +555 -0
- package/web/public/index.html +189 -0
- package/web/public/js/app.js +3102 -0
- package/web/public/js/i18n.js +1048 -0
- package/web/public/js/icons.js +104 -0
- package/web/public/login.html +48 -0
|
File without changes
|
package/dist/claude.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { findClaudeBinary } from "./find-claude-binary.js";
|
|
6
|
+
// Bot project root (one level up from src/)
|
|
7
|
+
const BOT_PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
// Load bot's CLAUDE.md at startup — personality, rules, memory instructions
|
|
9
|
+
let botClaudeMd = "";
|
|
10
|
+
try {
|
|
11
|
+
botClaudeMd = readFileSync(resolve(BOT_PROJECT_ROOT, "CLAUDE.md"), "utf-8");
|
|
12
|
+
// Replace relative docs/ paths with absolute paths so memory works from any CWD
|
|
13
|
+
botClaudeMd = botClaudeMd.replaceAll("docs/", `${BOT_PROJECT_ROOT}/docs/`);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// CLAUDE.md not found — continue without bot-specific instructions
|
|
17
|
+
}
|
|
18
|
+
// Checkpoint reminder thresholds
|
|
19
|
+
const CHECKPOINT_TOOL_THRESHOLD = 15; // After N tool uses → inject checkpoint reminder
|
|
20
|
+
const CHECKPOINT_MSG_THRESHOLD = 10; // After N messages → inject checkpoint reminder
|
|
21
|
+
export async function runClaudeAgent(opts) {
|
|
22
|
+
// Remove env vars that prevent nested Claude Code sessions
|
|
23
|
+
const cleanEnv = { ...process.env };
|
|
24
|
+
delete cleanEnv.CLAUDECODE;
|
|
25
|
+
delete cleanEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
26
|
+
// Build prompt — inject checkpoint reminder if thresholds exceeded
|
|
27
|
+
let prompt = opts.prompt;
|
|
28
|
+
const needsCheckpoint = opts.toolUseCount >= CHECKPOINT_TOOL_THRESHOLD ||
|
|
29
|
+
opts.messageCount >= CHECKPOINT_MSG_THRESHOLD;
|
|
30
|
+
if (needsCheckpoint) {
|
|
31
|
+
prompt = `[CHECKPOINT] Du hast bereits ${opts.toolUseCount} Tool-Aufrufe und ${opts.messageCount} Nachrichten in dieser Session. Schreibe jetzt einen Checkpoint in deine Memory-Datei (docs/memory/YYYY-MM-DD.md) bevor du diese Anfrage bearbeitest — fasse den bisherigen Kontext kurz zusammen.\n\n${prompt}`;
|
|
32
|
+
}
|
|
33
|
+
// Find the claude native binary path
|
|
34
|
+
const claudePath = findClaudeBinary();
|
|
35
|
+
const q = query({
|
|
36
|
+
prompt,
|
|
37
|
+
options: {
|
|
38
|
+
cwd: opts.workingDir,
|
|
39
|
+
abortController: opts.abortController,
|
|
40
|
+
resume: opts.sessionId ?? undefined,
|
|
41
|
+
pathToClaudeCodeExecutable: claudePath,
|
|
42
|
+
permissionMode: "bypassPermissions",
|
|
43
|
+
allowDangerouslySkipPermissions: true,
|
|
44
|
+
env: cleanEnv,
|
|
45
|
+
settingSources: ["user", "project"],
|
|
46
|
+
allowedTools: [
|
|
47
|
+
"Read", "Write", "Edit", "Bash", "Glob", "Grep",
|
|
48
|
+
"WebSearch", "WebFetch", "Task",
|
|
49
|
+
],
|
|
50
|
+
systemPrompt: `You are an autonomous AI agent, controlled via Telegram.
|
|
51
|
+
Keep answers short and concise, but thorough.
|
|
52
|
+
Use Markdown formatting compatible with Telegram (bold, italic, code blocks).
|
|
53
|
+
When you execute commands or edit files, briefly explain what you did.
|
|
54
|
+
Reply in the language the user writes in.
|
|
55
|
+
|
|
56
|
+
${botClaudeMd}`,
|
|
57
|
+
effort: opts.effort,
|
|
58
|
+
maxTurns: 50,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
let accumulatedText = "";
|
|
62
|
+
let capturedSessionId = opts.sessionId || "";
|
|
63
|
+
let localToolUseCount = 0;
|
|
64
|
+
for await (const message of q) {
|
|
65
|
+
// System init message — capture session ID
|
|
66
|
+
if (message.type === "system" && "subtype" in message && message.subtype === "init") {
|
|
67
|
+
const sysMsg = message;
|
|
68
|
+
capturedSessionId = sysMsg.session_id;
|
|
69
|
+
}
|
|
70
|
+
// Assistant message — extract text and tool use
|
|
71
|
+
if (message.type === "assistant") {
|
|
72
|
+
const assistantMsg = message;
|
|
73
|
+
capturedSessionId = assistantMsg.session_id;
|
|
74
|
+
if (assistantMsg.message?.content) {
|
|
75
|
+
for (const block of assistantMsg.message.content) {
|
|
76
|
+
if ("text" in block && block.text) {
|
|
77
|
+
accumulatedText += block.text;
|
|
78
|
+
await opts.onText(accumulatedText);
|
|
79
|
+
}
|
|
80
|
+
if ("name" in block) {
|
|
81
|
+
localToolUseCount++;
|
|
82
|
+
if (opts.onToolUse) {
|
|
83
|
+
await opts.onToolUse(block.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Result message — complete
|
|
90
|
+
if (message.type === "result") {
|
|
91
|
+
const resultMsg = message;
|
|
92
|
+
// Report tool use count back to caller for session tracking
|
|
93
|
+
if (opts.onToolUseCount) {
|
|
94
|
+
opts.onToolUseCount(localToolUseCount);
|
|
95
|
+
}
|
|
96
|
+
opts.onComplete({
|
|
97
|
+
sessionId: resultMsg.session_id || capturedSessionId,
|
|
98
|
+
cost: "total_cost_usd" in resultMsg ? resultMsg.total_cost_usd : 0,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
// Load .env from ~/.alvin-bot/.env (primary) with cwd fallback (dev mode)
|
|
6
|
+
const dataEnv = resolve(process.env.ALVIN_DATA_DIR || resolve(os.homedir(), ".alvin-bot"), ".env");
|
|
7
|
+
const cwdEnv = resolve(process.cwd(), ".env");
|
|
8
|
+
if (existsSync(dataEnv)) {
|
|
9
|
+
dotenv.config({ path: dataEnv });
|
|
10
|
+
}
|
|
11
|
+
else if (existsSync(cwdEnv)) {
|
|
12
|
+
dotenv.config({ path: cwdEnv });
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
dotenv.config(); // default behavior
|
|
16
|
+
}
|
|
17
|
+
export const config = {
|
|
18
|
+
// Telegram
|
|
19
|
+
botToken: process.env.BOT_TOKEN || "",
|
|
20
|
+
allowedUsers: (process.env.ALLOWED_USERS || "")
|
|
21
|
+
.split(",")
|
|
22
|
+
.map(Number)
|
|
23
|
+
.filter(Boolean),
|
|
24
|
+
telegramMaxLength: 4096,
|
|
25
|
+
streamThrottleMs: 1500,
|
|
26
|
+
// Agent
|
|
27
|
+
defaultWorkingDir: process.env.WORKING_DIR || os.homedir(),
|
|
28
|
+
maxBudgetUsd: Number(process.env.MAX_BUDGET_USD) || 5.0,
|
|
29
|
+
// Model provider (primary)
|
|
30
|
+
primaryProvider: process.env.PRIMARY_PROVIDER || "claude-sdk",
|
|
31
|
+
fallbackProviders: (process.env.FALLBACK_PROVIDERS || "")
|
|
32
|
+
.split(",")
|
|
33
|
+
.map(s => s.trim())
|
|
34
|
+
.filter(Boolean),
|
|
35
|
+
// API Keys (for multi-model support)
|
|
36
|
+
apiKeys: {
|
|
37
|
+
anthropic: process.env.ANTHROPIC_API_KEY || "",
|
|
38
|
+
groq: process.env.GROQ_API_KEY || "",
|
|
39
|
+
openai: process.env.OPENAI_API_KEY || "",
|
|
40
|
+
google: process.env.GOOGLE_API_KEY || "",
|
|
41
|
+
nvidia: process.env.NVIDIA_API_KEY || "",
|
|
42
|
+
openrouter: process.env.OPENROUTER_API_KEY || "",
|
|
43
|
+
},
|
|
44
|
+
// Compaction
|
|
45
|
+
compactionThreshold: Number(process.env.COMPACTION_THRESHOLD) || 80000,
|
|
46
|
+
// Sub-Agents
|
|
47
|
+
maxSubAgents: Number(process.env.MAX_SUBAGENTS) || 4,
|
|
48
|
+
subAgentTimeout: Number(process.env.SUBAGENT_TIMEOUT) || 300000, // 5 min
|
|
49
|
+
// TTS Provider
|
|
50
|
+
ttsProvider: (process.env.TTS_PROVIDER || "edge"),
|
|
51
|
+
elevenlabs: {
|
|
52
|
+
apiKey: process.env.ELEVENLABS_API_KEY || "",
|
|
53
|
+
voiceId: process.env.ELEVENLABS_VOICE_ID || "iP95p4xoKVk53GoZ742B",
|
|
54
|
+
modelId: process.env.ELEVENLABS_MODEL_ID || "eleven_v3",
|
|
55
|
+
},
|
|
56
|
+
authMode: (process.env.AUTH_MODE || "allowlist"),
|
|
57
|
+
sessionMode: (process.env.SESSION_MODE || "per-user"),
|
|
58
|
+
webhookEnabled: process.env.WEBHOOK_ENABLED === "true",
|
|
59
|
+
webhookToken: process.env.WEBHOOK_TOKEN || "",
|
|
60
|
+
// Browser
|
|
61
|
+
cdpUrl: process.env.CDP_URL || "",
|
|
62
|
+
browseServerPort: Number(process.env.BROWSE_SERVER_PORT) || 3800,
|
|
63
|
+
// Exec Security
|
|
64
|
+
execSecurity: (process.env.EXEC_SECURITY || "full"),
|
|
65
|
+
};
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine — Central AI query dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* Bridges the gap between Telegram handlers and the provider system.
|
|
5
|
+
* Handlers call engine.query(), engine routes to the right provider.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { config } from "./config.js";
|
|
9
|
+
import { createRegistry } from "./providers/index.js";
|
|
10
|
+
import { CUSTOM_MODELS as CUSTOM_MODELS_FILE } from "./paths.js";
|
|
11
|
+
let registry = null;
|
|
12
|
+
/**
|
|
13
|
+
* Load custom models from docs/custom-models.json
|
|
14
|
+
*/
|
|
15
|
+
function loadCustomProviders() {
|
|
16
|
+
try {
|
|
17
|
+
const models = JSON.parse(fs.readFileSync(CUSTOM_MODELS_FILE, "utf-8"));
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const m of models) {
|
|
20
|
+
result[m.key] = {
|
|
21
|
+
type: "openai-compatible",
|
|
22
|
+
name: m.name,
|
|
23
|
+
model: m.model,
|
|
24
|
+
baseUrl: m.baseUrl,
|
|
25
|
+
apiKey: m.apiKeyEnv ? process.env[m.apiKeyEnv] : undefined,
|
|
26
|
+
supportsVision: m.supportsVision ?? false,
|
|
27
|
+
supportsStreaming: m.supportsStreaming ?? true,
|
|
28
|
+
maxTokens: m.maxTokens,
|
|
29
|
+
temperature: m.temperature,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the provider registry from config.
|
|
40
|
+
* Called once at bot startup.
|
|
41
|
+
*/
|
|
42
|
+
export function initEngine() {
|
|
43
|
+
const customProviders = loadCustomProviders();
|
|
44
|
+
registry = createRegistry({
|
|
45
|
+
primary: config.primaryProvider,
|
|
46
|
+
fallbacks: config.fallbackProviders.length > 0 ? config.fallbackProviders : undefined,
|
|
47
|
+
apiKeys: {
|
|
48
|
+
anthropic: config.apiKeys.anthropic || undefined,
|
|
49
|
+
groq: config.apiKeys.groq || undefined,
|
|
50
|
+
openai: config.apiKeys.openai || undefined,
|
|
51
|
+
google: config.apiKeys.google || undefined,
|
|
52
|
+
nvidia: config.apiKeys.nvidia || undefined,
|
|
53
|
+
openrouter: config.apiKeys.openrouter || undefined,
|
|
54
|
+
},
|
|
55
|
+
customProviders: Object.keys(customProviders).length > 0 ? customProviders : undefined,
|
|
56
|
+
});
|
|
57
|
+
if (Object.keys(customProviders).length > 0) {
|
|
58
|
+
console.log(`Custom models loaded: ${Object.keys(customProviders).join(", ")}`);
|
|
59
|
+
}
|
|
60
|
+
return registry;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the provider registry. Must call initEngine() first.
|
|
64
|
+
*/
|
|
65
|
+
export function getRegistry() {
|
|
66
|
+
if (!registry) {
|
|
67
|
+
throw new Error("Engine not initialized. Call initEngine() first.");
|
|
68
|
+
}
|
|
69
|
+
return registry;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Run a query through the active provider (with fallback).
|
|
73
|
+
* This is the main entry point for handlers.
|
|
74
|
+
*/
|
|
75
|
+
export async function* engineQuery(options) {
|
|
76
|
+
const reg = getRegistry();
|
|
77
|
+
yield* reg.queryWithFallback(options);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get info about the current model setup for /status.
|
|
81
|
+
*/
|
|
82
|
+
export async function getEngineStatus() {
|
|
83
|
+
const reg = getRegistry();
|
|
84
|
+
const providers = await reg.listAll();
|
|
85
|
+
const lines = providers.map(p => {
|
|
86
|
+
const marker = p.active ? "→" : " ";
|
|
87
|
+
return `${marker} ${p.key}: ${p.name} (${p.model}) ${p.status}`;
|
|
88
|
+
});
|
|
89
|
+
return `Model: ${reg.getActiveKey()}\n\n${lines.join("\n")}`;
|
|
90
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the Claude Code native binary path.
|
|
3
|
+
*
|
|
4
|
+
* The Agent SDK requires the native Mach-O/ELF binary, NOT the npm/node wrapper.
|
|
5
|
+
* Native installer: ~/.local/bin/claude → ~/.local/share/claude/versions/<ver>
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, openSync, readSync, closeSync, readdirSync, realpathSync, statSync } from "fs";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
/** Check if a file is a node/shell script (NOT a native binary) */
|
|
12
|
+
function isScript(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const buf = Buffer.alloc(64);
|
|
15
|
+
const fd = openSync(filePath, "r");
|
|
16
|
+
readSync(fd, buf, 0, 64, 0);
|
|
17
|
+
closeSync(fd);
|
|
18
|
+
const hdr = buf.toString("utf-8", 0, 64);
|
|
19
|
+
return hdr.startsWith("#!") && (hdr.includes("node") || hdr.includes("sh"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Resolve a candidate path to the native binary, or undefined */
|
|
26
|
+
function tryCandidate(p, label) {
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(p))
|
|
29
|
+
return undefined;
|
|
30
|
+
const resolved = realpathSync(p);
|
|
31
|
+
if (!statSync(resolved).isFile())
|
|
32
|
+
return undefined;
|
|
33
|
+
if (isScript(resolved)) {
|
|
34
|
+
console.error(`[claude] ${label}: ${resolved} is a script wrapper, skipping`);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Find the native Claude Code binary. Returns the path or undefined. */
|
|
44
|
+
export function findClaudeBinary() {
|
|
45
|
+
try {
|
|
46
|
+
const home = homedir();
|
|
47
|
+
// Strategy 1: ~/.local/bin/claude (native installer symlink)
|
|
48
|
+
let result = tryCandidate(join(home, ".local", "bin", "claude"), "Strategy 1 (~/.local/bin/claude)");
|
|
49
|
+
if (result) {
|
|
50
|
+
console.error(`[claude] Native binary: ${result}`);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
// Strategy 2: Scan ~/.local/share/claude/versions/ (newest first)
|
|
54
|
+
const versionsDir = join(home, ".local", "share", "claude", "versions");
|
|
55
|
+
if (existsSync(versionsDir)) {
|
|
56
|
+
try {
|
|
57
|
+
const entries = readdirSync(versionsDir)
|
|
58
|
+
.filter((f) => !f.startsWith("."))
|
|
59
|
+
.sort()
|
|
60
|
+
.reverse();
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const entryPath = join(versionsDir, entry);
|
|
63
|
+
// Entry might be the binary itself OR a directory containing it
|
|
64
|
+
result = tryCandidate(entryPath, `Strategy 2 (versions/${entry})`);
|
|
65
|
+
if (!result) {
|
|
66
|
+
result = tryCandidate(join(entryPath, "claude"), `Strategy 2 (versions/${entry}/claude)`);
|
|
67
|
+
}
|
|
68
|
+
if (result) {
|
|
69
|
+
console.error(`[claude] Native binary: ${result}`);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
console.error(`[claude] Strategy 2: can't read versions dir: ${e}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Strategy 3: which claude → resolve → verify not a script
|
|
79
|
+
try {
|
|
80
|
+
const p = execSync("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
81
|
+
if (p) {
|
|
82
|
+
result = tryCandidate(p, `Strategy 3 (which → ${p})`);
|
|
83
|
+
if (result) {
|
|
84
|
+
console.error(`[claude] Native binary: ${result}`);
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { /* not in PATH */ }
|
|
90
|
+
console.error("[claude] WARNING: Native binary not found — SDK will use default (may fail)");
|
|
91
|
+
console.error(`[claude] Checked: ~/.local/bin/claude, ~/.local/share/claude/versions/, which claude`);
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error(`[claude] Binary search error: ${err instanceof Error ? err.message : err}`);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|