cc-claw 0.2.8 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +556 -55
- 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.
|
|
51
|
+
VERSION = true ? "0.3.1" : (() => {
|
|
52
52
|
try {
|
|
53
53
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
54
54
|
} catch {
|
|
@@ -864,10 +864,13 @@ __export(store_exports2, {
|
|
|
864
864
|
clearThinkingLevel: () => clearThinkingLevel,
|
|
865
865
|
clearUsage: () => clearUsage,
|
|
866
866
|
completeJobRun: () => completeJobRun,
|
|
867
|
+
deleteBookmark: () => deleteBookmark,
|
|
868
|
+
findBookmarksByPrefix: () => findBookmarksByPrefix,
|
|
867
869
|
forgetMemory: () => forgetMemory,
|
|
868
870
|
getActiveJobs: () => getActiveJobs,
|
|
869
871
|
getActiveWatches: () => getActiveWatches,
|
|
870
872
|
getAllBackendLimits: () => getAllBackendLimits,
|
|
873
|
+
getAllBookmarks: () => getAllBookmarks,
|
|
871
874
|
getAllChatAliases: () => getAllChatAliases,
|
|
872
875
|
getAllJobs: () => getAllJobs,
|
|
873
876
|
getAllMemoriesWithEmbeddings: () => getAllMemoriesWithEmbeddings,
|
|
@@ -876,6 +879,7 @@ __export(store_exports2, {
|
|
|
876
879
|
getBackend: () => getBackend,
|
|
877
880
|
getBackendLimit: () => getBackendLimit,
|
|
878
881
|
getBackendUsageInWindow: () => getBackendUsageInWindow,
|
|
882
|
+
getBookmark: () => getBookmark,
|
|
879
883
|
getChatIdByAlias: () => getChatIdByAlias,
|
|
880
884
|
getChatUsageByModel: () => getChatUsageByModel,
|
|
881
885
|
getCwd: () => getCwd,
|
|
@@ -887,6 +891,7 @@ __export(store_exports2, {
|
|
|
887
891
|
getMemoriesWithoutEmbeddings: () => getMemoriesWithoutEmbeddings,
|
|
888
892
|
getMode: () => getMode,
|
|
889
893
|
getModel: () => getModel,
|
|
894
|
+
getRecentBookmarks: () => getRecentBookmarks,
|
|
890
895
|
getRecentMemories: () => getRecentMemories,
|
|
891
896
|
getSessionId: () => getSessionId,
|
|
892
897
|
getSessionStartedAt: () => getSessionStartedAt,
|
|
@@ -929,13 +934,15 @@ __export(store_exports2, {
|
|
|
929
934
|
setThinkingLevel: () => setThinkingLevel,
|
|
930
935
|
setVerboseLevel: () => setVerboseLevel,
|
|
931
936
|
toggleTool: () => toggleTool,
|
|
937
|
+
touchBookmark: () => touchBookmark,
|
|
932
938
|
updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
|
|
933
939
|
updateJob: () => updateJob,
|
|
934
940
|
updateJobEnabled: () => updateJobEnabled,
|
|
935
941
|
updateJobLastRun: () => updateJobLastRun,
|
|
936
942
|
updateJobNextRun: () => updateJobNextRun,
|
|
937
943
|
updateMemoryEmbedding: () => updateMemoryEmbedding,
|
|
938
|
-
updateSessionSummaryEmbedding: () => updateSessionSummaryEmbedding
|
|
944
|
+
updateSessionSummaryEmbedding: () => updateSessionSummaryEmbedding,
|
|
945
|
+
upsertBookmark: () => upsertBookmark
|
|
939
946
|
});
|
|
940
947
|
import Database from "better-sqlite3";
|
|
941
948
|
function openDatabaseReadOnly() {
|
|
@@ -1297,6 +1304,16 @@ function initDatabase() {
|
|
|
1297
1304
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1298
1305
|
);
|
|
1299
1306
|
`);
|
|
1307
|
+
db.exec(`
|
|
1308
|
+
CREATE TABLE IF NOT EXISTS cwd_bookmarks (
|
|
1309
|
+
chatId TEXT NOT NULL,
|
|
1310
|
+
alias TEXT NOT NULL,
|
|
1311
|
+
path TEXT NOT NULL,
|
|
1312
|
+
manual INTEGER NOT NULL DEFAULT 0,
|
|
1313
|
+
lastUsed INTEGER NOT NULL DEFAULT 0,
|
|
1314
|
+
PRIMARY KEY (chatId, alias)
|
|
1315
|
+
)
|
|
1316
|
+
`);
|
|
1300
1317
|
try {
|
|
1301
1318
|
db.exec("ALTER TABLE memories ADD COLUMN embedding TEXT");
|
|
1302
1319
|
} catch {
|
|
@@ -1491,6 +1508,58 @@ function setCwd(chatId, cwd) {
|
|
|
1491
1508
|
function clearCwd(chatId) {
|
|
1492
1509
|
db.prepare("DELETE FROM chat_cwd WHERE chat_id = ?").run(chatId);
|
|
1493
1510
|
}
|
|
1511
|
+
function upsertBookmark(chatId, alias, path, manual) {
|
|
1512
|
+
const now = Date.now();
|
|
1513
|
+
const existing = db.prepare(
|
|
1514
|
+
"SELECT manual FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1515
|
+
).get(chatId, alias);
|
|
1516
|
+
if (existing) {
|
|
1517
|
+
if (existing.manual === 1 && !manual) {
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
db.prepare(
|
|
1521
|
+
"UPDATE cwd_bookmarks SET path = ?, manual = ?, lastUsed = ? WHERE chatId = ? AND alias = ?"
|
|
1522
|
+
).run(path, manual ? 1 : 0, now, chatId, alias);
|
|
1523
|
+
} else {
|
|
1524
|
+
db.prepare(
|
|
1525
|
+
"INSERT INTO cwd_bookmarks (chatId, alias, path, manual, lastUsed) VALUES (?, ?, ?, ?, ?)"
|
|
1526
|
+
).run(chatId, alias, path, manual ? 1 : 0, now);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function touchBookmark(chatId, alias) {
|
|
1530
|
+
db.prepare(
|
|
1531
|
+
"UPDATE cwd_bookmarks SET lastUsed = ? WHERE chatId = ? AND alias = ?"
|
|
1532
|
+
).run(Date.now(), chatId, alias);
|
|
1533
|
+
}
|
|
1534
|
+
function getBookmark(chatId, alias) {
|
|
1535
|
+
const row = db.prepare(
|
|
1536
|
+
"SELECT alias, path, manual FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1537
|
+
).get(chatId, alias);
|
|
1538
|
+
return row ? { alias: row.alias, path: row.path, manual: row.manual === 1 } : void 0;
|
|
1539
|
+
}
|
|
1540
|
+
function getRecentBookmarks(chatId, limit = 10) {
|
|
1541
|
+
return db.prepare(
|
|
1542
|
+
"SELECT alias, path FROM cwd_bookmarks WHERE chatId = ? ORDER BY lastUsed DESC LIMIT ?"
|
|
1543
|
+
).all(chatId, limit);
|
|
1544
|
+
}
|
|
1545
|
+
function findBookmarksByPrefix(chatId, prefix) {
|
|
1546
|
+
return db.prepare(
|
|
1547
|
+
"SELECT alias, path FROM cwd_bookmarks WHERE chatId = ? AND alias LIKE ? || '%'"
|
|
1548
|
+
).all(chatId, prefix);
|
|
1549
|
+
}
|
|
1550
|
+
function deleteBookmark(chatId, alias) {
|
|
1551
|
+
const result = db.prepare(
|
|
1552
|
+
"DELETE FROM cwd_bookmarks WHERE chatId = ? AND alias = ?"
|
|
1553
|
+
).run(chatId, alias);
|
|
1554
|
+
return result.changes > 0;
|
|
1555
|
+
}
|
|
1556
|
+
function getAllBookmarks(chatId) {
|
|
1557
|
+
return db.prepare(
|
|
1558
|
+
"SELECT alias, path, manual FROM cwd_bookmarks WHERE chatId = ? ORDER BY alias"
|
|
1559
|
+
).all(chatId).map(
|
|
1560
|
+
(r) => ({ alias: r.alias, path: r.path, manual: r.manual === 1 })
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1494
1563
|
function getModel(chatId) {
|
|
1495
1564
|
const row = db.prepare(
|
|
1496
1565
|
"SELECT model FROM chat_model WHERE chat_id = ?"
|
|
@@ -2199,13 +2268,13 @@ var init_claude = __esm({
|
|
|
2199
2268
|
displayName = "Claude";
|
|
2200
2269
|
availableModels = {
|
|
2201
2270
|
"claude-opus-4-6": {
|
|
2202
|
-
label: "Opus 4.6 \u2014 most capable",
|
|
2271
|
+
label: "Opus 4.6 \u2014 most capable, 1M context",
|
|
2203
2272
|
thinking: "adjustable",
|
|
2204
2273
|
thinkingLevels: ["auto", "off", "low", "medium", "high"],
|
|
2205
2274
|
defaultThinkingLevel: "medium"
|
|
2206
2275
|
},
|
|
2207
2276
|
"claude-sonnet-4-6": {
|
|
2208
|
-
label: "Sonnet 4.6 \u2014 balanced (default)",
|
|
2277
|
+
label: "Sonnet 4.6 \u2014 balanced (default), 1M context",
|
|
2209
2278
|
thinking: "adjustable",
|
|
2210
2279
|
thinkingLevels: ["auto", "off", "low", "medium", "high"],
|
|
2211
2280
|
defaultThinkingLevel: "medium"
|
|
@@ -2225,8 +2294,8 @@ var init_claude = __esm({
|
|
|
2225
2294
|
"claude-haiku-4-5": { in: 0.8, out: 4, cache: 0.08 }
|
|
2226
2295
|
};
|
|
2227
2296
|
contextWindow = {
|
|
2228
|
-
"claude-opus-4-6":
|
|
2229
|
-
"claude-sonnet-4-6":
|
|
2297
|
+
"claude-opus-4-6": 1e6,
|
|
2298
|
+
"claude-sonnet-4-6": 1e6,
|
|
2230
2299
|
"claude-haiku-4-5": 2e5
|
|
2231
2300
|
};
|
|
2232
2301
|
_resolvedPath = "";
|
|
@@ -4035,6 +4104,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
4035
4104
|
}
|
|
4036
4105
|
let mcpExtraArgs = [];
|
|
4037
4106
|
let orchestratorMcpName = "";
|
|
4107
|
+
if (process.env.DASHBOARD_ENABLED !== "1") {
|
|
4108
|
+
warn(`[orchestrator] DASHBOARD_ENABLED is not set \u2014 agent ${agentId.slice(0, 8)} will NOT have orchestrator MCP tools (set_state, send_message, etc.)`);
|
|
4109
|
+
}
|
|
4038
4110
|
if (process.env.DASHBOARD_ENABLED === "1") {
|
|
4039
4111
|
try {
|
|
4040
4112
|
const { createSubAgentToken: createSubAgentToken2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
@@ -4054,6 +4126,9 @@ async function startAgent(agentId, chatId, opts) {
|
|
|
4054
4126
|
});
|
|
4055
4127
|
mcpsAdded = [...mcpsAdded, orchestratorMcpName];
|
|
4056
4128
|
updateAgentMcpsAdded(db3, agentId, mcpsAdded);
|
|
4129
|
+
if (runner.id === "gemini") {
|
|
4130
|
+
mcpExtraArgs = [`--allowed-mcp-server-names=${orchestratorMcpName}`];
|
|
4131
|
+
}
|
|
4057
4132
|
log(`[orchestrator] Injected cc-claw MCP via add-remove for agent ${agentId.slice(0, 8)}`);
|
|
4058
4133
|
}
|
|
4059
4134
|
} catch (err) {
|
|
@@ -8278,6 +8353,180 @@ var init_video = __esm({
|
|
|
8278
8353
|
}
|
|
8279
8354
|
});
|
|
8280
8355
|
|
|
8356
|
+
// src/shell/guard.ts
|
|
8357
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8358
|
+
function isDestructive(command) {
|
|
8359
|
+
return DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command));
|
|
8360
|
+
}
|
|
8361
|
+
function storePendingCommand(command, chatId, raw) {
|
|
8362
|
+
const id = randomUUID2().slice(0, 8);
|
|
8363
|
+
pendingCommands.set(id, { command, chatId, raw, createdAt: Date.now() });
|
|
8364
|
+
setTimeout(() => pendingCommands.delete(id), PENDING_TTL_MS);
|
|
8365
|
+
return id;
|
|
8366
|
+
}
|
|
8367
|
+
function getPendingCommand(id) {
|
|
8368
|
+
return pendingCommands.get(id);
|
|
8369
|
+
}
|
|
8370
|
+
function removePendingCommand(id) {
|
|
8371
|
+
return pendingCommands.delete(id);
|
|
8372
|
+
}
|
|
8373
|
+
var DESTRUCTIVE_PATTERNS, pendingCommands, PENDING_TTL_MS;
|
|
8374
|
+
var init_guard = __esm({
|
|
8375
|
+
"src/shell/guard.ts"() {
|
|
8376
|
+
"use strict";
|
|
8377
|
+
DESTRUCTIVE_PATTERNS = [
|
|
8378
|
+
/\brm\s+(-[a-zA-Z]*[fr][a-zA-Z]*|-[a-zA-Z]*[rf][a-zA-Z]*|--force|--recursive)\b/,
|
|
8379
|
+
/\brm\s+-[a-zA-Z]*\s+\/\s*$/,
|
|
8380
|
+
/\bmkfs\b/,
|
|
8381
|
+
/\bdd\b.*\bof=/,
|
|
8382
|
+
/\b(shutdown|reboot|halt|poweroff)\b/,
|
|
8383
|
+
/>\s*\/dev\/sd/,
|
|
8384
|
+
/\bchmod\s+-R\s+777\b/,
|
|
8385
|
+
/\bchown\s+-R\b/,
|
|
8386
|
+
/\bkillall\b/,
|
|
8387
|
+
/\blaunchctl\s+(unload|remove)\b/,
|
|
8388
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:/
|
|
8389
|
+
];
|
|
8390
|
+
pendingCommands = /* @__PURE__ */ new Map();
|
|
8391
|
+
PENDING_TTL_MS = 5 * 60 * 1e3;
|
|
8392
|
+
}
|
|
8393
|
+
});
|
|
8394
|
+
|
|
8395
|
+
// src/shell/exec.ts
|
|
8396
|
+
import { execFile as execFile3 } from "child_process";
|
|
8397
|
+
function executeShell(command, cwd, timeoutMs = SHELL_TIMEOUT_MS) {
|
|
8398
|
+
return new Promise((resolve) => {
|
|
8399
|
+
execFile3(
|
|
8400
|
+
"/bin/zsh",
|
|
8401
|
+
["-c", command],
|
|
8402
|
+
{ cwd, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 },
|
|
8403
|
+
(error3, stdout, stderr) => {
|
|
8404
|
+
const output2 = (stdout || "") + (stderr || "");
|
|
8405
|
+
if (error3 && "killed" in error3 && error3.killed) {
|
|
8406
|
+
resolve({
|
|
8407
|
+
output: `Command timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
|
|
8408
|
+
exitCode: 124,
|
|
8409
|
+
timedOut: true
|
|
8410
|
+
});
|
|
8411
|
+
return;
|
|
8412
|
+
}
|
|
8413
|
+
const exitCode = error3 ? typeof error3.code === "number" ? error3.code : 1 : 0;
|
|
8414
|
+
resolve({
|
|
8415
|
+
output: output2,
|
|
8416
|
+
exitCode,
|
|
8417
|
+
timedOut: false
|
|
8418
|
+
});
|
|
8419
|
+
}
|
|
8420
|
+
);
|
|
8421
|
+
});
|
|
8422
|
+
}
|
|
8423
|
+
function formatCodeBlock(command, output2, exitCode) {
|
|
8424
|
+
return `<pre>$ ${command}
|
|
8425
|
+
${output2}
|
|
8426
|
+
[exit ${exitCode}]</pre>`;
|
|
8427
|
+
}
|
|
8428
|
+
function formatRaw(output2) {
|
|
8429
|
+
return output2.trim();
|
|
8430
|
+
}
|
|
8431
|
+
function shouldSendAsFile(output2) {
|
|
8432
|
+
return output2.length > OUTPUT_MAX_LENGTH;
|
|
8433
|
+
}
|
|
8434
|
+
var SHELL_TIMEOUT_MS, OUTPUT_MAX_LENGTH;
|
|
8435
|
+
var init_exec = __esm({
|
|
8436
|
+
"src/shell/exec.ts"() {
|
|
8437
|
+
"use strict";
|
|
8438
|
+
SHELL_TIMEOUT_MS = 6e4;
|
|
8439
|
+
OUTPUT_MAX_LENGTH = 4e3;
|
|
8440
|
+
}
|
|
8441
|
+
});
|
|
8442
|
+
|
|
8443
|
+
// src/shell/backend-cmd.ts
|
|
8444
|
+
import { execFile as execFile4 } from "child_process";
|
|
8445
|
+
function buildNativeResponse(backendId, command, output2) {
|
|
8446
|
+
const body = output2 || "(no output)";
|
|
8447
|
+
return `<pre>[${backendId}] ${command}
|
|
8448
|
+
${body}</pre>`;
|
|
8449
|
+
}
|
|
8450
|
+
function extractTextFromNdjson(raw) {
|
|
8451
|
+
const texts = [];
|
|
8452
|
+
for (const line of raw.split("\n")) {
|
|
8453
|
+
if (!line.trim()) continue;
|
|
8454
|
+
try {
|
|
8455
|
+
const obj = JSON.parse(line);
|
|
8456
|
+
if (obj.type === "result" && obj.result) {
|
|
8457
|
+
texts.push(typeof obj.result === "string" ? obj.result : JSON.stringify(obj.result));
|
|
8458
|
+
}
|
|
8459
|
+
if (obj.type === "assistant" && obj.message?.content) {
|
|
8460
|
+
for (const block of obj.message.content) {
|
|
8461
|
+
if (block.type === "text" && block.text) texts.push(block.text);
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
if (obj.type === "text" && obj.text) {
|
|
8465
|
+
texts.push(obj.text);
|
|
8466
|
+
}
|
|
8467
|
+
} catch {
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8470
|
+
return texts.join("\n").trim();
|
|
8471
|
+
}
|
|
8472
|
+
async function handleBackendCommand(command, chatId, channel) {
|
|
8473
|
+
let adapter;
|
|
8474
|
+
try {
|
|
8475
|
+
adapter = getAdapterForChat(chatId);
|
|
8476
|
+
} catch {
|
|
8477
|
+
await channel.sendText(chatId, "No backend set. Use /backend first.", "plain");
|
|
8478
|
+
return;
|
|
8479
|
+
}
|
|
8480
|
+
const sessionId = getSessionId(chatId);
|
|
8481
|
+
if (!sessionId) {
|
|
8482
|
+
await channel.sendText(chatId, "No active session. Start a conversation first.", "plain");
|
|
8483
|
+
return;
|
|
8484
|
+
}
|
|
8485
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
8486
|
+
try {
|
|
8487
|
+
const output2 = await spawnBackendCommand(adapter, command, sessionId, cwd);
|
|
8488
|
+
const formatted = buildNativeResponse(adapter.id, command, output2);
|
|
8489
|
+
await channel.sendText(chatId, formatted, "html");
|
|
8490
|
+
} catch (err) {
|
|
8491
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8492
|
+
await channel.sendText(chatId, `Backend command failed: ${msg}`, "plain");
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
function spawnBackendCommand(adapter, command, sessionId, cwd) {
|
|
8496
|
+
return new Promise((resolve, reject) => {
|
|
8497
|
+
const config2 = adapter.buildSpawnConfig({
|
|
8498
|
+
prompt: command,
|
|
8499
|
+
sessionId,
|
|
8500
|
+
cwd,
|
|
8501
|
+
permMode: "plan",
|
|
8502
|
+
allowedTools: []
|
|
8503
|
+
});
|
|
8504
|
+
const env = { ...process.env, ...adapter.getEnv() };
|
|
8505
|
+
execFile4(config2.executable, config2.args, {
|
|
8506
|
+
cwd,
|
|
8507
|
+
timeout: BACKEND_CMD_TIMEOUT_MS,
|
|
8508
|
+
env,
|
|
8509
|
+
maxBuffer: 5 * 1024 * 1024
|
|
8510
|
+
}, (error3, stdout, stderr) => {
|
|
8511
|
+
if (error3 && "killed" in error3 && error3.killed) {
|
|
8512
|
+
reject(new Error(`Backend command timed out after ${BACKEND_CMD_TIMEOUT_MS / 1e3} seconds.`));
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
const output2 = extractTextFromNdjson(stdout) || stdout || stderr || "";
|
|
8516
|
+
resolve(output2.trim());
|
|
8517
|
+
});
|
|
8518
|
+
});
|
|
8519
|
+
}
|
|
8520
|
+
var BACKEND_CMD_TIMEOUT_MS;
|
|
8521
|
+
var init_backend_cmd = __esm({
|
|
8522
|
+
"src/shell/backend-cmd.ts"() {
|
|
8523
|
+
"use strict";
|
|
8524
|
+
init_backends();
|
|
8525
|
+
init_store4();
|
|
8526
|
+
BACKEND_CMD_TIMEOUT_MS = 15e3;
|
|
8527
|
+
}
|
|
8528
|
+
});
|
|
8529
|
+
|
|
8281
8530
|
// src/router.ts
|
|
8282
8531
|
import { readFile as readFile5, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
|
|
8283
8532
|
import { resolve as resolvePath } from "path";
|
|
@@ -8367,6 +8616,17 @@ async function handleMessage(msg, channel) {
|
|
|
8367
8616
|
}
|
|
8368
8617
|
}
|
|
8369
8618
|
}
|
|
8619
|
+
if (msg.type === "text" && msg.text) {
|
|
8620
|
+
const text = msg.text.trim();
|
|
8621
|
+
if (text.startsWith("!!")) {
|
|
8622
|
+
const cmd = text.slice(2).trim();
|
|
8623
|
+
if (cmd) return handleRawShell(cmd, chatId, channel);
|
|
8624
|
+
}
|
|
8625
|
+
if (text.startsWith("!") && text.length > 1) {
|
|
8626
|
+
const cmd = text.slice(1).trim();
|
|
8627
|
+
if (cmd) return handleShell(cmd, chatId, channel);
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8370
8630
|
switch (msg.type) {
|
|
8371
8631
|
case "command":
|
|
8372
8632
|
await handleCommand(msg, channel);
|
|
@@ -8386,6 +8646,9 @@ async function handleMessage(msg, channel) {
|
|
|
8386
8646
|
}
|
|
8387
8647
|
async function handleCommand(msg, channel) {
|
|
8388
8648
|
const { chatId, command, commandArgs } = msg;
|
|
8649
|
+
if (command?.startsWith("/")) {
|
|
8650
|
+
return handleBackendCommand(command, chatId, channel);
|
|
8651
|
+
}
|
|
8389
8652
|
switch (command) {
|
|
8390
8653
|
case "start":
|
|
8391
8654
|
case "help":
|
|
@@ -8520,15 +8783,7 @@ Tap to toggle:`,
|
|
|
8520
8783
|
case "backend": {
|
|
8521
8784
|
const requestedBackend = (commandArgs ?? "").trim().toLowerCase();
|
|
8522
8785
|
if (requestedBackend && getAllBackendIds().includes(requestedBackend)) {
|
|
8523
|
-
|
|
8524
|
-
});
|
|
8525
|
-
clearSession(chatId);
|
|
8526
|
-
clearModel(chatId);
|
|
8527
|
-
clearThinkingLevel(chatId);
|
|
8528
|
-
setBackend(chatId, requestedBackend);
|
|
8529
|
-
const adapter = getAdapter(requestedBackend);
|
|
8530
|
-
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: requestedBackend } });
|
|
8531
|
-
await channel.sendText(chatId, `Switched to <b>${adapter.displayName}</b>.`, "html");
|
|
8786
|
+
await sendBackendSwitchConfirmation(chatId, requestedBackend, channel);
|
|
8532
8787
|
break;
|
|
8533
8788
|
}
|
|
8534
8789
|
const currentBackend = getBackend(chatId);
|
|
@@ -8552,15 +8807,7 @@ Tap to toggle:`,
|
|
|
8552
8807
|
case "codex": {
|
|
8553
8808
|
const backendId = command;
|
|
8554
8809
|
if (getAllBackendIds().includes(backendId)) {
|
|
8555
|
-
|
|
8556
|
-
});
|
|
8557
|
-
clearSession(chatId);
|
|
8558
|
-
clearModel(chatId);
|
|
8559
|
-
clearThinkingLevel(chatId);
|
|
8560
|
-
setBackend(chatId, backendId);
|
|
8561
|
-
const adapter = getAdapter(backendId);
|
|
8562
|
-
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
8563
|
-
await channel.sendText(chatId, `Switched to <b>${adapter.displayName}</b>.`, "html");
|
|
8810
|
+
await sendBackendSwitchConfirmation(chatId, backendId, channel);
|
|
8564
8811
|
} else {
|
|
8565
8812
|
await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
|
|
8566
8813
|
}
|
|
@@ -8733,11 +8980,28 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
|
|
|
8733
8980
|
case "cwd": {
|
|
8734
8981
|
if (!commandArgs) {
|
|
8735
8982
|
const current = getCwd(chatId);
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8983
|
+
const recents = getRecentBookmarks(chatId, 10);
|
|
8984
|
+
if (recents.length === 0) {
|
|
8985
|
+
await channel.sendText(
|
|
8986
|
+
chatId,
|
|
8987
|
+
current ? `Working directory: ${current}
|
|
8988
|
+
|
|
8989
|
+
No saved bookmarks yet. Set a directory with /cwd <path> to auto-save.` : "No working directory set. Usage: /cwd ~/projects/my-app",
|
|
8990
|
+
"plain"
|
|
8991
|
+
);
|
|
8992
|
+
return;
|
|
8993
|
+
}
|
|
8994
|
+
const text = current ? `Current: ${current}
|
|
8995
|
+
|
|
8996
|
+
Recent directories:` : "Recent directories:";
|
|
8997
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
8998
|
+
const buttons = recents.map((r) => [{ label: r.alias, data: `cwdpick:${r.alias}` }]);
|
|
8999
|
+
await channel.sendKeyboard(chatId, text, buttons);
|
|
9000
|
+
} else {
|
|
9001
|
+
const list = recents.map((r) => ` ${r.alias} \u2192 ${r.path}`).join("\n");
|
|
9002
|
+
await channel.sendText(chatId, `${text}
|
|
9003
|
+
${list}`, "plain");
|
|
9004
|
+
}
|
|
8741
9005
|
return;
|
|
8742
9006
|
}
|
|
8743
9007
|
if (commandArgs === "reset" || commandArgs === "clear") {
|
|
@@ -8745,12 +9009,71 @@ Use /summarizer auto, /summarizer off, or /summarizer <backend>:<model>`, "plain
|
|
|
8745
9009
|
await channel.sendText(chatId, "Working directory cleared. Using default.", "plain");
|
|
8746
9010
|
return;
|
|
8747
9011
|
}
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
9012
|
+
if (commandArgs === "aliases") {
|
|
9013
|
+
const all = getAllBookmarks(chatId);
|
|
9014
|
+
if (all.length === 0) {
|
|
9015
|
+
await channel.sendText(chatId, "No bookmarks saved yet.", "plain");
|
|
9016
|
+
return;
|
|
9017
|
+
}
|
|
9018
|
+
const lines = all.map((b) => ` ${b.manual ? "[manual]" : "[auto]"} ${b.alias} \u2192 ${b.path}`);
|
|
9019
|
+
await channel.sendText(chatId, `Directory bookmarks:
|
|
9020
|
+
${lines.join("\n")}`, "plain");
|
|
9021
|
+
return;
|
|
9022
|
+
}
|
|
9023
|
+
if (commandArgs.startsWith("unalias ")) {
|
|
9024
|
+
const aliasName = commandArgs.slice(8).trim();
|
|
9025
|
+
if (!aliasName) {
|
|
9026
|
+
await channel.sendText(chatId, "Usage: /cwd unalias <name>", "plain");
|
|
9027
|
+
return;
|
|
9028
|
+
}
|
|
9029
|
+
const deleted = deleteBookmark(chatId, aliasName);
|
|
9030
|
+
await channel.sendText(chatId, deleted ? `Bookmark '${aliasName}' removed.` : `Bookmark '${aliasName}' not found.`, "plain");
|
|
9031
|
+
return;
|
|
9032
|
+
}
|
|
9033
|
+
if (commandArgs.startsWith("alias ")) {
|
|
9034
|
+
const parts = commandArgs.slice(6).trim().split(/\s+/);
|
|
9035
|
+
if (parts.length < 2) {
|
|
9036
|
+
await channel.sendText(chatId, "Usage: /cwd alias <name> <path>", "plain");
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
const [aliasName, ...pathParts] = parts;
|
|
9040
|
+
const aliasPath = pathParts.join(" ").replace(/^~/, process.env.HOME ?? "");
|
|
9041
|
+
upsertBookmark(chatId, aliasName, aliasPath, true);
|
|
9042
|
+
await channel.sendText(chatId, `Bookmark saved: ${aliasName} \u2192 ${aliasPath}`, "plain");
|
|
9043
|
+
return;
|
|
9044
|
+
}
|
|
9045
|
+
const arg = commandArgs;
|
|
9046
|
+
if (arg.startsWith("/") || arg.startsWith("~")) {
|
|
9047
|
+
const resolvedPath = arg.startsWith("~") ? arg.replace("~", process.env.HOME ?? "") : arg;
|
|
9048
|
+
setCwd(chatId, resolvedPath);
|
|
9049
|
+
const basename2 = resolvedPath.split("/").filter(Boolean).pop();
|
|
9050
|
+
if (basename2) upsertBookmark(chatId, basename2, resolvedPath, false);
|
|
9051
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${resolvedPath}`, detail: { field: "cwd", value: resolvedPath } });
|
|
9052
|
+
await sendCwdSessionChoice(chatId, resolvedPath, channel);
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9055
|
+
const exact = getBookmark(chatId, arg);
|
|
9056
|
+
if (exact) {
|
|
9057
|
+
setCwd(chatId, exact.path);
|
|
9058
|
+
touchBookmark(chatId, arg);
|
|
9059
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${exact.path}`, detail: { field: "cwd", value: exact.path } });
|
|
9060
|
+
await sendCwdSessionChoice(chatId, exact.path, channel);
|
|
9061
|
+
return;
|
|
9062
|
+
}
|
|
9063
|
+
const matches = findBookmarksByPrefix(chatId, arg);
|
|
9064
|
+
if (matches.length === 1) {
|
|
9065
|
+
setCwd(chatId, matches[0].path);
|
|
9066
|
+
touchBookmark(chatId, matches[0].alias);
|
|
9067
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${matches[0].path}`, detail: { field: "cwd", value: matches[0].path } });
|
|
9068
|
+
await sendCwdSessionChoice(chatId, matches[0].path, channel);
|
|
9069
|
+
return;
|
|
9070
|
+
}
|
|
9071
|
+
if (matches.length > 1 && typeof channel.sendKeyboard === "function") {
|
|
9072
|
+
const buttons = matches.map((m) => [{ label: `${m.alias} \u2192 ${m.path}`, data: `cwdpick:${m.alias}` }]);
|
|
9073
|
+
await channel.sendKeyboard(chatId, `Multiple matches for "${arg}":`, buttons);
|
|
9074
|
+
return;
|
|
9075
|
+
}
|
|
9076
|
+
await channel.sendText(chatId, `Directory alias '${arg}' not found. Use /cwd aliases to see saved bookmarks.`, "plain");
|
|
8754
9077
|
break;
|
|
8755
9078
|
}
|
|
8756
9079
|
case "memory": {
|
|
@@ -9255,7 +9578,7 @@ Use /skills to see it.`, "plain");
|
|
|
9255
9578
|
lines.push("", "\u2501\u2501 <b>Built-in</b> \u2501\u2501");
|
|
9256
9579
|
lines.push(` \u2705 <b>cc-claw</b> <i>Agent orchestrator (spawn, tasks, inbox)</i>`);
|
|
9257
9580
|
}
|
|
9258
|
-
const { execFile:
|
|
9581
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
9259
9582
|
const { homedir: homedir5 } = await import("os");
|
|
9260
9583
|
const discoveryCwd = homedir5();
|
|
9261
9584
|
const runnerResults = await Promise.allSettled(
|
|
@@ -9264,7 +9587,7 @@ Use /skills to see it.`, "plain");
|
|
|
9264
9587
|
if (!listCmd.length) return Promise.resolve({ runner, output: "" });
|
|
9265
9588
|
const exe = runner.getExecutablePath();
|
|
9266
9589
|
return new Promise((resolve) => {
|
|
9267
|
-
|
|
9590
|
+
execFile5(exe, listCmd.slice(1), {
|
|
9268
9591
|
encoding: "utf-8",
|
|
9269
9592
|
timeout: 3e4,
|
|
9270
9593
|
cwd: discoveryCwd,
|
|
@@ -9711,31 +10034,65 @@ async function sendResponse(chatId, channel, text) {
|
|
|
9711
10034
|
function isImageExt(ext) {
|
|
9712
10035
|
return ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(ext);
|
|
9713
10036
|
}
|
|
10037
|
+
async function sendBackendSwitchConfirmation(chatId, target, channel) {
|
|
10038
|
+
const current = getBackend(chatId);
|
|
10039
|
+
const targetAdapter = getAdapter(target);
|
|
10040
|
+
if (current === target) {
|
|
10041
|
+
await channel.sendText(chatId, `Already using ${targetAdapter.displayName}.`, "plain");
|
|
10042
|
+
return;
|
|
10043
|
+
}
|
|
10044
|
+
const currentLabel = current ? getAdapter(current).displayName : "current backend";
|
|
10045
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10046
|
+
await channel.sendKeyboard(
|
|
10047
|
+
chatId,
|
|
10048
|
+
`\u26A0\uFE0F Switching to ${targetAdapter.displayName} will summarize and reset your current session.
|
|
10049
|
+
|
|
10050
|
+
What would you like to do?`,
|
|
10051
|
+
[
|
|
10052
|
+
[{ label: `Stay on ${currentLabel}`, data: "backend_cancel" }],
|
|
10053
|
+
[{ label: `Switch to ${targetAdapter.displayName} + summarize`, data: `backend_confirm:${target}` }]
|
|
10054
|
+
]
|
|
10055
|
+
);
|
|
10056
|
+
} else {
|
|
10057
|
+
await doBackendSwitch(chatId, target, channel);
|
|
10058
|
+
}
|
|
10059
|
+
}
|
|
10060
|
+
async function doBackendSwitch(chatId, backendId, channel) {
|
|
10061
|
+
summarizeSession(chatId).catch(() => {
|
|
10062
|
+
});
|
|
10063
|
+
clearSession(chatId);
|
|
10064
|
+
clearModel(chatId);
|
|
10065
|
+
clearThinkingLevel(chatId);
|
|
10066
|
+
setBackend(chatId, backendId);
|
|
10067
|
+
const adapter = getAdapter(backendId);
|
|
10068
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Backend switched to ${adapter.displayName}`, detail: { field: "backend", value: backendId } });
|
|
10069
|
+
await channel.sendText(
|
|
10070
|
+
chatId,
|
|
10071
|
+
`Backend switched to ${adapter.displayName}.
|
|
10072
|
+
Default model: ${adapter.defaultModel}
|
|
10073
|
+
Session reset. Ready!`,
|
|
10074
|
+
"plain"
|
|
10075
|
+
);
|
|
10076
|
+
}
|
|
9714
10077
|
async function handleCallback(chatId, data, channel) {
|
|
9715
10078
|
if (data.startsWith("backend:")) {
|
|
9716
10079
|
const chosen = data.slice(8);
|
|
9717
10080
|
if (!getAllBackendIds().includes(chosen)) return;
|
|
9718
10081
|
const previous = getBackend(chatId);
|
|
9719
10082
|
if (chosen === previous) {
|
|
9720
|
-
const
|
|
9721
|
-
await channel.sendText(chatId, `Already using ${
|
|
10083
|
+
const adapter = getAdapter(chosen);
|
|
10084
|
+
await channel.sendText(chatId, `Already using ${adapter.displayName}.`, "plain");
|
|
9722
10085
|
return;
|
|
9723
10086
|
}
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
const
|
|
9731
|
-
|
|
9732
|
-
await channel.sendText(
|
|
9733
|
-
chatId,
|
|
9734
|
-
`Backend switched to ${adapter.displayName}.
|
|
9735
|
-
Default model: ${adapter.defaultModel}
|
|
9736
|
-
Session reset. Ready!`,
|
|
9737
|
-
"plain"
|
|
9738
|
-
);
|
|
10087
|
+
await sendBackendSwitchConfirmation(chatId, chosen, channel);
|
|
10088
|
+
} else if (data.startsWith("backend_confirm:")) {
|
|
10089
|
+
const chosen = data.slice(16);
|
|
10090
|
+
if (!getAllBackendIds().includes(chosen)) return;
|
|
10091
|
+
await doBackendSwitch(chatId, chosen, channel);
|
|
10092
|
+
} else if (data === "backend_cancel") {
|
|
10093
|
+
const current = getBackend(chatId);
|
|
10094
|
+
const label2 = current ? getAdapter(current).displayName : "current backend";
|
|
10095
|
+
await channel.sendText(chatId, `No change. Staying on ${label2}.`, "plain");
|
|
9739
10096
|
} else if (data.startsWith("model:")) {
|
|
9740
10097
|
const chosen = data.slice(6);
|
|
9741
10098
|
let adapter;
|
|
@@ -9834,6 +10191,51 @@ ${PERM_MODES[chosen]}`,
|
|
|
9834
10191
|
} else {
|
|
9835
10192
|
await channel.sendText(chatId, "Preference not saved.", "plain");
|
|
9836
10193
|
}
|
|
10194
|
+
} else if (data.startsWith("shell:")) {
|
|
10195
|
+
const parts = data.split(":");
|
|
10196
|
+
const action = parts[1];
|
|
10197
|
+
const id = parts[2];
|
|
10198
|
+
if (action === "confirm") {
|
|
10199
|
+
const pending = getPendingCommand(id);
|
|
10200
|
+
if (!pending) {
|
|
10201
|
+
await channel.sendText(chatId, "Confirmation expired. Please re-send the command.", "plain");
|
|
10202
|
+
return;
|
|
10203
|
+
}
|
|
10204
|
+
removePendingCommand(id);
|
|
10205
|
+
if (pending.raw) {
|
|
10206
|
+
await handleRawShell(pending.command, pending.chatId, channel, true);
|
|
10207
|
+
} else {
|
|
10208
|
+
await handleShell(pending.command, pending.chatId, channel, true);
|
|
10209
|
+
}
|
|
10210
|
+
} else if (action === "cancel") {
|
|
10211
|
+
removePendingCommand(id);
|
|
10212
|
+
await channel.sendText(chatId, "Command cancelled.", "plain");
|
|
10213
|
+
}
|
|
10214
|
+
} else if (data.startsWith("cwd:")) {
|
|
10215
|
+
const parts = data.split(":");
|
|
10216
|
+
const action = parts[1];
|
|
10217
|
+
const targetChatId = parts.slice(2).join(":");
|
|
10218
|
+
if (action === "keep") {
|
|
10219
|
+
await channel.sendText(chatId, "Session kept. The agent will continue with existing context.", "plain");
|
|
10220
|
+
} else if (action === "summarize") {
|
|
10221
|
+
await summarizeSession(targetChatId);
|
|
10222
|
+
clearSession(targetChatId);
|
|
10223
|
+
await channel.sendText(chatId, "Session summarized and reset. Context preserved in memory.", "plain");
|
|
10224
|
+
} else if (action === "reset") {
|
|
10225
|
+
clearSession(targetChatId);
|
|
10226
|
+
await channel.sendText(chatId, "Session reset. Clean slate.", "plain");
|
|
10227
|
+
}
|
|
10228
|
+
} else if (data.startsWith("cwdpick:")) {
|
|
10229
|
+
const alias = data.slice(8);
|
|
10230
|
+
const bookmark = getBookmark(chatId, alias);
|
|
10231
|
+
if (!bookmark) {
|
|
10232
|
+
await channel.sendText(chatId, `Bookmark '${alias}' no longer exists.`, "plain");
|
|
10233
|
+
return;
|
|
10234
|
+
}
|
|
10235
|
+
setCwd(chatId, bookmark.path);
|
|
10236
|
+
touchBookmark(chatId, alias);
|
|
10237
|
+
logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${bookmark.path}`, detail: { field: "cwd", value: bookmark.path } });
|
|
10238
|
+
await sendCwdSessionChoice(chatId, bookmark.path, channel);
|
|
9837
10239
|
} else if (data.startsWith("skill:")) {
|
|
9838
10240
|
const parts = data.slice(6).split(":");
|
|
9839
10241
|
let skillName;
|
|
@@ -9871,6 +10273,101 @@ ${PERM_MODES[chosen]}`,
|
|
|
9871
10273
|
await sendResponse(chatId, channel, response.text);
|
|
9872
10274
|
}
|
|
9873
10275
|
}
|
|
10276
|
+
async function handleShell(command, chatId, channel, skipGuard = false) {
|
|
10277
|
+
if (!skipGuard && isDestructive(command)) {
|
|
10278
|
+
const id = storePendingCommand(command, chatId, false);
|
|
10279
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10280
|
+
await channel.sendKeyboard(
|
|
10281
|
+
chatId,
|
|
10282
|
+
`\u26A0\uFE0F This command looks potentially destructive.
|
|
10283
|
+
|
|
10284
|
+
Command: ${command}`,
|
|
10285
|
+
[[
|
|
10286
|
+
{ label: "Run anyway", data: `shell:confirm:${id}` },
|
|
10287
|
+
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
10288
|
+
]]
|
|
10289
|
+
);
|
|
10290
|
+
} else {
|
|
10291
|
+
await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
|
|
10292
|
+
No keyboard available to confirm.`, "plain");
|
|
10293
|
+
}
|
|
10294
|
+
return;
|
|
10295
|
+
}
|
|
10296
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
10297
|
+
const result = await executeShell(command, cwd);
|
|
10298
|
+
logActivity(getDb(), {
|
|
10299
|
+
chatId,
|
|
10300
|
+
source: "telegram",
|
|
10301
|
+
eventType: "shell_command",
|
|
10302
|
+
summary: `Shell: ${command.slice(0, 100)}`,
|
|
10303
|
+
detail: { command, exitCode: result.exitCode, outputLength: result.output.length, timedOut: result.timedOut }
|
|
10304
|
+
});
|
|
10305
|
+
const formatted = formatCodeBlock(command, result.output, result.exitCode);
|
|
10306
|
+
if (shouldSendAsFile(formatted)) {
|
|
10307
|
+
const buffer = Buffer.from(result.output, "utf-8");
|
|
10308
|
+
await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
|
|
10309
|
+
await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
|
|
10310
|
+
} else {
|
|
10311
|
+
await channel.sendText(chatId, formatted, "html");
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
async function handleRawShell(command, chatId, channel, skipGuard = false) {
|
|
10315
|
+
if (!skipGuard && isDestructive(command)) {
|
|
10316
|
+
const id = storePendingCommand(command, chatId, true);
|
|
10317
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10318
|
+
await channel.sendKeyboard(
|
|
10319
|
+
chatId,
|
|
10320
|
+
`\u26A0\uFE0F This command looks potentially destructive.
|
|
10321
|
+
|
|
10322
|
+
Command: ${command}`,
|
|
10323
|
+
[[
|
|
10324
|
+
{ label: "Run anyway", data: `shell:confirm:${id}` },
|
|
10325
|
+
{ label: "Cancel", data: `shell:cancel:${id}` }
|
|
10326
|
+
]]
|
|
10327
|
+
);
|
|
10328
|
+
} else {
|
|
10329
|
+
await channel.sendText(chatId, `\u26A0\uFE0F Destructive command blocked: ${command}
|
|
10330
|
+
No keyboard available to confirm.`, "plain");
|
|
10331
|
+
}
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10334
|
+
const cwd = getCwd(chatId) ?? `${process.env.HOME ?? "/tmp"}/.cc-claw/workspace`;
|
|
10335
|
+
const result = await executeShell(command, cwd);
|
|
10336
|
+
logActivity(getDb(), {
|
|
10337
|
+
chatId,
|
|
10338
|
+
source: "telegram",
|
|
10339
|
+
eventType: "shell_command",
|
|
10340
|
+
summary: `Shell: ${command.slice(0, 100)}`,
|
|
10341
|
+
detail: { command, exitCode: result.exitCode, outputLength: result.output.length, timedOut: result.timedOut }
|
|
10342
|
+
});
|
|
10343
|
+
const formatted = formatRaw(result.output);
|
|
10344
|
+
if (shouldSendAsFile(formatted)) {
|
|
10345
|
+
const buffer = Buffer.from(result.output, "utf-8");
|
|
10346
|
+
await channel.sendFile(chatId, buffer, "output.txt", "text/plain");
|
|
10347
|
+
await channel.sendText(chatId, `Output too long (${result.output.length} chars), sent as file.`, "plain");
|
|
10348
|
+
} else {
|
|
10349
|
+
await channel.sendText(chatId, formatted, "plain");
|
|
10350
|
+
}
|
|
10351
|
+
}
|
|
10352
|
+
async function sendCwdSessionChoice(chatId, path, channel) {
|
|
10353
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10354
|
+
await channel.sendKeyboard(
|
|
10355
|
+
chatId,
|
|
10356
|
+
`Working directory set to: ${path}
|
|
10357
|
+
|
|
10358
|
+
Changing directories mid-session may confuse the agent.
|
|
10359
|
+
What would you like to do?`,
|
|
10360
|
+
[[
|
|
10361
|
+
{ label: "Keep session", data: `cwd:keep:${chatId}` },
|
|
10362
|
+
{ label: "Summarize & reset", data: `cwd:summarize:${chatId}` },
|
|
10363
|
+
{ label: "Reset session", data: `cwd:reset:${chatId}` }
|
|
10364
|
+
]]
|
|
10365
|
+
);
|
|
10366
|
+
} else {
|
|
10367
|
+
await channel.sendText(chatId, `Working directory set to: ${path}
|
|
10368
|
+
Session kept.`, "plain");
|
|
10369
|
+
}
|
|
10370
|
+
}
|
|
9874
10371
|
function parseIntervalToMs(input) {
|
|
9875
10372
|
const match = input.trim().match(/^(\d+)\s*(m|min|h|hr|hour|s|sec)$/i);
|
|
9876
10373
|
if (!match) return null;
|
|
@@ -9938,6 +10435,9 @@ var init_router = __esm({
|
|
|
9938
10435
|
init_registry();
|
|
9939
10436
|
init_registry2();
|
|
9940
10437
|
init_store3();
|
|
10438
|
+
init_guard();
|
|
10439
|
+
init_exec();
|
|
10440
|
+
init_backend_cmd();
|
|
9941
10441
|
PERM_MODES = {
|
|
9942
10442
|
yolo: "YOLO \u2014 all tools, full autopilot",
|
|
9943
10443
|
safe: "Safe \u2014 only my allowed tools",
|
|
@@ -11520,7 +12020,7 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
11520
12020
|
const r = d;
|
|
11521
12021
|
const lines = [
|
|
11522
12022
|
"",
|
|
11523
|
-
box(
|
|
12023
|
+
box(`CC-Claw Doctor v${VERSION}`),
|
|
11524
12024
|
""
|
|
11525
12025
|
];
|
|
11526
12026
|
for (const c of r.checks) {
|
|
@@ -11550,6 +12050,7 @@ var init_doctor = __esm({
|
|
|
11550
12050
|
"use strict";
|
|
11551
12051
|
init_format();
|
|
11552
12052
|
init_paths();
|
|
12053
|
+
init_version();
|
|
11553
12054
|
}
|
|
11554
12055
|
});
|
|
11555
12056
|
|
package/package.json
CHANGED