cc-claw 0.4.4 → 0.4.6
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/README.md +6 -3
- package/dist/cli.js +675 -204
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ var VERSION;
|
|
|
48
48
|
var init_version = __esm({
|
|
49
49
|
"src/version.ts"() {
|
|
50
50
|
"use strict";
|
|
51
|
-
VERSION = true ? "0.4.
|
|
51
|
+
VERSION = true ? "0.4.6" : (() => {
|
|
52
52
|
try {
|
|
53
53
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
54
54
|
} catch {
|
|
@@ -358,10 +358,10 @@ function claimTask(db3, taskId, agentId) {
|
|
|
358
358
|
).get(...blockedBy);
|
|
359
359
|
if (incomplete.count > 0) return false;
|
|
360
360
|
}
|
|
361
|
-
db3.prepare(
|
|
361
|
+
const result = db3.prepare(
|
|
362
362
|
"UPDATE agent_tasks SET assignee = ?, status = 'in_progress' WHERE id = ? AND status = 'pending'"
|
|
363
363
|
).run(agentId, taskId);
|
|
364
|
-
return
|
|
364
|
+
return result.changes > 0;
|
|
365
365
|
}
|
|
366
366
|
function sendInboxMessage(db3, opts) {
|
|
367
367
|
const result = db3.prepare(`
|
|
@@ -968,6 +968,7 @@ __export(store_exports3, {
|
|
|
968
968
|
getRecentBookmarks: () => getRecentBookmarks,
|
|
969
969
|
getRecentMemories: () => getRecentMemories,
|
|
970
970
|
getRecentMessageLog: () => getRecentMessageLog,
|
|
971
|
+
getResponseStyle: () => getResponseStyle,
|
|
971
972
|
getSessionId: () => getSessionId,
|
|
972
973
|
getSessionStartedAt: () => getSessionStartedAt,
|
|
973
974
|
getSessionSummariesWithoutEmbeddings: () => getSessionSummariesWithoutEmbeddings,
|
|
@@ -1009,6 +1010,7 @@ __export(store_exports3, {
|
|
|
1009
1010
|
setHeartbeatConfig: () => setHeartbeatConfig,
|
|
1010
1011
|
setMode: () => setMode,
|
|
1011
1012
|
setModel: () => setModel,
|
|
1013
|
+
setResponseStyle: () => setResponseStyle,
|
|
1012
1014
|
setSessionId: () => setSessionId,
|
|
1013
1015
|
setSessionStartedAt: () => setSessionStartedAt,
|
|
1014
1016
|
setSummarizer: () => setSummarizer,
|
|
@@ -1358,9 +1360,19 @@ function initDatabase() {
|
|
|
1358
1360
|
db.exec(`
|
|
1359
1361
|
CREATE TABLE IF NOT EXISTS chat_voice (
|
|
1360
1362
|
chat_id TEXT PRIMARY KEY,
|
|
1361
|
-
enabled INTEGER NOT NULL DEFAULT 0
|
|
1363
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
1364
|
+
provider TEXT DEFAULT 'elevenlabs',
|
|
1365
|
+
voice_id TEXT
|
|
1362
1366
|
);
|
|
1363
1367
|
`);
|
|
1368
|
+
try {
|
|
1369
|
+
db.exec(`ALTER TABLE chat_voice ADD COLUMN provider TEXT DEFAULT 'elevenlabs'`);
|
|
1370
|
+
} catch {
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
db.exec(`ALTER TABLE chat_voice ADD COLUMN voice_id TEXT`);
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1364
1376
|
db.exec(`
|
|
1365
1377
|
CREATE TABLE IF NOT EXISTS backend_limits (
|
|
1366
1378
|
backend TEXT NOT NULL,
|
|
@@ -1468,6 +1480,12 @@ function initDatabase() {
|
|
|
1468
1480
|
initMcpTables(db);
|
|
1469
1481
|
initActivityTable(db);
|
|
1470
1482
|
initIdentityTables(db);
|
|
1483
|
+
db.exec(`
|
|
1484
|
+
CREATE TABLE IF NOT EXISTS chat_response_style (
|
|
1485
|
+
chat_id TEXT PRIMARY KEY,
|
|
1486
|
+
style TEXT NOT NULL DEFAULT 'normal'
|
|
1487
|
+
);
|
|
1488
|
+
`);
|
|
1471
1489
|
}
|
|
1472
1490
|
function getDb() {
|
|
1473
1491
|
return db;
|
|
@@ -1600,6 +1618,17 @@ function applySalienceDecay() {
|
|
|
1600
1618
|
db.prepare("DELETE FROM session_summaries WHERE salience < 0.1").run();
|
|
1601
1619
|
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('last_salience_decay', ?)").run(now);
|
|
1602
1620
|
}
|
|
1621
|
+
function getResponseStyle(chatId) {
|
|
1622
|
+
const row = db.prepare("SELECT style FROM chat_response_style WHERE chat_id = ?").get(chatId);
|
|
1623
|
+
return row?.style ?? "normal";
|
|
1624
|
+
}
|
|
1625
|
+
function setResponseStyle(chatId, style) {
|
|
1626
|
+
db.prepare(`
|
|
1627
|
+
INSERT INTO chat_response_style (chat_id, style)
|
|
1628
|
+
VALUES (?, ?)
|
|
1629
|
+
ON CONFLICT(chat_id) DO UPDATE SET style = excluded.style
|
|
1630
|
+
`).run(chatId, style);
|
|
1631
|
+
}
|
|
1603
1632
|
function getSessionId(chatId) {
|
|
1604
1633
|
const row = db.prepare(
|
|
1605
1634
|
"SELECT session_id FROM sessions WHERE chat_id = ?"
|
|
@@ -2461,6 +2490,7 @@ var init_store4 = __esm({
|
|
|
2461
2490
|
});
|
|
2462
2491
|
|
|
2463
2492
|
// src/env.ts
|
|
2493
|
+
import { homedir as homedir2 } from "os";
|
|
2464
2494
|
function stripProxyVars(env) {
|
|
2465
2495
|
for (const key of PROXY_KEYS) delete env[key];
|
|
2466
2496
|
}
|
|
@@ -2469,7 +2499,7 @@ function buildBaseEnv(extraOverrides) {
|
|
|
2469
2499
|
for (const [k, v] of Object.entries(process.env)) {
|
|
2470
2500
|
if (v !== void 0) env[k] = v;
|
|
2471
2501
|
}
|
|
2472
|
-
if (!env.HOME) env.HOME =
|
|
2502
|
+
if (!env.HOME) env.HOME = homedir2();
|
|
2473
2503
|
stripProxyVars(env);
|
|
2474
2504
|
if (extraOverrides) Object.assign(env, extraOverrides);
|
|
2475
2505
|
return env;
|
|
@@ -3179,8 +3209,9 @@ async function injectMemoryContext(userMessage) {
|
|
|
3179
3209
|
seen.add(mem.id);
|
|
3180
3210
|
combinedMemories.push(mem);
|
|
3181
3211
|
}
|
|
3212
|
+
if (combinedMemories.length >= FINAL_TOP_K_MEMORIES) break;
|
|
3182
3213
|
}
|
|
3183
|
-
combinedSessions = ftsSessions;
|
|
3214
|
+
combinedSessions = ftsSessions.slice(0, FINAL_TOP_K_SESSIONS);
|
|
3184
3215
|
}
|
|
3185
3216
|
if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
|
|
3186
3217
|
const lines = [];
|
|
@@ -3219,7 +3250,7 @@ var init_inject = __esm({
|
|
|
3219
3250
|
});
|
|
3220
3251
|
|
|
3221
3252
|
// src/bootstrap/defaults.ts
|
|
3222
|
-
var DEFAULT_SOUL, DEFAULT_USER;
|
|
3253
|
+
var DEFAULT_SOUL, DEFAULT_USER, DEFAULT_EXPERTISE;
|
|
3223
3254
|
var init_defaults = __esm({
|
|
3224
3255
|
"src/bootstrap/defaults.ts"() {
|
|
3225
3256
|
"use strict";
|
|
@@ -3256,6 +3287,40 @@ This file is auto-generated. Use /setup-profile to customize, or edit directly.
|
|
|
3256
3287
|
- **Timezone**: UTC
|
|
3257
3288
|
- **Communication style**: concise
|
|
3258
3289
|
- **Primary use**: general assistant
|
|
3290
|
+
`;
|
|
3291
|
+
DEFAULT_EXPERTISE = `# CC-Claw System Expertise
|
|
3292
|
+
|
|
3293
|
+
You are an expert user and operator of the CC-Claw architecture. Because you are reading this file, the user has likely asked you about a system feature, how the bot operates, its capabilities, or its internal architecture.
|
|
3294
|
+
|
|
3295
|
+
## 1. Core Architecture & Philosophy
|
|
3296
|
+
- **Multi-Backend System**: You are decoupled from any specific LLM. The user can seamlessly hot-swap your "brain" between Claude (Anthropic), Gemini (Google), and Codex (OpenAI) at any time. You maintain persistent, unified memory across all of them.
|
|
3297
|
+
- **The Daemon**: CC-Claw runs a background daemon (\`cc-claw start\`, \`cc-claw stop\`) that manages the HTTP API, Webhooks, and the Cron Scheduler.
|
|
3298
|
+
- **Database**: All state is stored locally in SQLite (\`~/.cc-claw/data/cc-claw.db\`), ensuring total privacy and fast local access for memory, jobs, and orchestration.
|
|
3299
|
+
|
|
3300
|
+
## 2. Identity & Context (The "Brain")
|
|
3301
|
+
- **SOUL & USER**: Your personality is defined in \`~/.cc-claw/workspace/SOUL.md\`, and the user's profile is in \`USER.md\`. These are the single source of truth for your behavior.
|
|
3302
|
+
- **On-Demand Context**: The \`context/\` directory holds files like this one. They are injected into your prompt only when triggered by relevant semantic keywords, keeping your context window lean.
|
|
3303
|
+
- **Proactive Memory**: You can autonomously save user facts and preferences to the database by writing \`[UPDATE_USER:key=value]\` in your replies.
|
|
3304
|
+
|
|
3305
|
+
## 3. Advanced Agent Orchestration
|
|
3306
|
+
- **Sub-Agents**: You are not limited to sequential replies. If a task requires heavy research or coding, you can spawn parallel worker agents using your \`spawn_agent\` MCP tool.
|
|
3307
|
+
- **Supervision**: You manage sub-agents by reading their messages via \`read_inbox\` and sharing data on the orchestration whiteboard via \`set_state\` / \`get_state\`.
|
|
3308
|
+
|
|
3309
|
+
## 4. Scheduling & Cron (Autonomous Execution)
|
|
3310
|
+
- **Job Management**: You manage scheduled tasks natively. Jobs can route on cron expressions, "every 4h", or "at 09:00".
|
|
3311
|
+
- **Dry Run & Webhooks**: Jobs aren't just for Telegram messages. Using \`--delivery dry_run\`, you can test jobs internally (output is logged to the database but not spammed to chat). You can also route AI output directly to external APIs using \`--delivery webhook\`.
|
|
3312
|
+
- **Auditing**: Every cron execution and its output is permanently logged in the database's \`message_log\` for verifiable auditing.
|
|
3313
|
+
|
|
3314
|
+
## 5. System Controls & Modes
|
|
3315
|
+
- **Permission Modes**: Command execution is gated. \`safe\` mode requires user approval for mutations, \`yolo\` auto-runs everything without asking, and \`plan\` is strictly read-only for drafting proposals.
|
|
3316
|
+
- **Global Settings**: The user controls your behavior system-wide using commands like \`/response_style\` (concise, normal, detailed) to enforce verbosity constraints, and \`/voice_config\` to select Text-to-Speech voices from premium providers like ElevenLabs, Grok (xAI), or local macOS.
|
|
3317
|
+
|
|
3318
|
+
## 6. Integrations & MCP
|
|
3319
|
+
- **Skills**: You can load specific workflows from the \`~/.cc-claw/workspace/skills/\` directory.
|
|
3320
|
+
- **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
|
|
3321
|
+
- **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
|
|
3322
|
+
|
|
3323
|
+
If the user asks *how* to do something with CC-Claw, use this expertise to suggest the most native, idiomatic approach available in the architecture.
|
|
3259
3324
|
`;
|
|
3260
3325
|
}
|
|
3261
3326
|
});
|
|
@@ -3282,6 +3347,11 @@ function bootstrapWorkspaceFiles() {
|
|
|
3282
3347
|
mkdirSync(CONTEXT_DIR, { recursive: true });
|
|
3283
3348
|
log("[bootstrap] Created context/ directory");
|
|
3284
3349
|
}
|
|
3350
|
+
const expertisePath = join3(CONTEXT_DIR, "cc-claw-expertise.md");
|
|
3351
|
+
if (!existsSync4(expertisePath)) {
|
|
3352
|
+
writeFileSync(expertisePath, DEFAULT_EXPERTISE, "utf-8");
|
|
3353
|
+
log("[bootstrap] Created default context/cc-claw-expertise.md");
|
|
3354
|
+
}
|
|
3285
3355
|
syncNativeCliFiles();
|
|
3286
3356
|
}
|
|
3287
3357
|
function syncNativeCliFiles() {
|
|
@@ -3399,12 +3469,19 @@ function searchContext(userMessage) {
|
|
|
3399
3469
|
}
|
|
3400
3470
|
return null;
|
|
3401
3471
|
}
|
|
3402
|
-
async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode) {
|
|
3472
|
+
async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle) {
|
|
3403
3473
|
const sections = [];
|
|
3404
3474
|
syncNativeCliFiles();
|
|
3405
3475
|
if (permMode && permMode !== "yolo") {
|
|
3406
3476
|
sections.push(buildPermissionNotice(permMode));
|
|
3407
3477
|
}
|
|
3478
|
+
if (responseStyle) {
|
|
3479
|
+
if (responseStyle === "concise") {
|
|
3480
|
+
sections.push("[Response Style]\nYou must be as concise and direct as possible. Avoid unnecessary verbosity, pleasantries, or long explanations.");
|
|
3481
|
+
} else if (responseStyle === "detailed") {
|
|
3482
|
+
sections.push("[Response Style]\nYou should be detailed and thorough in your responses. Explain concepts fully and provide comprehensive answers.");
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3408
3485
|
if (tier === "full") {
|
|
3409
3486
|
const ctx = searchContext(userMessage);
|
|
3410
3487
|
if (ctx) {
|
|
@@ -3925,8 +4002,10 @@ function spawnAgentProcess(runner, opts, callbacks) {
|
|
|
3925
4002
|
const child = spawn2(runner.getExecutablePath(), args, {
|
|
3926
4003
|
env: buildSpawnEnv(runner, opts.isSubAgent),
|
|
3927
4004
|
cwd: opts.cwd,
|
|
3928
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4005
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4006
|
+
detached: true
|
|
3929
4007
|
});
|
|
4008
|
+
child.unref();
|
|
3930
4009
|
let resultText = "";
|
|
3931
4010
|
let usage2;
|
|
3932
4011
|
if (child.stdout) {
|
|
@@ -3949,7 +4028,14 @@ function spawnAgentProcess(runner, opts, callbacks) {
|
|
|
3949
4028
|
if (event.usage) usage2 = event.usage;
|
|
3950
4029
|
callbacks.onResult?.(resultText, usage2);
|
|
3951
4030
|
if (runner.shouldKillOnResult()) {
|
|
3952
|
-
|
|
4031
|
+
try {
|
|
4032
|
+
if (child.pid) process.kill(-child.pid, "SIGTERM");
|
|
4033
|
+
} catch {
|
|
4034
|
+
try {
|
|
4035
|
+
child.kill("SIGTERM");
|
|
4036
|
+
} catch {
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
3953
4039
|
}
|
|
3954
4040
|
}
|
|
3955
4041
|
}
|
|
@@ -4029,7 +4115,7 @@ var init_cost = __esm({
|
|
|
4029
4115
|
// src/mcps/propagate.ts
|
|
4030
4116
|
import { execFile } from "child_process";
|
|
4031
4117
|
import { promisify } from "util";
|
|
4032
|
-
import { homedir as
|
|
4118
|
+
import { homedir as homedir3 } from "os";
|
|
4033
4119
|
async function discoverExistingMcps(runner) {
|
|
4034
4120
|
try {
|
|
4035
4121
|
const listCmd = runner.getMcpListCommand();
|
|
@@ -4038,7 +4124,7 @@ async function discoverExistingMcps(runner) {
|
|
|
4038
4124
|
const result = await execFileAsync(exe, args, {
|
|
4039
4125
|
encoding: "utf-8",
|
|
4040
4126
|
env: runner.getEnv(),
|
|
4041
|
-
cwd:
|
|
4127
|
+
cwd: homedir3(),
|
|
4042
4128
|
timeout: 3e4
|
|
4043
4129
|
});
|
|
4044
4130
|
const stdout = typeof result === "string" ? result : Array.isArray(result) ? result[0] : result?.stdout ?? null;
|
|
@@ -4553,6 +4639,26 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
4553
4639
|
clearTimeout(timeoutTimers.get(agentId));
|
|
4554
4640
|
timeoutTimers.delete(agentId);
|
|
4555
4641
|
activeProcesses.delete(agentId);
|
|
4642
|
+
const crashedAgent = getAgent(db3, agentId);
|
|
4643
|
+
if (crashedAgent) {
|
|
4644
|
+
const mcpsCrashed = crashedAgent.mcpsAdded ? JSON.parse(crashedAgent.mcpsAdded) : [];
|
|
4645
|
+
if (mcpsCrashed.length > 0) {
|
|
4646
|
+
const runner2 = getRunner(crashedAgent.runnerId);
|
|
4647
|
+
if (runner2) {
|
|
4648
|
+
const cleanupFn = () => cleanupMcps(runner2, mcpsCrashed, db3, `agent:${agentId}`);
|
|
4649
|
+
if (runner2.capabilities.mcpInjection === "add-remove") {
|
|
4650
|
+
withRunnerLock(runner2.id, cleanupFn).catch((err) => {
|
|
4651
|
+
warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
|
|
4652
|
+
});
|
|
4653
|
+
} else {
|
|
4654
|
+
cleanupFn().catch((err) => {
|
|
4655
|
+
warn(`[orchestrator] MCP cleanup failed for crashed agent ${agentId.slice(0, 8)}:`, err);
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
deleteMcpConfigFile(`cc-claw-${agentId.slice(0, 8)}`);
|
|
4661
|
+
}
|
|
4556
4662
|
sendInboxMessage(db3, {
|
|
4557
4663
|
orchestrationId: agent.orchestrationId,
|
|
4558
4664
|
toAgentId: "main",
|
|
@@ -4714,9 +4820,23 @@ function cancelAgent(agentId, reason = "user_cancelled") {
|
|
|
4714
4820
|
if (!agent) return false;
|
|
4715
4821
|
const proc = activeProcesses.get(agentId);
|
|
4716
4822
|
if (proc) {
|
|
4717
|
-
|
|
4823
|
+
try {
|
|
4824
|
+
if (proc.pid) process.kill(-proc.pid, "SIGTERM");
|
|
4825
|
+
} catch {
|
|
4826
|
+
try {
|
|
4827
|
+
proc.kill("SIGTERM");
|
|
4828
|
+
} catch {
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4718
4831
|
setTimeout(() => {
|
|
4719
|
-
|
|
4832
|
+
try {
|
|
4833
|
+
if (proc.pid) process.kill(-proc.pid, "SIGKILL");
|
|
4834
|
+
} catch {
|
|
4835
|
+
try {
|
|
4836
|
+
proc.kill("SIGKILL");
|
|
4837
|
+
} catch {
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4720
4840
|
}, 2e3);
|
|
4721
4841
|
}
|
|
4722
4842
|
updateAgentStatus(db3, agentId, "cancelled");
|
|
@@ -4761,7 +4881,14 @@ function cancelAllAgents(chatId, reason = "user_cancelled") {
|
|
|
4761
4881
|
}
|
|
4762
4882
|
function shutdownOrchestrator() {
|
|
4763
4883
|
for (const [agentId, proc] of activeProcesses) {
|
|
4764
|
-
|
|
4884
|
+
try {
|
|
4885
|
+
if (proc.pid) process.kill(-proc.pid, "SIGTERM");
|
|
4886
|
+
} catch {
|
|
4887
|
+
try {
|
|
4888
|
+
proc.kill("SIGTERM");
|
|
4889
|
+
} catch {
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4765
4892
|
clearTimeout(timeoutTimers.get(agentId));
|
|
4766
4893
|
}
|
|
4767
4894
|
activeProcesses.clear();
|
|
@@ -5621,8 +5748,8 @@ data: ${JSON.stringify(data)}
|
|
|
5621
5748
|
if (url.pathname === "/api/heartbeat/set" && req.method === "POST") {
|
|
5622
5749
|
try {
|
|
5623
5750
|
const body = JSON.parse(await readBody(req));
|
|
5624
|
-
const { setHeartbeatConfig:
|
|
5625
|
-
|
|
5751
|
+
const { setHeartbeatConfig: setHeartbeatConfig2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
5752
|
+
setHeartbeatConfig2(body.chatId, body);
|
|
5626
5753
|
return jsonResponse(res, { success: true });
|
|
5627
5754
|
} catch (err) {
|
|
5628
5755
|
return jsonResponse(res, { error: errorMessage(err) }, 400);
|
|
@@ -5671,7 +5798,7 @@ data: ${JSON.stringify(data)}
|
|
|
5671
5798
|
if (url.pathname === "/api/config/set" && req.method === "POST") {
|
|
5672
5799
|
try {
|
|
5673
5800
|
const body = JSON.parse(await readBody(req));
|
|
5674
|
-
const validKeys = ["backend", "model", "thinking", "summarizer", "mode", "verbose", "cwd", "voice"];
|
|
5801
|
+
const validKeys = ["backend", "model", "thinking", "summarizer", "mode", "verbose", "cwd", "voice", "response-style"];
|
|
5675
5802
|
if (!validKeys.includes(body.key)) {
|
|
5676
5803
|
return jsonResponse(res, { error: `Invalid config key. Valid: ${validKeys.join(", ")}` }, 400);
|
|
5677
5804
|
}
|
|
@@ -5689,6 +5816,9 @@ data: ${JSON.stringify(data)}
|
|
|
5689
5816
|
} else if (body.key === "voice") {
|
|
5690
5817
|
const db3 = getDb();
|
|
5691
5818
|
db3.prepare("INSERT OR REPLACE INTO chat_voice (chat_id, enabled) VALUES (?, ?)").run(body.chatId, body.value === "on" || body.value === "1" ? 1 : 0);
|
|
5819
|
+
} else if (body.key === "response-style") {
|
|
5820
|
+
const { setResponseStyle: setResponseStyle2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
5821
|
+
setResponseStyle2(body.chatId, body.value);
|
|
5692
5822
|
} else if (body.key === "backend") {
|
|
5693
5823
|
const { setBackend: setBackend2, clearSession: clearSession2, clearModel: clearModel2, clearThinkingLevel: clearThinkingLevel2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
5694
5824
|
clearSession2(body.chatId);
|
|
@@ -5920,14 +6050,21 @@ var init_server = __esm({
|
|
|
5920
6050
|
var agent_exports = {};
|
|
5921
6051
|
__export(agent_exports, {
|
|
5922
6052
|
askAgent: () => askAgent,
|
|
5923
|
-
isAgentActive: () => isAgentActive,
|
|
5924
6053
|
isChatBusy: () => isChatBusy,
|
|
5925
6054
|
stopAgent: () => stopAgent
|
|
5926
6055
|
});
|
|
5927
6056
|
import { spawn as spawn4 } from "child_process";
|
|
5928
6057
|
import { createInterface as createInterface3 } from "readline";
|
|
5929
|
-
|
|
5930
|
-
|
|
6058
|
+
function killProcessGroup(proc, signal = "SIGTERM") {
|
|
6059
|
+
try {
|
|
6060
|
+
if (proc.pid) process.kill(-proc.pid, signal);
|
|
6061
|
+
} catch {
|
|
6062
|
+
try {
|
|
6063
|
+
proc.kill(signal);
|
|
6064
|
+
} catch {
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
5931
6068
|
function withChatLock(chatId, fn) {
|
|
5932
6069
|
const prev = chatLocks.get(chatId) ?? Promise.resolve();
|
|
5933
6070
|
const isBlocked = activeChats.has(chatId);
|
|
@@ -5948,14 +6085,13 @@ function stopAgent(chatId) {
|
|
|
5948
6085
|
if (!state) return false;
|
|
5949
6086
|
state.cancelled = true;
|
|
5950
6087
|
if (state.process) {
|
|
5951
|
-
state.process
|
|
5952
|
-
state.killTimer = setTimeout(() =>
|
|
6088
|
+
killProcessGroup(state.process, "SIGTERM");
|
|
6089
|
+
state.killTimer = setTimeout(() => {
|
|
6090
|
+
if (state.process) killProcessGroup(state.process, "SIGKILL");
|
|
6091
|
+
}, 2e3);
|
|
5953
6092
|
}
|
|
5954
6093
|
return true;
|
|
5955
6094
|
}
|
|
5956
|
-
function isAgentActive(chatId) {
|
|
5957
|
-
return activeChats.has(chatId);
|
|
5958
|
-
}
|
|
5959
6095
|
function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs) {
|
|
5960
6096
|
const effectiveTimeout = timeoutMs ?? SPAWN_TIMEOUT_MS;
|
|
5961
6097
|
return new Promise((resolve, reject) => {
|
|
@@ -5966,21 +6102,18 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
|
|
|
5966
6102
|
const proc = spawn4(config2.executable, finalArgs, {
|
|
5967
6103
|
env,
|
|
5968
6104
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6105
|
+
detached: true,
|
|
5969
6106
|
...config2.cwd ? { cwd: config2.cwd } : {}
|
|
5970
6107
|
});
|
|
6108
|
+
proc.unref();
|
|
5971
6109
|
cancelState.process = proc;
|
|
5972
6110
|
let timedOut = false;
|
|
5973
6111
|
let sigkillTimer;
|
|
5974
6112
|
const spawnTimeout = setTimeout(() => {
|
|
5975
6113
|
timedOut = true;
|
|
5976
6114
|
warn(`[agent] Spawn timeout after ${effectiveTimeout / 1e3}s for ${adapter.id} \u2014 killing process`);
|
|
5977
|
-
proc
|
|
5978
|
-
sigkillTimer = setTimeout(() =>
|
|
5979
|
-
try {
|
|
5980
|
-
proc.kill("SIGKILL");
|
|
5981
|
-
} catch {
|
|
5982
|
-
}
|
|
5983
|
-
}, 3e3);
|
|
6115
|
+
killProcessGroup(proc, "SIGTERM");
|
|
6116
|
+
sigkillTimer = setTimeout(() => killProcessGroup(proc, "SIGKILL"), 3e3);
|
|
5984
6117
|
}, effectiveTimeout);
|
|
5985
6118
|
let resultText = "";
|
|
5986
6119
|
let accumulatedText = "";
|
|
@@ -6068,7 +6201,7 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
|
|
|
6068
6201
|
rl2.close();
|
|
6069
6202
|
} catch {
|
|
6070
6203
|
}
|
|
6071
|
-
proc
|
|
6204
|
+
killProcessGroup(proc, "SIGTERM");
|
|
6072
6205
|
}
|
|
6073
6206
|
break;
|
|
6074
6207
|
}
|
|
@@ -6116,10 +6249,11 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6116
6249
|
const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
|
|
6117
6250
|
const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(chatId);
|
|
6118
6251
|
const mode = permMode ?? getMode(chatId);
|
|
6252
|
+
const responseStyle = getResponseStyle(chatId);
|
|
6119
6253
|
const thinkingLevel = getThinkingLevel(chatId);
|
|
6120
6254
|
const resolvedCwd = cwd ?? WORKSPACE_PATH;
|
|
6121
6255
|
const tier = bootstrapTier ?? "full";
|
|
6122
|
-
const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, chatId, mode);
|
|
6256
|
+
const fullPrompt = await assembleBootstrapPrompt(userMessage, tier, chatId, mode, responseStyle);
|
|
6123
6257
|
const existingSessionId = getSessionId(chatId);
|
|
6124
6258
|
const allowedTools = getEnabledTools(chatId);
|
|
6125
6259
|
const mcpConfigPath = getMcpConfigPath(chatId);
|
|
@@ -6209,7 +6343,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
|
|
|
6209
6343
|
if (!flag) return args;
|
|
6210
6344
|
return [...args, ...flag, mcpConfigPath];
|
|
6211
6345
|
}
|
|
6212
|
-
var
|
|
6346
|
+
var activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
|
|
6213
6347
|
var init_agent = __esm({
|
|
6214
6348
|
"src/agent.ts"() {
|
|
6215
6349
|
"use strict";
|
|
@@ -6222,8 +6356,6 @@ var init_agent = __esm({
|
|
|
6222
6356
|
init_summarize();
|
|
6223
6357
|
init_server();
|
|
6224
6358
|
init_mcp_config();
|
|
6225
|
-
__filename2 = fileURLToPath2(import.meta.url);
|
|
6226
|
-
__dirname2 = dirname2(__filename2);
|
|
6227
6359
|
activeChats = /* @__PURE__ */ new Map();
|
|
6228
6360
|
chatLocks = /* @__PURE__ */ new Map();
|
|
6229
6361
|
SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -6280,30 +6412,40 @@ function parseTelegramTarget(target) {
|
|
|
6280
6412
|
async function deliverJobOutput(job, responseText) {
|
|
6281
6413
|
if (job.deliveryMode === "none") {
|
|
6282
6414
|
log(`[delivery] Job #${job.id}: delivery=none, skipping`);
|
|
6283
|
-
return;
|
|
6415
|
+
return true;
|
|
6416
|
+
}
|
|
6417
|
+
if (job.deliveryMode === "dry_run") {
|
|
6418
|
+
log(`[delivery] Job #${job.id}: DRY RUN \u2014 output (${responseText.length} chars):
|
|
6419
|
+
${responseText.slice(0, 500)}`);
|
|
6420
|
+
appendToLog(job.chatId, `[cron:#${job.id}:dry_run] ${job.description}`, responseText, job.backend ?? "claude", job.model, null);
|
|
6421
|
+
return true;
|
|
6284
6422
|
}
|
|
6285
6423
|
try {
|
|
6286
6424
|
if (job.deliveryMode === "webhook") {
|
|
6287
6425
|
await deliverWebhook(job, responseText);
|
|
6288
|
-
|
|
6426
|
+
appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, responseText, job.backend ?? "claude", job.model, null);
|
|
6427
|
+
return true;
|
|
6289
6428
|
}
|
|
6290
6429
|
const channelName = job.channel ?? "telegram";
|
|
6291
6430
|
const channel = registry?.get(channelName);
|
|
6292
6431
|
if (!channel) {
|
|
6293
6432
|
error(`[delivery] Job #${job.id}: channel "${channelName}" not found`);
|
|
6294
|
-
return;
|
|
6433
|
+
return false;
|
|
6295
6434
|
}
|
|
6296
6435
|
const targetChatId = job.target ?? job.chatId;
|
|
6297
6436
|
const cleanText = await processFileSends(targetChatId, channel, responseText);
|
|
6298
|
-
if (!cleanText) return;
|
|
6437
|
+
if (!cleanText) return true;
|
|
6299
6438
|
if (channelName === "telegram") {
|
|
6300
6439
|
const parsed = parseTelegramTarget(targetChatId);
|
|
6301
|
-
await channel.sendText(parsed.chatId, cleanText);
|
|
6440
|
+
await channel.sendText(parsed.chatId, cleanText, void 0, parsed.threadId);
|
|
6302
6441
|
} else {
|
|
6303
6442
|
await channel.sendText(targetChatId, cleanText);
|
|
6304
6443
|
}
|
|
6444
|
+
appendToLog(job.chatId, `[cron:#${job.id}] ${job.description}`, cleanText, job.backend ?? "claude", job.model, null);
|
|
6445
|
+
return true;
|
|
6305
6446
|
} catch (err) {
|
|
6306
6447
|
error(`[delivery] Job #${job.id} delivery failed (non-fatal): ${errorMessage(err)}`);
|
|
6448
|
+
return false;
|
|
6307
6449
|
}
|
|
6308
6450
|
}
|
|
6309
6451
|
async function deliverWebhook(job, responseText) {
|
|
@@ -6321,7 +6463,8 @@ async function deliverWebhook(job, responseText) {
|
|
|
6321
6463
|
description: job.description,
|
|
6322
6464
|
text: responseText,
|
|
6323
6465
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6324
|
-
})
|
|
6466
|
+
}),
|
|
6467
|
+
signal: AbortSignal.timeout(3e4)
|
|
6325
6468
|
});
|
|
6326
6469
|
if (!resp.ok) {
|
|
6327
6470
|
error(`[delivery] Webhook POST to ${url} failed: ${resp.status} ${resp.statusText}`);
|
|
@@ -6362,6 +6505,7 @@ var init_delivery = __esm({
|
|
|
6362
6505
|
"src/scheduler/delivery.ts"() {
|
|
6363
6506
|
"use strict";
|
|
6364
6507
|
init_log();
|
|
6508
|
+
init_session_log();
|
|
6365
6509
|
registry = null;
|
|
6366
6510
|
BLOCKED_PATH_PATTERNS = [
|
|
6367
6511
|
/\/\.ssh\//,
|
|
@@ -6691,18 +6835,22 @@ async function executeJob(job) {
|
|
|
6691
6835
|
return;
|
|
6692
6836
|
}
|
|
6693
6837
|
const response = await runWithRetry(job, resolvedModel, runId, t0);
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
cacheRead: response.usage?.cacheRead,
|
|
6698
|
-
durationMs: Date.now() - t0
|
|
6699
|
-
});
|
|
6838
|
+
const isEmpty = !response.text || response.text.trim().length === 0 || /^\(No response from/i.test(response.text);
|
|
6839
|
+
const contentStatus = isEmpty ? "no_content" : "success";
|
|
6840
|
+
const durationMs = Date.now() - t0;
|
|
6700
6841
|
updateJobLastRun(job.id, (/* @__PURE__ */ new Date()).toISOString());
|
|
6701
6842
|
resetJobFailures(job.id);
|
|
6702
6843
|
if (response.usage) {
|
|
6703
6844
|
addUsage(job.chatId, response.usage.input, response.usage.output, response.usage.cacheRead, resolvedModel);
|
|
6704
6845
|
}
|
|
6705
|
-
await deliverJobOutput(job, response.text);
|
|
6846
|
+
const delivered = await deliverJobOutput(job, response.text);
|
|
6847
|
+
const finalStatus = !delivered && contentStatus === "success" ? "delivery_failed" : contentStatus;
|
|
6848
|
+
completeJobRun(runId, finalStatus, {
|
|
6849
|
+
usageInput: response.usage?.input,
|
|
6850
|
+
usageOutput: response.usage?.output,
|
|
6851
|
+
cacheRead: response.usage?.cacheRead,
|
|
6852
|
+
durationMs
|
|
6853
|
+
});
|
|
6706
6854
|
} catch (err) {
|
|
6707
6855
|
const durationMs = Date.now() - t0;
|
|
6708
6856
|
const errorClass = classifyError(err);
|
|
@@ -6721,6 +6869,9 @@ async function executeJob(job) {
|
|
|
6721
6869
|
}
|
|
6722
6870
|
} finally {
|
|
6723
6871
|
runningJobs.delete(job.id);
|
|
6872
|
+
if (job.sessionType === "isolated" && job.thinking && job.thinking !== "auto") {
|
|
6873
|
+
clearThinkingLevel(`cron:${job.id}:${runId}`);
|
|
6874
|
+
}
|
|
6724
6875
|
}
|
|
6725
6876
|
}
|
|
6726
6877
|
async function runWithRetry(job, model2, runId, t0) {
|
|
@@ -6788,7 +6939,7 @@ function resolveJobModel(job) {
|
|
|
6788
6939
|
const backendId = resolveJobBackendId(job);
|
|
6789
6940
|
try {
|
|
6790
6941
|
const adapter = getAdapter(backendId);
|
|
6791
|
-
return
|
|
6942
|
+
return adapter.defaultModel;
|
|
6792
6943
|
} catch {
|
|
6793
6944
|
return "claude-sonnet-4-6";
|
|
6794
6945
|
}
|
|
@@ -6817,7 +6968,7 @@ var init_cron = __esm({
|
|
|
6817
6968
|
});
|
|
6818
6969
|
|
|
6819
6970
|
// src/agents/runners/wrap-backend.ts
|
|
6820
|
-
import { join as
|
|
6971
|
+
import { join as join7 } from "path";
|
|
6821
6972
|
function buildMcpCommands(backendId) {
|
|
6822
6973
|
const exe = backendId;
|
|
6823
6974
|
return {
|
|
@@ -6878,7 +7029,8 @@ function wrapBackendAdapter(adapter) {
|
|
|
6878
7029
|
sessionId: opts.sessionId,
|
|
6879
7030
|
permMode: opts.permMode ?? "plan",
|
|
6880
7031
|
allowedTools: opts.allowedTools ?? [],
|
|
6881
|
-
cwd: opts.cwd
|
|
7032
|
+
cwd: opts.cwd,
|
|
7033
|
+
model: opts.model
|
|
6882
7034
|
});
|
|
6883
7035
|
return config2.args;
|
|
6884
7036
|
},
|
|
@@ -6907,7 +7059,7 @@ function wrapBackendAdapter(adapter) {
|
|
|
6907
7059
|
const configPath = writeMcpConfigFile(server);
|
|
6908
7060
|
return ["--mcp-config", configPath];
|
|
6909
7061
|
},
|
|
6910
|
-
getSkillPath: () =>
|
|
7062
|
+
getSkillPath: () => join7(SKILLS_PATH, `agent-${adapter.id}.md`)
|
|
6911
7063
|
};
|
|
6912
7064
|
}
|
|
6913
7065
|
var BACKEND_CAPABILITIES;
|
|
@@ -6950,7 +7102,7 @@ var init_wrap_backend = __esm({
|
|
|
6950
7102
|
|
|
6951
7103
|
// src/agents/runners/config-loader.ts
|
|
6952
7104
|
import { readFileSync as readFileSync6, readdirSync as readdirSync4, existsSync as existsSync9, mkdirSync as mkdirSync4, watchFile, unwatchFile } from "fs";
|
|
6953
|
-
import { join as
|
|
7105
|
+
import { join as join8 } from "path";
|
|
6954
7106
|
import { execFileSync } from "child_process";
|
|
6955
7107
|
function resolveExecutable(config2) {
|
|
6956
7108
|
if (existsSync9(config2.executable)) return config2.executable;
|
|
@@ -7086,7 +7238,7 @@ function configToRunner(config2) {
|
|
|
7086
7238
|
prepareMcpInjection() {
|
|
7087
7239
|
return [];
|
|
7088
7240
|
},
|
|
7089
|
-
getSkillPath: () =>
|
|
7241
|
+
getSkillPath: () => join8(SKILLS_PATH, `agent-${config2.id}.md`)
|
|
7090
7242
|
};
|
|
7091
7243
|
}
|
|
7092
7244
|
function loadRunnerConfig(filePath) {
|
|
@@ -7106,7 +7258,7 @@ function loadAllRunnerConfigs() {
|
|
|
7106
7258
|
const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
|
|
7107
7259
|
const configs = [];
|
|
7108
7260
|
for (const file of files) {
|
|
7109
|
-
const config2 = loadRunnerConfig(
|
|
7261
|
+
const config2 = loadRunnerConfig(join8(RUNNERS_PATH, file));
|
|
7110
7262
|
if (config2) configs.push(config2);
|
|
7111
7263
|
}
|
|
7112
7264
|
return configs;
|
|
@@ -7136,7 +7288,7 @@ function watchRunnerConfigs(onChange) {
|
|
|
7136
7288
|
}
|
|
7137
7289
|
const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
|
|
7138
7290
|
for (const file of files) {
|
|
7139
|
-
const fullPath =
|
|
7291
|
+
const fullPath = join8(RUNNERS_PATH, file);
|
|
7140
7292
|
if (watchedFiles.has(fullPath)) continue;
|
|
7141
7293
|
watchedFiles.add(fullPath);
|
|
7142
7294
|
watchFile(fullPath, { interval: 5e3 }, () => {
|
|
@@ -7445,6 +7597,8 @@ var init_telegram2 = __esm({
|
|
|
7445
7597
|
// Skills & profile
|
|
7446
7598
|
{ command: "skills", description: "List and invoke skills" },
|
|
7447
7599
|
{ command: "voice", description: "Toggle voice responses" },
|
|
7600
|
+
{ command: "voice_config", description: "Configure voice provider and voice" },
|
|
7601
|
+
{ command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
|
|
7448
7602
|
{ command: "imagine", description: "Generate an image from a prompt" },
|
|
7449
7603
|
{ command: "heartbeat", description: "Configure proactive heartbeat" },
|
|
7450
7604
|
{ command: "chats", description: "Manage multi-chat aliases" }
|
|
@@ -7503,11 +7657,12 @@ var init_telegram2 = __esm({
|
|
|
7503
7657
|
async sendTyping(chatId) {
|
|
7504
7658
|
await this.bot.api.sendChatAction(numericChatId(chatId), "typing");
|
|
7505
7659
|
}
|
|
7506
|
-
async sendText(chatId, text, parseMode) {
|
|
7660
|
+
async sendText(chatId, text, parseMode, threadId) {
|
|
7661
|
+
const threadOpts = threadId ? { message_thread_id: threadId } : {};
|
|
7507
7662
|
if (parseMode === "plain") {
|
|
7508
7663
|
const plainChunks = splitMessage(text);
|
|
7509
7664
|
for (const chunk of plainChunks) {
|
|
7510
|
-
await this.bot.api.sendMessage(numericChatId(chatId), chunk);
|
|
7665
|
+
await this.bot.api.sendMessage(numericChatId(chatId), chunk, { ...threadOpts });
|
|
7511
7666
|
}
|
|
7512
7667
|
return;
|
|
7513
7668
|
}
|
|
@@ -7516,12 +7671,14 @@ var init_telegram2 = __esm({
|
|
|
7516
7671
|
for (const chunk of chunks) {
|
|
7517
7672
|
try {
|
|
7518
7673
|
await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
|
|
7519
|
-
parse_mode: "HTML"
|
|
7674
|
+
parse_mode: "HTML",
|
|
7675
|
+
...threadOpts
|
|
7520
7676
|
});
|
|
7521
7677
|
} catch {
|
|
7522
7678
|
await this.bot.api.sendMessage(
|
|
7523
7679
|
numericChatId(chatId),
|
|
7524
|
-
chunk.replace(/<[^>]+>/g, "")
|
|
7680
|
+
chunk.replace(/<[^>]+>/g, ""),
|
|
7681
|
+
{ ...threadOpts }
|
|
7525
7682
|
);
|
|
7526
7683
|
}
|
|
7527
7684
|
}
|
|
@@ -7546,7 +7703,9 @@ var init_telegram2 = __esm({
|
|
|
7546
7703
|
}
|
|
7547
7704
|
async sendTextReturningId(chatId, text, parseMode) {
|
|
7548
7705
|
try {
|
|
7549
|
-
const
|
|
7706
|
+
const formatted = parseMode === "html" ? text : parseMode === "plain" ? text : formatForTelegram(text);
|
|
7707
|
+
const opts = parseMode === "plain" ? {} : { parse_mode: "HTML" };
|
|
7708
|
+
const msg = await this.bot.api.sendMessage(numericChatId(chatId), formatted, opts);
|
|
7550
7709
|
return msg.message_id.toString();
|
|
7551
7710
|
} catch {
|
|
7552
7711
|
return void 0;
|
|
@@ -7720,13 +7879,13 @@ __export(discover_exports, {
|
|
|
7720
7879
|
});
|
|
7721
7880
|
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
7722
7881
|
import { createHash } from "crypto";
|
|
7723
|
-
import { homedir as
|
|
7724
|
-
import { join as
|
|
7882
|
+
import { homedir as homedir4 } from "os";
|
|
7883
|
+
import { join as join9 } from "path";
|
|
7725
7884
|
async function discoverAllSkills() {
|
|
7726
7885
|
const rawSkills = [];
|
|
7727
7886
|
rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
|
|
7728
7887
|
for (const backendId of getAllBackendIds()) {
|
|
7729
|
-
const dirs = BACKEND_SKILL_DIRS[backendId] ?? [
|
|
7888
|
+
const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join9(homedir4(), `.${backendId}`, "skills")];
|
|
7730
7889
|
for (const dir of dirs) {
|
|
7731
7890
|
rawSkills.push(...await scanSkillDir(dir, backendId));
|
|
7732
7891
|
}
|
|
@@ -7746,7 +7905,7 @@ async function scanSkillDir(skillsDir, source) {
|
|
|
7746
7905
|
let content;
|
|
7747
7906
|
let resolvedPath;
|
|
7748
7907
|
for (const candidate of SKILL_FILE_CANDIDATES) {
|
|
7749
|
-
const p =
|
|
7908
|
+
const p = join9(skillsDir, entry.name, candidate);
|
|
7750
7909
|
try {
|
|
7751
7910
|
content = await readFile2(p, "utf-8");
|
|
7752
7911
|
resolvedPath = p;
|
|
@@ -7849,11 +8008,11 @@ var init_discover = __esm({
|
|
|
7849
8008
|
init_backends();
|
|
7850
8009
|
SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
|
|
7851
8010
|
BACKEND_SKILL_DIRS = {
|
|
7852
|
-
claude: [
|
|
7853
|
-
gemini: [
|
|
8011
|
+
claude: [join9(homedir4(), ".claude", "skills")],
|
|
8012
|
+
gemini: [join9(homedir4(), ".gemini", "skills")],
|
|
7854
8013
|
codex: [
|
|
7855
|
-
|
|
7856
|
-
|
|
8014
|
+
join9(homedir4(), ".agents", "skills"),
|
|
8015
|
+
join9(homedir4(), ".codex", "skills")
|
|
7857
8016
|
]
|
|
7858
8017
|
};
|
|
7859
8018
|
}
|
|
@@ -7866,7 +8025,7 @@ __export(install_exports, {
|
|
|
7866
8025
|
});
|
|
7867
8026
|
import { mkdir, readdir as readdir2, readFile as readFile3, cp } from "fs/promises";
|
|
7868
8027
|
import { existsSync as existsSync10 } from "fs";
|
|
7869
|
-
import { join as
|
|
8028
|
+
import { join as join10, basename } from "path";
|
|
7870
8029
|
import { execSync as execSync4 } from "child_process";
|
|
7871
8030
|
async function installSkillFromGitHub(urlOrShorthand) {
|
|
7872
8031
|
let repoUrl;
|
|
@@ -7877,23 +8036,23 @@ async function installSkillFromGitHub(urlOrShorthand) {
|
|
|
7877
8036
|
}
|
|
7878
8037
|
repoUrl = parsed.cloneUrl;
|
|
7879
8038
|
subPath = parsed.subPath;
|
|
7880
|
-
const tmpDir =
|
|
8039
|
+
const tmpDir = join10("/tmp", `cc-claw-skill-${Date.now()}`);
|
|
7881
8040
|
try {
|
|
7882
8041
|
log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
|
|
7883
8042
|
execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
|
|
7884
8043
|
stdio: "pipe",
|
|
7885
8044
|
timeout: 3e4
|
|
7886
8045
|
});
|
|
7887
|
-
if (!existsSync10(
|
|
8046
|
+
if (!existsSync10(join10(tmpDir, ".git"))) {
|
|
7888
8047
|
return { success: false, error: "Git clone failed: no .git directory produced" };
|
|
7889
8048
|
}
|
|
7890
|
-
const searchRoot = subPath ?
|
|
8049
|
+
const searchRoot = subPath ? join10(tmpDir, subPath) : tmpDir;
|
|
7891
8050
|
const skillDir = await findSkillDir(searchRoot);
|
|
7892
8051
|
if (!skillDir) {
|
|
7893
8052
|
return { success: false, error: "No SKILL.md found in the repository." };
|
|
7894
8053
|
}
|
|
7895
8054
|
const skillFolderName = basename(skillDir);
|
|
7896
|
-
const destDir =
|
|
8055
|
+
const destDir = join10(SKILLS_PATH, skillFolderName);
|
|
7897
8056
|
if (existsSync10(destDir)) {
|
|
7898
8057
|
log(`[skill-install] Overwriting existing skill at ${destDir}`);
|
|
7899
8058
|
}
|
|
@@ -7901,12 +8060,12 @@ async function installSkillFromGitHub(urlOrShorthand) {
|
|
|
7901
8060
|
await cp(skillDir, destDir, { recursive: true });
|
|
7902
8061
|
let skillName = skillFolderName;
|
|
7903
8062
|
try {
|
|
7904
|
-
const content = await readFile3(
|
|
8063
|
+
const content = await readFile3(join10(destDir, "SKILL.md"), "utf-8");
|
|
7905
8064
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
7906
8065
|
if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
7907
8066
|
} catch {
|
|
7908
8067
|
try {
|
|
7909
|
-
const content = await readFile3(
|
|
8068
|
+
const content = await readFile3(join10(destDir, "skill.md"), "utf-8");
|
|
7910
8069
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
7911
8070
|
if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
7912
8071
|
} catch {
|
|
@@ -7941,15 +8100,15 @@ function parseGitHubUrl(input) {
|
|
|
7941
8100
|
async function findSkillDir(root) {
|
|
7942
8101
|
const candidates = ["SKILL.md", "skill.md"];
|
|
7943
8102
|
for (const c of candidates) {
|
|
7944
|
-
if (existsSync10(
|
|
8103
|
+
if (existsSync10(join10(root, c))) return root;
|
|
7945
8104
|
}
|
|
7946
8105
|
try {
|
|
7947
8106
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
7948
8107
|
for (const entry of entries) {
|
|
7949
8108
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
7950
8109
|
for (const c of candidates) {
|
|
7951
|
-
if (existsSync10(
|
|
7952
|
-
return
|
|
8110
|
+
if (existsSync10(join10(root, entry.name, c))) {
|
|
8111
|
+
return join10(root, entry.name);
|
|
7953
8112
|
}
|
|
7954
8113
|
}
|
|
7955
8114
|
}
|
|
@@ -7961,15 +8120,15 @@ async function findSkillDir(root) {
|
|
|
7961
8120
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
7962
8121
|
let subEntries;
|
|
7963
8122
|
try {
|
|
7964
|
-
subEntries = await readdir2(
|
|
8123
|
+
subEntries = await readdir2(join10(root, entry.name), { withFileTypes: true });
|
|
7965
8124
|
} catch {
|
|
7966
8125
|
continue;
|
|
7967
8126
|
}
|
|
7968
8127
|
for (const sub of subEntries) {
|
|
7969
8128
|
if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
|
|
7970
8129
|
for (const c of candidates) {
|
|
7971
|
-
if (existsSync10(
|
|
7972
|
-
return
|
|
8130
|
+
if (existsSync10(join10(root, entry.name, sub.name, c))) {
|
|
8131
|
+
return join10(root, entry.name, sub.name);
|
|
7973
8132
|
}
|
|
7974
8133
|
}
|
|
7975
8134
|
}
|
|
@@ -7988,7 +8147,7 @@ var init_install = __esm({
|
|
|
7988
8147
|
|
|
7989
8148
|
// src/bootstrap/profile.ts
|
|
7990
8149
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
|
|
7991
|
-
import { join as
|
|
8150
|
+
import { join as join11 } from "path";
|
|
7992
8151
|
function hasActiveProfile(chatId) {
|
|
7993
8152
|
return activeProfiles.has(chatId);
|
|
7994
8153
|
}
|
|
@@ -8133,14 +8292,14 @@ var init_profile = __esm({
|
|
|
8133
8292
|
"use strict";
|
|
8134
8293
|
init_paths();
|
|
8135
8294
|
init_log();
|
|
8136
|
-
USER_PATH2 =
|
|
8295
|
+
USER_PATH2 = join11(WORKSPACE_PATH, "USER.md");
|
|
8137
8296
|
activeProfiles = /* @__PURE__ */ new Map();
|
|
8138
8297
|
}
|
|
8139
8298
|
});
|
|
8140
8299
|
|
|
8141
8300
|
// src/bootstrap/heartbeat.ts
|
|
8142
8301
|
import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
|
|
8143
|
-
import { join as
|
|
8302
|
+
import { join as join12 } from "path";
|
|
8144
8303
|
function initHeartbeat(channelReg) {
|
|
8145
8304
|
registry2 = channelReg;
|
|
8146
8305
|
}
|
|
@@ -8148,8 +8307,18 @@ function startHeartbeatForChat(chatId) {
|
|
|
8148
8307
|
stopHeartbeatForChat(chatId);
|
|
8149
8308
|
const config2 = getHeartbeatConfig(chatId);
|
|
8150
8309
|
if (!config2 || !config2.enabled) return;
|
|
8310
|
+
let running = false;
|
|
8151
8311
|
const timer = setInterval(async () => {
|
|
8152
|
-
|
|
8312
|
+
if (running) {
|
|
8313
|
+
log(`[heartbeat] Skipping tick for ${chatId}: previous heartbeat still running`);
|
|
8314
|
+
return;
|
|
8315
|
+
}
|
|
8316
|
+
running = true;
|
|
8317
|
+
try {
|
|
8318
|
+
await runHeartbeat(chatId, config2);
|
|
8319
|
+
} finally {
|
|
8320
|
+
running = false;
|
|
8321
|
+
}
|
|
8153
8322
|
}, config2.intervalMs);
|
|
8154
8323
|
activeTimers2.set(chatId, timer);
|
|
8155
8324
|
const nextBeat = new Date(Date.now() + config2.intervalMs).toISOString();
|
|
@@ -8316,7 +8485,7 @@ var init_heartbeat = __esm({
|
|
|
8316
8485
|
init_backends();
|
|
8317
8486
|
init_health2();
|
|
8318
8487
|
init_log();
|
|
8319
|
-
HEARTBEAT_MD_PATH =
|
|
8488
|
+
HEARTBEAT_MD_PATH = join12(WORKSPACE_PATH, "HEARTBEAT.md");
|
|
8320
8489
|
HEARTBEAT_OK = "HEARTBEAT_OK";
|
|
8321
8490
|
registry2 = null;
|
|
8322
8491
|
activeTimers2 = /* @__PURE__ */ new Map();
|
|
@@ -8362,8 +8531,9 @@ var init_format_time = __esm({
|
|
|
8362
8531
|
});
|
|
8363
8532
|
|
|
8364
8533
|
// src/media/image-gen.ts
|
|
8365
|
-
import {
|
|
8366
|
-
import {
|
|
8534
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
|
|
8535
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
8536
|
+
import { join as join13 } from "path";
|
|
8367
8537
|
async function generateImage(prompt) {
|
|
8368
8538
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
8369
8539
|
if (!apiKey) {
|
|
@@ -8415,9 +8585,9 @@ async function generateImage(prompt) {
|
|
|
8415
8585
|
}
|
|
8416
8586
|
const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
|
|
8417
8587
|
const filename = `img_${Date.now()}.${ext}`;
|
|
8418
|
-
const filePath =
|
|
8588
|
+
const filePath = join13(IMAGE_OUTPUT_DIR, filename);
|
|
8419
8589
|
const buffer = Buffer.from(imageData, "base64");
|
|
8420
|
-
|
|
8590
|
+
await writeFile2(filePath, buffer);
|
|
8421
8591
|
log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
|
|
8422
8592
|
return { filePath, text: textResponse, mimeType };
|
|
8423
8593
|
}
|
|
@@ -8430,8 +8600,8 @@ var init_image_gen = __esm({
|
|
|
8430
8600
|
"use strict";
|
|
8431
8601
|
init_log();
|
|
8432
8602
|
IMAGE_MODEL = "gemini-3.1-flash-image-preview";
|
|
8433
|
-
IMAGE_OUTPUT_DIR =
|
|
8434
|
-
process.env.CC_CLAW_HOME ??
|
|
8603
|
+
IMAGE_OUTPUT_DIR = join13(
|
|
8604
|
+
process.env.CC_CLAW_HOME ?? join13(process.env.HOME ?? "/tmp", ".cc-claw"),
|
|
8435
8605
|
"data",
|
|
8436
8606
|
"images"
|
|
8437
8607
|
);
|
|
@@ -8458,6 +8628,23 @@ function toggleVoice(chatId) {
|
|
|
8458
8628
|
`).run(chatId, newState, newState);
|
|
8459
8629
|
return newState === 1;
|
|
8460
8630
|
}
|
|
8631
|
+
function getVoiceConfig(chatId) {
|
|
8632
|
+
const db3 = getDb();
|
|
8633
|
+
const row = db3.prepare("SELECT enabled, provider, voice_id FROM chat_voice WHERE chat_id = ?").get(chatId);
|
|
8634
|
+
return {
|
|
8635
|
+
enabled: row?.enabled === 1,
|
|
8636
|
+
provider: row?.provider ?? "elevenlabs",
|
|
8637
|
+
voiceId: row?.voice_id ?? null
|
|
8638
|
+
};
|
|
8639
|
+
}
|
|
8640
|
+
function setVoiceProvider(chatId, provider, voiceId) {
|
|
8641
|
+
const db3 = getDb();
|
|
8642
|
+
db3.prepare(`
|
|
8643
|
+
INSERT INTO chat_voice (chat_id, enabled, provider, voice_id)
|
|
8644
|
+
VALUES (?, 1, ?, ?)
|
|
8645
|
+
ON CONFLICT(chat_id) DO UPDATE SET provider = ?, voice_id = ?, enabled = 1
|
|
8646
|
+
`).run(chatId, provider, voiceId, provider, voiceId);
|
|
8647
|
+
}
|
|
8461
8648
|
async function transcribeAudio(audioBuffer, mimeType = "audio/ogg") {
|
|
8462
8649
|
const GROQ_API_KEY = process.env.GROQ_API_KEY;
|
|
8463
8650
|
if (!GROQ_API_KEY) return null;
|
|
@@ -8476,25 +8663,48 @@ async function transcribeAudio(audioBuffer, mimeType = "audio/ogg") {
|
|
|
8476
8663
|
}
|
|
8477
8664
|
return (await response.text()).trim();
|
|
8478
8665
|
}
|
|
8479
|
-
async function synthesizeSpeech(text) {
|
|
8666
|
+
async function synthesizeSpeech(text, chatId) {
|
|
8480
8667
|
const ttsText = text.length > 4e3 ? text.slice(0, 4e3) + "..." : text;
|
|
8481
|
-
const
|
|
8482
|
-
|
|
8668
|
+
const config2 = chatId ? getVoiceConfig(chatId) : null;
|
|
8669
|
+
const provider = config2?.provider ?? "elevenlabs";
|
|
8670
|
+
if (provider === "grok") {
|
|
8671
|
+
const xaiKey = process.env.XAI_API_KEY;
|
|
8672
|
+
if (xaiKey) {
|
|
8673
|
+
try {
|
|
8674
|
+
return await grokTts(ttsText, xaiKey, config2?.voiceId ?? "eve");
|
|
8675
|
+
} catch (err) {
|
|
8676
|
+
error("[tts] Grok failed:", err);
|
|
8677
|
+
}
|
|
8678
|
+
} else {
|
|
8679
|
+
log("[tts] Grok selected but XAI_API_KEY not set, falling back");
|
|
8680
|
+
}
|
|
8681
|
+
}
|
|
8682
|
+
if (provider === "macos") {
|
|
8483
8683
|
try {
|
|
8484
|
-
return await
|
|
8684
|
+
return await macOsTts(ttsText, config2?.voiceId ?? "Samantha");
|
|
8485
8685
|
} catch (err) {
|
|
8486
|
-
error("[tts]
|
|
8686
|
+
error("[tts] macOS TTS failed:", err);
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
if (provider === "elevenlabs" || provider === "grok") {
|
|
8690
|
+
const elevenLabsKey = process.env.ELEVENLABS_API_KEY;
|
|
8691
|
+
if (elevenLabsKey) {
|
|
8692
|
+
try {
|
|
8693
|
+
const voiceId = provider === "elevenlabs" && config2?.voiceId ? config2.voiceId : process.env.ELEVENLABS_VOICE_ID ?? "21m00Tcm4TlvDq8ikWAM";
|
|
8694
|
+
return await elevenLabsTts(ttsText, elevenLabsKey, voiceId);
|
|
8695
|
+
} catch (err) {
|
|
8696
|
+
error("[tts] ElevenLabs failed:", err);
|
|
8697
|
+
}
|
|
8487
8698
|
}
|
|
8488
8699
|
}
|
|
8489
8700
|
try {
|
|
8490
|
-
return await macOsTts(ttsText);
|
|
8701
|
+
return await macOsTts(ttsText, "Samantha");
|
|
8491
8702
|
} catch (err) {
|
|
8492
8703
|
error("[tts] macOS TTS failed:", err);
|
|
8493
8704
|
}
|
|
8494
8705
|
return null;
|
|
8495
8706
|
}
|
|
8496
|
-
async function elevenLabsTts(text, apiKey) {
|
|
8497
|
-
const voiceId = process.env.ELEVENLABS_VOICE_ID ?? "21m00Tcm4TlvDq8ikWAM";
|
|
8707
|
+
async function elevenLabsTts(text, apiKey, voiceId) {
|
|
8498
8708
|
const response = await fetch(
|
|
8499
8709
|
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
|
|
8500
8710
|
{
|
|
@@ -8515,12 +8725,47 @@ async function elevenLabsTts(text, apiKey) {
|
|
|
8515
8725
|
}
|
|
8516
8726
|
return Buffer.from(await response.arrayBuffer());
|
|
8517
8727
|
}
|
|
8518
|
-
async function
|
|
8728
|
+
async function grokTts(text, apiKey, voiceId) {
|
|
8729
|
+
const response = await fetch("https://api.x.ai/v1/tts", {
|
|
8730
|
+
method: "POST",
|
|
8731
|
+
headers: {
|
|
8732
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
8733
|
+
"Content-Type": "application/json"
|
|
8734
|
+
},
|
|
8735
|
+
body: JSON.stringify({
|
|
8736
|
+
text,
|
|
8737
|
+
voice_id: voiceId,
|
|
8738
|
+
language: "en"
|
|
8739
|
+
})
|
|
8740
|
+
});
|
|
8741
|
+
if (!response.ok) {
|
|
8742
|
+
const errText = await response.text().catch(() => "");
|
|
8743
|
+
throw new Error(`Grok TTS API error: ${response.status} ${errText}`);
|
|
8744
|
+
}
|
|
8745
|
+
const mp3Buffer = Buffer.from(await response.arrayBuffer());
|
|
8746
|
+
return await mp3ToOgg(mp3Buffer);
|
|
8747
|
+
}
|
|
8748
|
+
async function mp3ToOgg(mp3Buffer) {
|
|
8749
|
+
const id = crypto.randomUUID();
|
|
8750
|
+
const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
|
|
8751
|
+
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
8752
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
8753
|
+
await writeFile5(tmpMp3, mp3Buffer);
|
|
8754
|
+
await execFileAsync2("ffmpeg", ["-y", "-i", tmpMp3, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
|
|
8755
|
+
const oggBuffer = await readFile4(tmpOgg);
|
|
8756
|
+
unlink(tmpMp3).catch((err) => {
|
|
8757
|
+
error("[tts] cleanup failed:", err);
|
|
8758
|
+
});
|
|
8759
|
+
unlink(tmpOgg).catch((err) => {
|
|
8760
|
+
error("[tts] cleanup failed:", err);
|
|
8761
|
+
});
|
|
8762
|
+
return oggBuffer;
|
|
8763
|
+
}
|
|
8764
|
+
async function macOsTts(text, voice2 = "Samantha") {
|
|
8519
8765
|
const id = crypto.randomUUID();
|
|
8520
8766
|
const tmpAiff = `/tmp/cc-claw-tts-${id}.aiff`;
|
|
8521
8767
|
const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
|
|
8522
|
-
|
|
8523
|
-
await execFileAsync2("say", ["-o", tmpAiff, sanitized]);
|
|
8768
|
+
await execFileAsync2("say", ["-v", voice2, "-o", tmpAiff, text]);
|
|
8524
8769
|
await execFileAsync2("ffmpeg", ["-y", "-i", tmpAiff, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
|
|
8525
8770
|
const oggBuffer = await readFile4(tmpOgg);
|
|
8526
8771
|
unlink(tmpAiff).catch((err) => {
|
|
@@ -8531,13 +8776,28 @@ async function macOsTts(text) {
|
|
|
8531
8776
|
});
|
|
8532
8777
|
return oggBuffer;
|
|
8533
8778
|
}
|
|
8534
|
-
var execFileAsync2;
|
|
8779
|
+
var execFileAsync2, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
|
|
8535
8780
|
var init_stt = __esm({
|
|
8536
8781
|
"src/voice/stt.ts"() {
|
|
8537
8782
|
"use strict";
|
|
8538
8783
|
init_log();
|
|
8539
8784
|
init_store4();
|
|
8540
8785
|
execFileAsync2 = promisify2(execFile2);
|
|
8786
|
+
ELEVENLABS_VOICES = {
|
|
8787
|
+
"21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
|
|
8788
|
+
"EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
|
|
8789
|
+
"XB0fDUnXU5powFXDhCwa": { name: "Charlotte", gender: "F" },
|
|
8790
|
+
"pFZP5JQG7iQjIQuC4Bku": { name: "Lily", gender: "F" },
|
|
8791
|
+
"pNInz6obpgDQGcFmaJgB": { name: "Adam", gender: "M" },
|
|
8792
|
+
"nPczCjzI2devNBz1zQrb": { name: "Brian", gender: "M" },
|
|
8793
|
+
"onwK4e9ZLuTAKqWW03F9": { name: "Daniel", gender: "M" },
|
|
8794
|
+
"TxGEqnHWrfWFTfGW9XjX": { name: "Josh", gender: "M" }
|
|
8795
|
+
};
|
|
8796
|
+
GROK_VOICES = ["eve", "ara", "rex", "sal", "leo"];
|
|
8797
|
+
MACOS_VOICES = {
|
|
8798
|
+
"Samantha": { name: "Samantha", gender: "F" },
|
|
8799
|
+
"Albert": { name: "Albert", gender: "M" }
|
|
8800
|
+
};
|
|
8541
8801
|
}
|
|
8542
8802
|
});
|
|
8543
8803
|
|
|
@@ -9380,7 +9640,7 @@ var init_backend_cmd = __esm({
|
|
|
9380
9640
|
});
|
|
9381
9641
|
|
|
9382
9642
|
// src/router.ts
|
|
9383
|
-
import { readFile as readFile5, writeFile as
|
|
9643
|
+
import { readFile as readFile5, writeFile as writeFile3, unlink as unlink2 } from "fs/promises";
|
|
9384
9644
|
import { resolve as resolvePath } from "path";
|
|
9385
9645
|
function parseMcpListOutput(output2) {
|
|
9386
9646
|
const results = [];
|
|
@@ -9506,7 +9766,7 @@ async function handleCommand(msg, channel) {
|
|
|
9506
9766
|
case "help":
|
|
9507
9767
|
await channel.sendText(
|
|
9508
9768
|
chatId,
|
|
9509
|
-
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/remember <text> - Save a memory\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/imagine <prompt> - Generate an image (or /image)\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
|
|
9769
|
+
"Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/remember <text> - Save a memory\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/voice_config - Configure voice provider and voice\n/imagine <prompt> - Generate an image (or /image)\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
|
|
9510
9770
|
"plain"
|
|
9511
9771
|
);
|
|
9512
9772
|
break;
|
|
@@ -10000,12 +10260,37 @@ ${lines.join("\n")}`, "plain");
|
|
|
10000
10260
|
break;
|
|
10001
10261
|
}
|
|
10002
10262
|
case "voice": {
|
|
10003
|
-
const
|
|
10004
|
-
|
|
10005
|
-
chatId,
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10263
|
+
const vcEnabled = isVoiceEnabled(chatId);
|
|
10264
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10265
|
+
await channel.sendKeyboard(chatId, `\u{1F3A7} Voice responses: ${vcEnabled ? "ON" : "OFF"}`, [
|
|
10266
|
+
[
|
|
10267
|
+
{ label: `${vcEnabled ? "" : "\u2713 "}\u{1F507} Off`, data: "voice:off" },
|
|
10268
|
+
{ label: `${vcEnabled ? "\u2713 " : ""}\u{1F50A} On`, data: "voice:on" }
|
|
10269
|
+
]
|
|
10270
|
+
]);
|
|
10271
|
+
} else {
|
|
10272
|
+
const toggled = toggleVoice(chatId);
|
|
10273
|
+
await channel.sendText(chatId, toggled ? "Voice responses enabled." : "Voice responses disabled.", "plain");
|
|
10274
|
+
}
|
|
10275
|
+
break;
|
|
10276
|
+
}
|
|
10277
|
+
case "voice_config": {
|
|
10278
|
+
await sendVoiceConfigKeyboard(chatId, channel);
|
|
10279
|
+
break;
|
|
10280
|
+
}
|
|
10281
|
+
case "response_style": {
|
|
10282
|
+
const currentStyle = getResponseStyle(chatId);
|
|
10283
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10284
|
+
await channel.sendKeyboard(chatId, "\u{1F5E3}\uFE0F AI Response Style:", [
|
|
10285
|
+
[
|
|
10286
|
+
{ label: `${currentStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise" },
|
|
10287
|
+
{ label: `${currentStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal" },
|
|
10288
|
+
{ label: `${currentStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed" }
|
|
10289
|
+
]
|
|
10290
|
+
]);
|
|
10291
|
+
} else {
|
|
10292
|
+
await channel.sendText(chatId, `Current Response Style: ${currentStyle}`, "plain");
|
|
10293
|
+
}
|
|
10009
10294
|
break;
|
|
10010
10295
|
}
|
|
10011
10296
|
case "imagine":
|
|
@@ -10306,8 +10591,8 @@ Use /skills to see it.`, "plain");
|
|
|
10306
10591
|
if (!lim.max_input_tokens) continue;
|
|
10307
10592
|
const u = getBackendUsageInWindow(lim.backend, lim.window);
|
|
10308
10593
|
const pct = (u.input_tokens / lim.max_input_tokens * 100).toFixed(0);
|
|
10309
|
-
const
|
|
10310
|
-
lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${
|
|
10594
|
+
const warn2 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
|
|
10595
|
+
lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn2 ? " \u26A0\uFE0F" : ""}`);
|
|
10311
10596
|
}
|
|
10312
10597
|
}
|
|
10313
10598
|
await channel.sendText(chatId, lines.join("\n"), "plain");
|
|
@@ -10507,8 +10792,8 @@ Use /skills to see it.`, "plain");
|
|
|
10507
10792
|
lines.push(` \u2705 <b>cc-claw</b> <i>Agent orchestrator (spawn, tasks, inbox)</i>`);
|
|
10508
10793
|
}
|
|
10509
10794
|
const { execFile: execFile5 } = await import("child_process");
|
|
10510
|
-
const { homedir:
|
|
10511
|
-
const discoveryCwd =
|
|
10795
|
+
const { homedir: homedir7 } = await import("os");
|
|
10796
|
+
const discoveryCwd = homedir7();
|
|
10512
10797
|
const runnerResults = await Promise.allSettled(
|
|
10513
10798
|
getAllRunners().map((runner) => {
|
|
10514
10799
|
const listCmd = runner.getMcpListCommand();
|
|
@@ -10697,24 +10982,44 @@ async function handleMedia(msg, channel) {
|
|
|
10697
10982
|
return;
|
|
10698
10983
|
}
|
|
10699
10984
|
const videoBuffer = await channel.downloadFile(fileId);
|
|
10700
|
-
const
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10985
|
+
const videoMime = msg.metadata?.mimeType ?? msg.mimeType ?? "video/mp4";
|
|
10986
|
+
const videoExt = videoMime.split("/")[1]?.replace("quicktime", "mov") || "mp4";
|
|
10987
|
+
const tempVideoPath = `/tmp/cc-claw-video-${Date.now()}.${videoExt}`;
|
|
10988
|
+
await writeFile3(tempVideoPath, videoBuffer);
|
|
10989
|
+
let prompt2;
|
|
10990
|
+
const cleanupVideo = () => unlink2(tempVideoPath).catch(() => {
|
|
10991
|
+
});
|
|
10992
|
+
if (wantsVideoAnalysis(caption)) {
|
|
10993
|
+
const geminiPrompt = caption || "Analyze this video and describe what you see in detail.";
|
|
10994
|
+
let analysis;
|
|
10995
|
+
try {
|
|
10996
|
+
analysis = await analyzeVideo(videoBuffer, geminiPrompt, videoMime);
|
|
10997
|
+
} catch (err) {
|
|
10998
|
+
analysis = `Video analysis failed: ${errorMessage(err)}`;
|
|
10999
|
+
}
|
|
11000
|
+
prompt2 = caption ? `The user sent a video with caption: "${caption}"
|
|
10709
11001
|
|
|
10710
11002
|
Gemini video analysis:
|
|
10711
11003
|
${analysis}
|
|
10712
11004
|
|
|
11005
|
+
The video is also saved at: ${tempVideoPath}
|
|
11006
|
+
|
|
10713
11007
|
Respond to the user based on this analysis.` : `The user sent a video. Gemini analyzed it:
|
|
10714
11008
|
|
|
10715
11009
|
${analysis}
|
|
10716
11010
|
|
|
11011
|
+
The video is also saved at: ${tempVideoPath}
|
|
11012
|
+
|
|
10717
11013
|
Summarize this for the user.`;
|
|
11014
|
+
} else {
|
|
11015
|
+
prompt2 = caption ? `The user sent a video with caption: "${caption}"
|
|
11016
|
+
|
|
11017
|
+
The video has been saved to: ${tempVideoPath}
|
|
11018
|
+
|
|
11019
|
+
Respond to their message. Do NOT analyze the video unless they ask you to.` : `The user sent a video. It has been saved to: ${tempVideoPath}
|
|
11020
|
+
|
|
11021
|
+
Acknowledge receipt. Do NOT analyze the video unless they ask you to.`;
|
|
11022
|
+
}
|
|
10718
11023
|
const vidModel = resolveModel(chatId);
|
|
10719
11024
|
const vMode = getMode(chatId);
|
|
10720
11025
|
const vidVerbose = getVerboseLevel(chatId);
|
|
@@ -10722,6 +11027,7 @@ Summarize this for the user.`;
|
|
|
10722
11027
|
const response2 = await askAgent(chatId, prompt2, { cwd: getCwd(chatId), model: vidModel, permMode: vMode, onToolAction: vidToolCb });
|
|
10723
11028
|
if (response2.usage) addUsage(chatId, response2.usage.input, response2.usage.output, response2.usage.cacheRead, vidModel);
|
|
10724
11029
|
await sendResponse(chatId, channel, response2.text, msg.messageId);
|
|
11030
|
+
cleanupVideo();
|
|
10725
11031
|
return;
|
|
10726
11032
|
}
|
|
10727
11033
|
const fileBuffer = await channel.downloadFile(fileName);
|
|
@@ -10732,7 +11038,7 @@ Summarize this for the user.`;
|
|
|
10732
11038
|
if (msg.type === "photo" || isImageExt(ext)) {
|
|
10733
11039
|
const imgExt = msg.type === "photo" ? "jpg" : ext || "jpg";
|
|
10734
11040
|
tempFilePath = `/tmp/cc-claw-img-${Date.now()}.${imgExt}`;
|
|
10735
|
-
await
|
|
11041
|
+
await writeFile3(tempFilePath, fileBuffer);
|
|
10736
11042
|
prompt = caption ? `The user sent an image with caption: "${caption}"
|
|
10737
11043
|
|
|
10738
11044
|
The image has been saved to: ${tempFilePath}
|
|
@@ -11068,7 +11374,7 @@ async function sendResponse(chatId, channel, text, messageId) {
|
|
|
11068
11374
|
if (!cleanText) return;
|
|
11069
11375
|
if (isVoiceEnabled(chatId)) {
|
|
11070
11376
|
try {
|
|
11071
|
-
const audioBuffer = await synthesizeSpeech(cleanText);
|
|
11377
|
+
const audioBuffer = await synthesizeSpeech(cleanText, chatId);
|
|
11072
11378
|
if (audioBuffer) {
|
|
11073
11379
|
await channel.sendVoice(chatId, audioBuffer);
|
|
11074
11380
|
return;
|
|
@@ -11082,6 +11388,70 @@ async function sendResponse(chatId, channel, text, messageId) {
|
|
|
11082
11388
|
function isImageExt(ext) {
|
|
11083
11389
|
return ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(ext);
|
|
11084
11390
|
}
|
|
11391
|
+
async function sendVoiceConfigKeyboard(chatId, channel) {
|
|
11392
|
+
if (typeof channel.sendKeyboard !== "function") {
|
|
11393
|
+
await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", "plain");
|
|
11394
|
+
return;
|
|
11395
|
+
}
|
|
11396
|
+
const config2 = getVoiceConfig(chatId);
|
|
11397
|
+
const currentVoiceName = config2.provider === "elevenlabs" ? ELEVENLABS_VOICES[config2.voiceId ?? ""]?.name ?? "Rachel" : config2.provider === "macos" ? MACOS_VOICES[config2.voiceId ?? ""]?.name ?? "Samantha" : config2.voiceId ?? "eve";
|
|
11398
|
+
const providerLabel = config2.provider === "grok" ? "Grok (xAI)" : config2.provider === "macos" ? "macOS" : "ElevenLabs";
|
|
11399
|
+
const header2 = `\u{1F3A7} Voice Configuration
|
|
11400
|
+
Provider: ${providerLabel}
|
|
11401
|
+
Voice: ${currentVoiceName}
|
|
11402
|
+
Status: ${config2.enabled ? "ON" : "OFF"}`;
|
|
11403
|
+
const buttons = [];
|
|
11404
|
+
buttons.push([
|
|
11405
|
+
{ label: `${config2.provider === "elevenlabs" ? "\u2713 " : ""}ElevenLabs`, data: "vcfg:p:elevenlabs" },
|
|
11406
|
+
{ label: `${config2.provider === "grok" ? "\u2713 " : ""}Grok`, data: "vcfg:p:grok" },
|
|
11407
|
+
{ label: `${config2.provider === "macos" ? "\u2713 " : ""}macOS`, data: "vcfg:p:macos" }
|
|
11408
|
+
]);
|
|
11409
|
+
if (config2.provider === "elevenlabs") {
|
|
11410
|
+
const entries = Object.entries(ELEVENLABS_VOICES);
|
|
11411
|
+
const female = entries.filter(([, v]) => v.gender === "F");
|
|
11412
|
+
const male = entries.filter(([, v]) => v.gender === "M");
|
|
11413
|
+
buttons.push(female.map(([id, v]) => ({
|
|
11414
|
+
label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
|
|
11415
|
+
data: `vcfg:v:${id}`
|
|
11416
|
+
})));
|
|
11417
|
+
buttons.push(male.map(([id, v]) => ({
|
|
11418
|
+
label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
|
|
11419
|
+
data: `vcfg:v:${id}`
|
|
11420
|
+
})));
|
|
11421
|
+
} else if (config2.provider === "grok") {
|
|
11422
|
+
buttons.push(GROK_VOICES.map((v) => ({
|
|
11423
|
+
label: `${config2.voiceId === v ? "\u2713 " : ""}${v.charAt(0).toUpperCase() + v.slice(1)}`,
|
|
11424
|
+
data: `vcfg:v:${v}`
|
|
11425
|
+
})));
|
|
11426
|
+
} else {
|
|
11427
|
+
const entries = Object.entries(MACOS_VOICES);
|
|
11428
|
+
buttons.push(entries.map(([id, v]) => ({
|
|
11429
|
+
label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
|
|
11430
|
+
data: `vcfg:v:${id}`
|
|
11431
|
+
})));
|
|
11432
|
+
}
|
|
11433
|
+
await channel.sendKeyboard(chatId, header2, buttons);
|
|
11434
|
+
}
|
|
11435
|
+
function wantsVideoAnalysis(caption) {
|
|
11436
|
+
if (!caption) return false;
|
|
11437
|
+
const lower = caption.toLowerCase();
|
|
11438
|
+
const patterns = [
|
|
11439
|
+
/\banalyze\b/,
|
|
11440
|
+
/\banalysis\b/,
|
|
11441
|
+
/\bdescribe\b/,
|
|
11442
|
+
/\bwhat('s| is) (in |happening)/,
|
|
11443
|
+
/\btell me (about|what)/,
|
|
11444
|
+
/\bexplain\b/,
|
|
11445
|
+
/\bsummarize\b/,
|
|
11446
|
+
/\bwhat do you see\b/,
|
|
11447
|
+
/\breview\b/,
|
|
11448
|
+
/\bwatch\b/,
|
|
11449
|
+
/\btranscri/,
|
|
11450
|
+
/\bidentif/,
|
|
11451
|
+
/\bwhat happens\b/
|
|
11452
|
+
];
|
|
11453
|
+
return patterns.some((p) => p.test(lower));
|
|
11454
|
+
}
|
|
11085
11455
|
async function sendBackendSwitchConfirmation(chatId, target, channel) {
|
|
11086
11456
|
const current = getBackend(chatId);
|
|
11087
11457
|
const targetAdapter = getAdapter(target);
|
|
@@ -11227,7 +11597,7 @@ ${PERM_MODES[chosen]}`,
|
|
|
11227
11597
|
const pending = getPendingEscalation(chatId);
|
|
11228
11598
|
if (pending) {
|
|
11229
11599
|
removePendingEscalation(chatId);
|
|
11230
|
-
await handleMessage({ text: pending, chatId, source: "telegram" }, channel);
|
|
11600
|
+
await handleMessage({ text: pending, chatId, source: "telegram", type: "text" }, channel);
|
|
11231
11601
|
}
|
|
11232
11602
|
} else if (data === "perm:deny") {
|
|
11233
11603
|
removePendingEscalation(chatId);
|
|
@@ -11315,8 +11685,9 @@ ${PERM_MODES[chosen]}`,
|
|
|
11315
11685
|
pendingInterrupts.delete(targetChatId);
|
|
11316
11686
|
stopAgent(targetChatId);
|
|
11317
11687
|
await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", "plain");
|
|
11318
|
-
|
|
11319
|
-
|
|
11688
|
+
bypassBusyCheck.add(targetChatId);
|
|
11689
|
+
handleMessage(pending.msg, pending.channel).catch(() => {
|
|
11690
|
+
});
|
|
11320
11691
|
} else if (action === "queue" && pending) {
|
|
11321
11692
|
pendingInterrupts.delete(targetChatId);
|
|
11322
11693
|
bypassBusyCheck.add(targetChatId);
|
|
@@ -11347,6 +11718,60 @@ ${PERM_MODES[chosen]}`,
|
|
|
11347
11718
|
} else {
|
|
11348
11719
|
await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", "plain");
|
|
11349
11720
|
}
|
|
11721
|
+
} else if (data.startsWith("voice:")) {
|
|
11722
|
+
const action = data.slice(6);
|
|
11723
|
+
if (action === "on" || action === "off") {
|
|
11724
|
+
const current = isVoiceEnabled(chatId);
|
|
11725
|
+
const desired = action === "on";
|
|
11726
|
+
if (current !== desired) toggleVoice(chatId);
|
|
11727
|
+
await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", "plain");
|
|
11728
|
+
}
|
|
11729
|
+
} else if (data.startsWith("style:")) {
|
|
11730
|
+
const selectedStyle = data.split(":")[1];
|
|
11731
|
+
if (selectedStyle === "concise" || selectedStyle === "normal" || selectedStyle === "detailed") {
|
|
11732
|
+
setResponseStyle(chatId, selectedStyle);
|
|
11733
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
11734
|
+
await channel.sendKeyboard(chatId, "\u{1F5E3}\uFE0F AI Response Style:", [
|
|
11735
|
+
[
|
|
11736
|
+
{ label: `${selectedStyle === "concise" ? "\u2713 " : ""}Concise`, data: "style:concise" },
|
|
11737
|
+
{ label: `${selectedStyle === "normal" ? "\u2713 " : ""}Normal`, data: "style:normal" },
|
|
11738
|
+
{ label: `${selectedStyle === "detailed" ? "\u2713 " : ""}Detailed`, data: "style:detailed" }
|
|
11739
|
+
]
|
|
11740
|
+
]);
|
|
11741
|
+
}
|
|
11742
|
+
await channel.sendText(chatId, `Response style set to: ${selectedStyle}`, "plain");
|
|
11743
|
+
}
|
|
11744
|
+
} else if (data.startsWith("vcfg:")) {
|
|
11745
|
+
const parts = data.slice(5).split(":");
|
|
11746
|
+
const action = parts[0];
|
|
11747
|
+
if (action === "p") {
|
|
11748
|
+
const provider = parts[1];
|
|
11749
|
+
if (provider === "elevenlabs" || provider === "grok" || provider === "macos") {
|
|
11750
|
+
if (provider === "grok" && !process.env.XAI_API_KEY) {
|
|
11751
|
+
await channel.sendText(
|
|
11752
|
+
chatId,
|
|
11753
|
+
"\u26A0\uFE0F Grok requires `XAI_API_KEY` to be set.\n\nAdd it to your config:\n```\necho 'XAI_API_KEY=your-key-here' >> ~/.cc-claw/.env\ncc-claw service restart\n```\n\nGet a key at: https://console.x.ai/team/default/api-keys\n\nSetting Grok as your provider \u2014 it will activate once the key is added.",
|
|
11754
|
+
"markdown"
|
|
11755
|
+
);
|
|
11756
|
+
}
|
|
11757
|
+
if (provider === "elevenlabs" && !process.env.ELEVENLABS_API_KEY) {
|
|
11758
|
+
await channel.sendText(
|
|
11759
|
+
chatId,
|
|
11760
|
+
"\u26A0\uFE0F ElevenLabs requires `ELEVENLABS_API_KEY` to be set.\n\nAdd it to your config:\n```\necho 'ELEVENLABS_API_KEY=your-key-here' >> ~/.cc-claw/.env\ncc-claw service restart\n```\n\nGet a key at: https://elevenlabs.io/api\n\nSetting ElevenLabs as your provider \u2014 it will activate once the key is added.",
|
|
11761
|
+
"markdown"
|
|
11762
|
+
);
|
|
11763
|
+
}
|
|
11764
|
+
const defaultVoice = provider === "grok" ? "eve" : provider === "macos" ? "Samantha" : "21m00Tcm4TlvDq8ikWAM";
|
|
11765
|
+
setVoiceProvider(chatId, provider, defaultVoice);
|
|
11766
|
+
await sendVoiceConfigKeyboard(chatId, channel);
|
|
11767
|
+
}
|
|
11768
|
+
} else if (action === "v") {
|
|
11769
|
+
const voiceId = parts.slice(1).join(":");
|
|
11770
|
+
const config2 = getVoiceConfig(chatId);
|
|
11771
|
+
setVoiceProvider(chatId, config2.provider, voiceId);
|
|
11772
|
+
const voiceName = config2.provider === "elevenlabs" ? ELEVENLABS_VOICES[voiceId]?.name ?? voiceId : config2.provider === "macos" ? MACOS_VOICES[voiceId]?.name ?? voiceId : voiceId;
|
|
11773
|
+
await channel.sendText(chatId, `\u2705 Voice set to: ${voiceName}`, "plain");
|
|
11774
|
+
}
|
|
11350
11775
|
} else if (data.startsWith("skills:page:")) {
|
|
11351
11776
|
const page = parseInt(data.slice(12), 10);
|
|
11352
11777
|
const skills2 = await discoverAllSkills();
|
|
@@ -11605,7 +12030,7 @@ var init_router = __esm({
|
|
|
11605
12030
|
pendingFallbackMessages = /* @__PURE__ */ new Map();
|
|
11606
12031
|
CLI_INSTALL_HINTS = {
|
|
11607
12032
|
claude: "Install: npm install -g @anthropic-ai/claude-code",
|
|
11608
|
-
gemini: "Install: npm install -g @
|
|
12033
|
+
gemini: "Install: npm install -g @google/gemini-cli",
|
|
11609
12034
|
codex: "Install: npm install -g @openai/codex"
|
|
11610
12035
|
};
|
|
11611
12036
|
BLOCKED_PATH_PATTERNS2 = [
|
|
@@ -11698,17 +12123,17 @@ var init_router = __esm({
|
|
|
11698
12123
|
|
|
11699
12124
|
// src/skills/bootstrap.ts
|
|
11700
12125
|
import { existsSync as existsSync14 } from "fs";
|
|
11701
|
-
import { readdir as readdir3, readFile as readFile6, writeFile as
|
|
11702
|
-
import { join as
|
|
11703
|
-
import { fileURLToPath as
|
|
12126
|
+
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
|
|
12127
|
+
import { join as join14, dirname as dirname2 } from "path";
|
|
12128
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11704
12129
|
async function copyAgentManifestSkills() {
|
|
11705
12130
|
if (!existsSync14(PKG_SKILLS)) return;
|
|
11706
12131
|
try {
|
|
11707
12132
|
const entries = await readdir3(PKG_SKILLS, { withFileTypes: true });
|
|
11708
12133
|
for (const entry of entries) {
|
|
11709
12134
|
if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
|
|
11710
|
-
const src =
|
|
11711
|
-
const dest =
|
|
12135
|
+
const src = join14(PKG_SKILLS, entry.name);
|
|
12136
|
+
const dest = join14(SKILLS_PATH, entry.name);
|
|
11712
12137
|
if (existsSync14(dest)) continue;
|
|
11713
12138
|
await copyFile(src, dest);
|
|
11714
12139
|
log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
|
|
@@ -11719,7 +12144,7 @@ async function copyAgentManifestSkills() {
|
|
|
11719
12144
|
}
|
|
11720
12145
|
async function bootstrapSkills() {
|
|
11721
12146
|
await copyAgentManifestSkills();
|
|
11722
|
-
const usmDir =
|
|
12147
|
+
const usmDir = join14(SKILLS_PATH, USM_DIR_NAME);
|
|
11723
12148
|
if (existsSync14(usmDir)) return;
|
|
11724
12149
|
try {
|
|
11725
12150
|
const entries = await readdir3(SKILLS_PATH);
|
|
@@ -11742,7 +12167,7 @@ async function bootstrapSkills() {
|
|
|
11742
12167
|
}
|
|
11743
12168
|
}
|
|
11744
12169
|
async function patchUsmForCcClaw(usmDir) {
|
|
11745
|
-
const skillPath =
|
|
12170
|
+
const skillPath = join14(usmDir, "SKILL.md");
|
|
11746
12171
|
if (!existsSync14(skillPath)) return;
|
|
11747
12172
|
try {
|
|
11748
12173
|
let content = await readFile6(skillPath, "utf-8");
|
|
@@ -11771,7 +12196,7 @@ async function patchUsmForCcClaw(usmDir) {
|
|
|
11771
12196
|
}
|
|
11772
12197
|
}
|
|
11773
12198
|
if (patched) {
|
|
11774
|
-
await
|
|
12199
|
+
await writeFile4(skillPath, content, "utf-8");
|
|
11775
12200
|
log("[skills] Patched USM SKILL.md with CC-Claw support");
|
|
11776
12201
|
}
|
|
11777
12202
|
} catch (err) {
|
|
@@ -11788,8 +12213,8 @@ var init_bootstrap = __esm({
|
|
|
11788
12213
|
USM_REPO = "jacob-bd/universal-skills-manager";
|
|
11789
12214
|
USM_DIR_NAME = "universal-skills-manager";
|
|
11790
12215
|
CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
|
|
11791
|
-
PKG_ROOT =
|
|
11792
|
-
PKG_SKILLS =
|
|
12216
|
+
PKG_ROOT = join14(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
|
|
12217
|
+
PKG_SKILLS = join14(PKG_ROOT, "skills");
|
|
11793
12218
|
}
|
|
11794
12219
|
});
|
|
11795
12220
|
|
|
@@ -12003,9 +12428,9 @@ __export(ai_skill_exports, {
|
|
|
12003
12428
|
generateAiSkill: () => generateAiSkill,
|
|
12004
12429
|
installAiSkill: () => installAiSkill
|
|
12005
12430
|
});
|
|
12006
|
-
import { existsSync as existsSync15, writeFileSync as
|
|
12007
|
-
import { join as
|
|
12008
|
-
import { homedir as
|
|
12431
|
+
import { existsSync as existsSync15, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
12432
|
+
import { join as join15 } from "path";
|
|
12433
|
+
import { homedir as homedir5 } from "os";
|
|
12009
12434
|
function generateAiSkill() {
|
|
12010
12435
|
const version = VERSION;
|
|
12011
12436
|
let systemState = "";
|
|
@@ -12305,11 +12730,11 @@ function installAiSkill() {
|
|
|
12305
12730
|
const failed = [];
|
|
12306
12731
|
for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
|
|
12307
12732
|
for (const dir of dirs) {
|
|
12308
|
-
const skillDir =
|
|
12309
|
-
const skillPath =
|
|
12733
|
+
const skillDir = join15(dir, "cc-claw-cli");
|
|
12734
|
+
const skillPath = join15(skillDir, "SKILL.md");
|
|
12310
12735
|
try {
|
|
12311
12736
|
mkdirSync6(skillDir, { recursive: true });
|
|
12312
|
-
|
|
12737
|
+
writeFileSync5(skillPath, skill, "utf-8");
|
|
12313
12738
|
installed.push(skillPath);
|
|
12314
12739
|
} catch {
|
|
12315
12740
|
failed.push(skillPath);
|
|
@@ -12325,10 +12750,10 @@ var init_ai_skill = __esm({
|
|
|
12325
12750
|
init_paths();
|
|
12326
12751
|
init_version();
|
|
12327
12752
|
BACKEND_SKILL_DIRS2 = {
|
|
12328
|
-
"cc-claw": [
|
|
12329
|
-
claude: [
|
|
12330
|
-
gemini: [
|
|
12331
|
-
codex: [
|
|
12753
|
+
"cc-claw": [join15(homedir5(), ".cc-claw", "workspace", "skills")],
|
|
12754
|
+
claude: [join15(homedir5(), ".claude", "skills")],
|
|
12755
|
+
gemini: [join15(homedir5(), ".gemini", "skills")],
|
|
12756
|
+
codex: [join15(homedir5(), ".agents", "skills")]
|
|
12332
12757
|
};
|
|
12333
12758
|
}
|
|
12334
12759
|
});
|
|
@@ -12339,17 +12764,17 @@ __export(index_exports, {
|
|
|
12339
12764
|
main: () => main
|
|
12340
12765
|
});
|
|
12341
12766
|
import { mkdirSync as mkdirSync7, existsSync as existsSync16, renameSync, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
|
|
12342
|
-
import { join as
|
|
12767
|
+
import { join as join16 } from "path";
|
|
12343
12768
|
import dotenv from "dotenv";
|
|
12344
12769
|
function migrateLayout() {
|
|
12345
12770
|
const moves = [
|
|
12346
|
-
[
|
|
12347
|
-
[
|
|
12348
|
-
[
|
|
12349
|
-
[
|
|
12350
|
-
[
|
|
12351
|
-
[
|
|
12352
|
-
[
|
|
12771
|
+
[join16(CC_CLAW_HOME, "cc-claw.db"), join16(DATA_PATH, "cc-claw.db")],
|
|
12772
|
+
[join16(CC_CLAW_HOME, "cc-claw.db-shm"), join16(DATA_PATH, "cc-claw.db-shm")],
|
|
12773
|
+
[join16(CC_CLAW_HOME, "cc-claw.db-wal"), join16(DATA_PATH, "cc-claw.db-wal")],
|
|
12774
|
+
[join16(CC_CLAW_HOME, "cc-claw.log"), join16(LOGS_PATH, "cc-claw.log")],
|
|
12775
|
+
[join16(CC_CLAW_HOME, "cc-claw.log.1"), join16(LOGS_PATH, "cc-claw.log.1")],
|
|
12776
|
+
[join16(CC_CLAW_HOME, "cc-claw.error.log"), join16(LOGS_PATH, "cc-claw.error.log")],
|
|
12777
|
+
[join16(CC_CLAW_HOME, "cc-claw.error.log.1"), join16(LOGS_PATH, "cc-claw.error.log.1")]
|
|
12353
12778
|
];
|
|
12354
12779
|
for (const [from, to] of moves) {
|
|
12355
12780
|
if (existsSync16(from) && !existsSync16(to)) {
|
|
@@ -12454,11 +12879,11 @@ async function main() {
|
|
|
12454
12879
|
bootstrapSkills().catch((err) => error("[cc-claw] Skill bootstrap failed:", err));
|
|
12455
12880
|
try {
|
|
12456
12881
|
const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
|
|
12457
|
-
const { writeFileSync:
|
|
12458
|
-
const { join:
|
|
12459
|
-
const skillDir =
|
|
12882
|
+
const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync11 } = await import("fs");
|
|
12883
|
+
const { join: join19 } = await import("path");
|
|
12884
|
+
const skillDir = join19(SKILLS_PATH, "cc-claw-cli");
|
|
12460
12885
|
mkdirSync11(skillDir, { recursive: true });
|
|
12461
|
-
|
|
12886
|
+
writeFileSync8(join19(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
|
|
12462
12887
|
log("[cc-claw] AI skill updated");
|
|
12463
12888
|
} catch {
|
|
12464
12889
|
}
|
|
@@ -12484,7 +12909,13 @@ async function main() {
|
|
|
12484
12909
|
stopMonitor();
|
|
12485
12910
|
shutdownOrchestrator();
|
|
12486
12911
|
shutdownScheduler();
|
|
12487
|
-
await
|
|
12912
|
+
await Promise.race([
|
|
12913
|
+
summarizeAllPending(),
|
|
12914
|
+
new Promise((resolve) => setTimeout(() => {
|
|
12915
|
+
log("[cc-claw] Summarization timed out after 15s, skipping.");
|
|
12916
|
+
resolve();
|
|
12917
|
+
}, 15e3))
|
|
12918
|
+
]);
|
|
12488
12919
|
await channelRegistry.stopAll();
|
|
12489
12920
|
log("[cc-claw] Clean shutdown complete.");
|
|
12490
12921
|
} catch (err) {
|
|
@@ -12542,10 +12973,10 @@ __export(service_exports, {
|
|
|
12542
12973
|
serviceStatus: () => serviceStatus,
|
|
12543
12974
|
uninstallService: () => uninstallService
|
|
12544
12975
|
});
|
|
12545
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as
|
|
12976
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
12546
12977
|
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
12547
|
-
import { homedir as
|
|
12548
|
-
import { join as
|
|
12978
|
+
import { homedir as homedir6, platform } from "os";
|
|
12979
|
+
import { join as join17, dirname as dirname3 } from "path";
|
|
12549
12980
|
function resolveExecutable2(name) {
|
|
12550
12981
|
try {
|
|
12551
12982
|
return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
|
|
@@ -12554,18 +12985,18 @@ function resolveExecutable2(name) {
|
|
|
12554
12985
|
}
|
|
12555
12986
|
}
|
|
12556
12987
|
function getPathDirs() {
|
|
12557
|
-
const nodeBin =
|
|
12558
|
-
const home =
|
|
12988
|
+
const nodeBin = dirname3(process.execPath);
|
|
12989
|
+
const home = homedir6();
|
|
12559
12990
|
const dirs = /* @__PURE__ */ new Set([
|
|
12560
12991
|
nodeBin,
|
|
12561
|
-
|
|
12992
|
+
join17(home, ".local", "bin"),
|
|
12562
12993
|
"/usr/local/bin",
|
|
12563
12994
|
"/usr/bin",
|
|
12564
12995
|
"/bin"
|
|
12565
12996
|
]);
|
|
12566
12997
|
try {
|
|
12567
12998
|
const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
|
|
12568
|
-
if (prefix) dirs.add(
|
|
12999
|
+
if (prefix) dirs.add(join17(prefix, "bin"));
|
|
12569
13000
|
} catch {
|
|
12570
13001
|
}
|
|
12571
13002
|
return [...dirs].join(":");
|
|
@@ -12573,7 +13004,7 @@ function getPathDirs() {
|
|
|
12573
13004
|
function generatePlist() {
|
|
12574
13005
|
const ccClawBin = resolveExecutable2("cc-claw");
|
|
12575
13006
|
const pathDirs = getPathDirs();
|
|
12576
|
-
const home =
|
|
13007
|
+
const home = homedir6();
|
|
12577
13008
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12578
13009
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
12579
13010
|
<plist version="1.0">
|
|
@@ -12619,7 +13050,7 @@ function generatePlist() {
|
|
|
12619
13050
|
</plist>`;
|
|
12620
13051
|
}
|
|
12621
13052
|
function installMacOS() {
|
|
12622
|
-
const agentsDir =
|
|
13053
|
+
const agentsDir = dirname3(PLIST_PATH);
|
|
12623
13054
|
if (!existsSync17(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
|
|
12624
13055
|
if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
|
|
12625
13056
|
if (existsSync17(PLIST_PATH)) {
|
|
@@ -12628,7 +13059,7 @@ function installMacOS() {
|
|
|
12628
13059
|
} catch {
|
|
12629
13060
|
}
|
|
12630
13061
|
}
|
|
12631
|
-
|
|
13062
|
+
writeFileSync6(PLIST_PATH, generatePlist());
|
|
12632
13063
|
console.log(` Installed: ${PLIST_PATH}`);
|
|
12633
13064
|
execFileSync2("launchctl", ["load", PLIST_PATH]);
|
|
12634
13065
|
console.log(" Service loaded and starting.");
|
|
@@ -12678,7 +13109,7 @@ Restart=on-failure
|
|
|
12678
13109
|
RestartSec=10
|
|
12679
13110
|
WorkingDirectory=${CC_CLAW_HOME}
|
|
12680
13111
|
Environment=PATH=${pathDirs}
|
|
12681
|
-
Environment=HOME=${
|
|
13112
|
+
Environment=HOME=${homedir6()}
|
|
12682
13113
|
|
|
12683
13114
|
[Install]
|
|
12684
13115
|
WantedBy=default.target
|
|
@@ -12687,7 +13118,7 @@ WantedBy=default.target
|
|
|
12687
13118
|
function installLinux() {
|
|
12688
13119
|
if (!existsSync17(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
|
|
12689
13120
|
if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
|
|
12690
|
-
|
|
13121
|
+
writeFileSync6(UNIT_PATH, generateUnit());
|
|
12691
13122
|
console.log(` Installed: ${UNIT_PATH}`);
|
|
12692
13123
|
execFileSync2("systemctl", ["--user", "daemon-reload"]);
|
|
12693
13124
|
execFileSync2("systemctl", ["--user", "enable", "cc-claw"]);
|
|
@@ -12720,7 +13151,7 @@ function statusLinux() {
|
|
|
12720
13151
|
}
|
|
12721
13152
|
}
|
|
12722
13153
|
function installService() {
|
|
12723
|
-
if (!existsSync17(
|
|
13154
|
+
if (!existsSync17(join17(CC_CLAW_HOME, ".env"))) {
|
|
12724
13155
|
console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
|
|
12725
13156
|
console.error(" Run 'cc-claw setup' before installing the service.");
|
|
12726
13157
|
process.exitCode = 1;
|
|
@@ -12749,9 +13180,9 @@ var init_service = __esm({
|
|
|
12749
13180
|
"use strict";
|
|
12750
13181
|
init_paths();
|
|
12751
13182
|
PLIST_LABEL = "com.cc-claw";
|
|
12752
|
-
PLIST_PATH =
|
|
12753
|
-
SYSTEMD_DIR =
|
|
12754
|
-
UNIT_PATH =
|
|
13183
|
+
PLIST_PATH = join17(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
13184
|
+
SYSTEMD_DIR = join17(homedir6(), ".config", "systemd", "user");
|
|
13185
|
+
UNIT_PATH = join17(SYSTEMD_DIR, "cc-claw.service");
|
|
12755
13186
|
}
|
|
12756
13187
|
});
|
|
12757
13188
|
|
|
@@ -12975,7 +13406,7 @@ async function apiGet(path) {
|
|
|
12975
13406
|
const req = httpRequest(url, {
|
|
12976
13407
|
method: "GET",
|
|
12977
13408
|
headers: token ? { "Authorization": `Bearer ${token}` } : {},
|
|
12978
|
-
timeout:
|
|
13409
|
+
timeout: 3e3
|
|
12979
13410
|
}, (res) => {
|
|
12980
13411
|
const chunks = [];
|
|
12981
13412
|
res.on("data", (c) => chunks.push(c));
|
|
@@ -13412,15 +13843,14 @@ async function logsCommand(opts) {
|
|
|
13412
13843
|
console.log(tailLines.join("\n"));
|
|
13413
13844
|
if (opts.follow) {
|
|
13414
13845
|
console.log(muted("\n Following... (Ctrl+C to stop)\n"));
|
|
13415
|
-
let
|
|
13846
|
+
let lastLength = content.length;
|
|
13416
13847
|
watchFile2(logFile, { interval: 500 }, () => {
|
|
13417
13848
|
try {
|
|
13418
13849
|
const newContent = readFileSync14(logFile, "utf-8");
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
const newPart = newContent.slice(content.length);
|
|
13850
|
+
if (newContent.length > lastLength) {
|
|
13851
|
+
const newPart = newContent.slice(lastLength);
|
|
13422
13852
|
process.stdout.write(newPart);
|
|
13423
|
-
|
|
13853
|
+
lastLength = newContent.length;
|
|
13424
13854
|
}
|
|
13425
13855
|
} catch {
|
|
13426
13856
|
}
|
|
@@ -13893,8 +14323,17 @@ async function cronEdit(globalOpts, id, opts) {
|
|
|
13893
14323
|
values.push(opts.at);
|
|
13894
14324
|
}
|
|
13895
14325
|
if (opts.every) {
|
|
13896
|
-
|
|
13897
|
-
|
|
14326
|
+
const m = opts.every.match(/^(\d+)\s*(m|min|h|hr|s|sec)$/i);
|
|
14327
|
+
if (m) {
|
|
14328
|
+
const num = parseInt(m[1], 10);
|
|
14329
|
+
const unit = m[2].toLowerCase();
|
|
14330
|
+
const ms = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
|
|
14331
|
+
updates.push("every_ms = ?, schedule_type = 'every'");
|
|
14332
|
+
values.push(ms);
|
|
14333
|
+
} else {
|
|
14334
|
+
updates.push("every_ms = ?, schedule_type = 'every'");
|
|
14335
|
+
values.push(parseInt(opts.every, 10) || 0);
|
|
14336
|
+
}
|
|
13898
14337
|
}
|
|
13899
14338
|
if (opts.backend) {
|
|
13900
14339
|
updates.push("backend = ?");
|
|
@@ -14146,7 +14585,7 @@ __export(db_exports, {
|
|
|
14146
14585
|
dbStats: () => dbStats
|
|
14147
14586
|
});
|
|
14148
14587
|
import { existsSync as existsSync27, statSync as statSync5, copyFileSync, mkdirSync as mkdirSync9 } from "fs";
|
|
14149
|
-
import { dirname as
|
|
14588
|
+
import { dirname as dirname4 } from "path";
|
|
14150
14589
|
async function dbStats(globalOpts) {
|
|
14151
14590
|
if (!existsSync27(DB_PATH)) {
|
|
14152
14591
|
outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
|
|
@@ -14196,7 +14635,7 @@ async function dbBackup(globalOpts, destPath) {
|
|
|
14196
14635
|
}
|
|
14197
14636
|
const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
14198
14637
|
try {
|
|
14199
|
-
mkdirSync9(
|
|
14638
|
+
mkdirSync9(dirname4(dest), { recursive: true });
|
|
14200
14639
|
copyFileSync(DB_PATH, dest);
|
|
14201
14640
|
const walPath = DB_PATH + "-wal";
|
|
14202
14641
|
if (existsSync27(walPath)) copyFileSync(walPath, dest + "-wal");
|
|
@@ -14533,7 +14972,7 @@ var init_config = __esm({
|
|
|
14533
14972
|
init_format();
|
|
14534
14973
|
init_paths();
|
|
14535
14974
|
init_resolve_chat();
|
|
14536
|
-
RUNTIME_KEYS = ["backend", "model", "thinking", "summarizer", "mode", "verbose", "cwd", "voice"];
|
|
14975
|
+
RUNTIME_KEYS = ["backend", "model", "thinking", "summarizer", "mode", "verbose", "cwd", "voice", "response-style"];
|
|
14537
14976
|
KEY_TABLE_MAP = {
|
|
14538
14977
|
backend: { table: "chat_backend", col: "backend" },
|
|
14539
14978
|
model: { table: "chat_model", col: "model" },
|
|
@@ -14542,7 +14981,8 @@ var init_config = __esm({
|
|
|
14542
14981
|
mode: { table: "chat_mode", col: "mode" },
|
|
14543
14982
|
verbose: { table: "chat_verbose", col: "level" },
|
|
14544
14983
|
cwd: { table: "chat_cwd", col: "cwd" },
|
|
14545
|
-
voice: { table: "chat_voice", col: "enabled" }
|
|
14984
|
+
voice: { table: "chat_voice", col: "enabled" },
|
|
14985
|
+
"response-style": { table: "chat_response_style", col: "style" }
|
|
14546
14986
|
};
|
|
14547
14987
|
ALLOWED_TABLES = new Set(Object.values(KEY_TABLE_MAP).map((v) => v.table));
|
|
14548
14988
|
ALLOWED_COLS = new Set(Object.values(KEY_TABLE_MAP).map((v) => v.col));
|
|
@@ -15567,7 +16007,7 @@ var init_completion = __esm({
|
|
|
15567
16007
|
model: ["list", "get", "set"],
|
|
15568
16008
|
thinking: ["get", "set"],
|
|
15569
16009
|
summarizer: ["get", "set"],
|
|
15570
|
-
memory: ["list", "search", "
|
|
16010
|
+
memory: ["list", "search", "history"],
|
|
15571
16011
|
cron: ["list", "create", "edit", "cancel", "pause", "resume", "run", "runs", "health"],
|
|
15572
16012
|
schedule: ["list", "create", "edit", "cancel", "pause", "resume", "run", "runs", "health"],
|
|
15573
16013
|
agents: ["list", "spawn", "cancel", "cancel-all"],
|
|
@@ -15585,7 +16025,7 @@ var init_completion = __esm({
|
|
|
15585
16025
|
chats: ["list", "alias", "remove-alias"],
|
|
15586
16026
|
skills: ["list", "install"],
|
|
15587
16027
|
mcps: ["list"],
|
|
15588
|
-
db: ["stats", "path", "backup"
|
|
16028
|
+
db: ["stats", "path", "backup"],
|
|
15589
16029
|
chat: ["send", "stop"]
|
|
15590
16030
|
};
|
|
15591
16031
|
}
|
|
@@ -15593,10 +16033,10 @@ var init_completion = __esm({
|
|
|
15593
16033
|
|
|
15594
16034
|
// src/setup.ts
|
|
15595
16035
|
var setup_exports = {};
|
|
15596
|
-
import { existsSync as existsSync38, writeFileSync as
|
|
16036
|
+
import { existsSync as existsSync38, writeFileSync as writeFileSync7, readFileSync as readFileSync17, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
|
|
15597
16037
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
15598
16038
|
import { createInterface as createInterface5 } from "readline";
|
|
15599
|
-
import { join as
|
|
16039
|
+
import { join as join18 } from "path";
|
|
15600
16040
|
function divider2() {
|
|
15601
16041
|
console.log(dim("\u2500".repeat(55)));
|
|
15602
16042
|
}
|
|
@@ -15683,7 +16123,7 @@ async function setup() {
|
|
|
15683
16123
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
15684
16124
|
}
|
|
15685
16125
|
}
|
|
15686
|
-
const cwdDb =
|
|
16126
|
+
const cwdDb = join18(process.cwd(), "cc-claw.db");
|
|
15687
16127
|
if (existsSync38(cwdDb) && !existsSync38(DB_PATH)) {
|
|
15688
16128
|
const { size } = statSync6(cwdDb);
|
|
15689
16129
|
console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
|
|
@@ -15827,11 +16267,27 @@ async function setup() {
|
|
|
15827
16267
|
env.GROQ_API_KEY = groqKey;
|
|
15828
16268
|
console.log(green(" Voice transcription (STT) enabled!"));
|
|
15829
16269
|
console.log("");
|
|
15830
|
-
|
|
16270
|
+
console.log(dim(" Choose a voice reply provider:"));
|
|
16271
|
+
console.log(" 1. ElevenLabs (high-quality, requires API key)");
|
|
16272
|
+
console.log(" 2. Grok / xAI (high-quality, requires API key)");
|
|
16273
|
+
console.log(" 3. macOS (free, uses built-in voices \u2014 Samantha, Albert)");
|
|
16274
|
+
console.log(" 4. Skip voice replies");
|
|
16275
|
+
console.log("");
|
|
16276
|
+
const ttsChoice = await requiredInput("Enter choice (1/2/3/4)", "4");
|
|
16277
|
+
if (ttsChoice === "1") {
|
|
15831
16278
|
console.log(dim(" Get an ElevenLabs key at: https://elevenlabs.io/api\n"));
|
|
15832
16279
|
const elevenKey = await requiredInput("ElevenLabs API key", env.ELEVENLABS_API_KEY);
|
|
15833
16280
|
env.ELEVENLABS_API_KEY = elevenKey;
|
|
15834
|
-
console.log(green(" Voice replies
|
|
16281
|
+
console.log(green(" Voice replies via ElevenLabs enabled!"));
|
|
16282
|
+
} else if (ttsChoice === "2") {
|
|
16283
|
+
console.log(dim(" Get an xAI key at: https://console.x.ai/team/default/api-keys\n"));
|
|
16284
|
+
const xaiKey = await requiredInput("xAI API key", env.XAI_API_KEY);
|
|
16285
|
+
env.XAI_API_KEY = xaiKey;
|
|
16286
|
+
console.log(green(" Voice replies via Grok enabled!"));
|
|
16287
|
+
console.log(dim(" Use /voice_config in Telegram to select a voice (Eve, Ara, Rex, Sal, Leo)."));
|
|
16288
|
+
} else if (ttsChoice === "3") {
|
|
16289
|
+
console.log(green(" Voice replies via macOS enabled!"));
|
|
16290
|
+
console.log(dim(" Use /voice_config in Telegram to select a voice (Samantha or Albert)."));
|
|
15835
16291
|
} else {
|
|
15836
16292
|
console.log(dim(" Voice replies will use macOS text-to-speech as fallback."));
|
|
15837
16293
|
}
|
|
@@ -15870,6 +16326,9 @@ async function setup() {
|
|
|
15870
16326
|
if (env.ELEVENLABS_API_KEY) {
|
|
15871
16327
|
envLines.push(`ELEVENLABS_API_KEY=${env.ELEVENLABS_API_KEY}`);
|
|
15872
16328
|
}
|
|
16329
|
+
if (env.XAI_API_KEY) {
|
|
16330
|
+
envLines.push(`XAI_API_KEY=${env.XAI_API_KEY}`);
|
|
16331
|
+
}
|
|
15873
16332
|
}
|
|
15874
16333
|
if (env.DASHBOARD_ENABLED) {
|
|
15875
16334
|
envLines.push("", "# Dashboard", `DASHBOARD_ENABLED=${env.DASHBOARD_ENABLED}`);
|
|
@@ -15878,7 +16337,7 @@ async function setup() {
|
|
|
15878
16337
|
envLines.push("", "# Video Analysis", `GEMINI_API_KEY=${env.GEMINI_API_KEY}`);
|
|
15879
16338
|
}
|
|
15880
16339
|
const envContent = envLines.join("\n") + "\n";
|
|
15881
|
-
|
|
16340
|
+
writeFileSync7(ENV_PATH, envContent, { mode: 384 });
|
|
15882
16341
|
console.log(green(` Config saved to ${ENV_PATH} (permissions: owner-only)`));
|
|
15883
16342
|
header(6, TOTAL_STEPS, "Run on Startup (Daemon)");
|
|
15884
16343
|
console.log(" CC-Claw can run automatically in the background, starting");
|
|
@@ -16161,6 +16620,18 @@ config.command("set <key> <value>").description("Set a runtime config value (via
|
|
|
16161
16620
|
const { configSet: configSet2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
16162
16621
|
await configSet2(program.opts(), key, value);
|
|
16163
16622
|
});
|
|
16623
|
+
config.command("response-style [style]").description("Get or set the AI response style (concise/normal/detailed)").action(async (style) => {
|
|
16624
|
+
const { configGet: configGet2, configSet: configSet2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
16625
|
+
if (style) {
|
|
16626
|
+
if (!["concise", "normal", "detailed"].includes(style)) {
|
|
16627
|
+
console.error("Invalid style. Must be concise, normal, or detailed.");
|
|
16628
|
+
process.exit(1);
|
|
16629
|
+
}
|
|
16630
|
+
await configSet2(program.opts(), "response-style", style);
|
|
16631
|
+
} else {
|
|
16632
|
+
await configGet2(program.opts(), "response-style");
|
|
16633
|
+
}
|
|
16634
|
+
});
|
|
16164
16635
|
config.command("env").description("Print .env path and values (static config, redacted secrets)").action(async () => {
|
|
16165
16636
|
const { configEnv: configEnv2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
16166
16637
|
await configEnv2(program.opts());
|