cc-claw 0.6.1 → 0.7.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 +396 -74
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -55,7 +55,7 @@ var VERSION;
|
|
|
55
55
|
var init_version = __esm({
|
|
56
56
|
"src/version.ts"() {
|
|
57
57
|
"use strict";
|
|
58
|
-
VERSION = true ? "0.
|
|
58
|
+
VERSION = true ? "0.7.1" : (() => {
|
|
59
59
|
try {
|
|
60
60
|
return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
|
|
61
61
|
} catch {
|
|
@@ -964,12 +964,14 @@ __export(store_exports3, {
|
|
|
964
964
|
getBackendLimit: () => getBackendLimit,
|
|
965
965
|
getBackendUsageInWindow: () => getBackendUsageInWindow,
|
|
966
966
|
getBookmark: () => getBookmark,
|
|
967
|
+
getChatGeminiSlotId: () => getChatGeminiSlotId,
|
|
967
968
|
getChatIdByAlias: () => getChatIdByAlias,
|
|
968
969
|
getChatUsageByModel: () => getChatUsageByModel,
|
|
969
970
|
getCwd: () => getCwd,
|
|
970
971
|
getDb: () => getDb,
|
|
971
972
|
getEligibleGeminiSlots: () => getEligibleGeminiSlots,
|
|
972
973
|
getEnabledTools: () => getEnabledTools,
|
|
974
|
+
getGeminiRotationMode: () => getGeminiRotationMode,
|
|
973
975
|
getGeminiSlots: () => getGeminiSlots,
|
|
974
976
|
getHeartbeatConfig: () => getHeartbeatConfig,
|
|
975
977
|
getJobById: () => getJobById,
|
|
@@ -1028,6 +1030,7 @@ __export(store_exports3, {
|
|
|
1028
1030
|
setBootTime: () => setBootTime,
|
|
1029
1031
|
setChatAlias: () => setChatAlias,
|
|
1030
1032
|
setCwd: () => setCwd,
|
|
1033
|
+
setGeminiRotationMode: () => setGeminiRotationMode,
|
|
1031
1034
|
setGeminiSlotEnabled: () => setGeminiSlotEnabled,
|
|
1032
1035
|
setHeartbeatConfig: () => setHeartbeatConfig,
|
|
1033
1036
|
setMode: () => setMode,
|
|
@@ -2406,17 +2409,24 @@ function getGeminiSlots() {
|
|
|
2406
2409
|
FROM gemini_credentials ORDER BY priority ASC, id ASC
|
|
2407
2410
|
`).all();
|
|
2408
2411
|
}
|
|
2409
|
-
function getEligibleGeminiSlots() {
|
|
2412
|
+
function getEligibleGeminiSlots(mode) {
|
|
2413
|
+
if (mode === "off") return [];
|
|
2414
|
+
const slotTypeFilter = mode === "accounts" ? "AND slot_type = 'oauth'" : mode === "keys" ? "AND slot_type = 'api_key'" : "";
|
|
2410
2415
|
return db.prepare(`
|
|
2411
2416
|
SELECT id, slot_type AS slotType, label, api_key AS apiKey, config_home AS configHome,
|
|
2412
2417
|
priority, enabled, cooldown_until AS cooldownUntil, last_used AS lastUsed,
|
|
2413
2418
|
consecutive_errors AS consecutiveErrors, created_at AS createdAt
|
|
2414
2419
|
FROM gemini_credentials
|
|
2415
2420
|
WHERE enabled = 1 AND (cooldown_until IS NULL OR cooldown_until <= datetime('now'))
|
|
2421
|
+
${slotTypeFilter}
|
|
2416
2422
|
ORDER BY priority ASC, last_used ASC NULLS FIRST
|
|
2417
2423
|
`).all();
|
|
2418
2424
|
}
|
|
2419
|
-
function
|
|
2425
|
+
function getChatGeminiSlotId(chatId) {
|
|
2426
|
+
const row = db.prepare("SELECT slot_id FROM chat_gemini_slot WHERE chat_id = ?").get(chatId);
|
|
2427
|
+
return row?.slot_id ?? null;
|
|
2428
|
+
}
|
|
2429
|
+
function getNextGeminiSlot(chatId, mode) {
|
|
2420
2430
|
const pinned = db.prepare(`
|
|
2421
2431
|
SELECT gc.id, gc.slot_type AS slotType, gc.label, gc.api_key AS apiKey, gc.config_home AS configHome,
|
|
2422
2432
|
gc.priority, gc.enabled, gc.cooldown_until AS cooldownUntil, gc.last_used AS lastUsed,
|
|
@@ -2426,7 +2436,7 @@ function getNextGeminiSlot(chatId) {
|
|
|
2426
2436
|
AND (gc.cooldown_until IS NULL OR gc.cooldown_until <= datetime('now'))
|
|
2427
2437
|
`).get(chatId);
|
|
2428
2438
|
if (pinned) return pinned;
|
|
2429
|
-
const eligible = getEligibleGeminiSlots();
|
|
2439
|
+
const eligible = getEligibleGeminiSlots(mode);
|
|
2430
2440
|
return eligible.length > 0 ? eligible[0] : null;
|
|
2431
2441
|
}
|
|
2432
2442
|
function markSlotExhausted(slotId, quotaClass) {
|
|
@@ -2473,6 +2483,13 @@ function setGeminiSlotEnabled(id, enabled) {
|
|
|
2473
2483
|
function reorderGeminiSlot(id, priority) {
|
|
2474
2484
|
db.prepare("UPDATE gemini_credentials SET priority = ? WHERE id = ?").run(priority, id);
|
|
2475
2485
|
}
|
|
2486
|
+
function getGeminiRotationMode() {
|
|
2487
|
+
const row = db.prepare("SELECT value FROM meta WHERE key = 'gemini_rotation_mode'").get();
|
|
2488
|
+
return row?.value ?? "all";
|
|
2489
|
+
}
|
|
2490
|
+
function setGeminiRotationMode(mode) {
|
|
2491
|
+
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('gemini_rotation_mode', ?)").run(mode);
|
|
2492
|
+
}
|
|
2476
2493
|
function getAgentMode(chatId) {
|
|
2477
2494
|
const row = db.prepare("SELECT mode FROM chat_agent_mode WHERE chat_id = ?").get(chatId);
|
|
2478
2495
|
return row?.mode ?? "auto";
|
|
@@ -2911,17 +2928,25 @@ var init_gemini = __esm({
|
|
|
2911
2928
|
thinking: "adjustable",
|
|
2912
2929
|
thinkingLevels: ["low", "high"],
|
|
2913
2930
|
defaultThinkingLevel: "high"
|
|
2931
|
+
},
|
|
2932
|
+
"gemini-3.1-flash-lite-preview": {
|
|
2933
|
+
label: "Gemini 3.1 Flash Lite \u2014 lightest, cheapest",
|
|
2934
|
+
thinking: "adjustable",
|
|
2935
|
+
thinkingLevels: ["low", "high"],
|
|
2936
|
+
defaultThinkingLevel: "low"
|
|
2914
2937
|
}
|
|
2915
2938
|
};
|
|
2916
2939
|
defaultModel = "gemini-3.1-pro-preview";
|
|
2917
|
-
summarizerModel = "gemini-3-flash-preview";
|
|
2940
|
+
summarizerModel = "gemini-3.1-flash-lite-preview";
|
|
2918
2941
|
pricing = {
|
|
2919
2942
|
"gemini-3.1-pro-preview": { in: 1.25, out: 10, cache: 0.32 },
|
|
2920
|
-
"gemini-3-flash-preview": { in: 0.15, out: 0.6, cache: 0.04 }
|
|
2943
|
+
"gemini-3-flash-preview": { in: 0.15, out: 0.6, cache: 0.04 },
|
|
2944
|
+
"gemini-3.1-flash-lite-preview": { in: 0.04, out: 0.15, cache: 0.01 }
|
|
2921
2945
|
};
|
|
2922
2946
|
contextWindow = {
|
|
2923
2947
|
"gemini-3.1-pro-preview": 1e6,
|
|
2924
|
-
"gemini-3-flash-preview": 1e6
|
|
2948
|
+
"gemini-3-flash-preview": 1e6,
|
|
2949
|
+
"gemini-3.1-flash-lite-preview": 1e6
|
|
2925
2950
|
};
|
|
2926
2951
|
_resolvedPath = "";
|
|
2927
2952
|
getExecutablePath() {
|
|
@@ -3024,8 +3049,8 @@ var init_gemini = __esm({
|
|
|
3024
3049
|
* OAuth slots set GEMINI_CLI_HOME for isolated config and unset GEMINI_API_KEY so
|
|
3025
3050
|
* the CLI uses OAuth instead. Returns the slot used (or null for default behavior).
|
|
3026
3051
|
*/
|
|
3027
|
-
getEnvForSlot(chatId, thinkingOverrides) {
|
|
3028
|
-
const slot = getNextGeminiSlot(chatId);
|
|
3052
|
+
getEnvForSlot(chatId, thinkingOverrides, mode) {
|
|
3053
|
+
const slot = getNextGeminiSlot(chatId, mode);
|
|
3029
3054
|
const env = this.getEnv(thinkingOverrides);
|
|
3030
3055
|
if (!slot) return { env, slot: null };
|
|
3031
3056
|
if (slot.slotType === "api_key" && slot.apiKey) {
|
|
@@ -3894,6 +3919,16 @@ var init_loader = __esm({
|
|
|
3894
3919
|
});
|
|
3895
3920
|
|
|
3896
3921
|
// src/memory/session-log.ts
|
|
3922
|
+
var session_log_exports = {};
|
|
3923
|
+
__export(session_log_exports, {
|
|
3924
|
+
appendToLog: () => appendToLog,
|
|
3925
|
+
clearLog: () => clearLog,
|
|
3926
|
+
getCachedLog: () => getCachedLog,
|
|
3927
|
+
getLastMessageTimestamp: () => getLastMessageTimestamp,
|
|
3928
|
+
getLog: () => getLog,
|
|
3929
|
+
getLoggedChatIds: () => getLoggedChatIds,
|
|
3930
|
+
getMessagePairCount: () => getMessagePairCount
|
|
3931
|
+
});
|
|
3897
3932
|
function appendToLog(chatId, userMessage, assistantResponse, backend2, model2, sessionId) {
|
|
3898
3933
|
const now = Date.now();
|
|
3899
3934
|
appendMessageLog(chatId, "user", userMessage, backend2 ?? null, model2 ?? null, sessionId ?? null);
|
|
@@ -3937,6 +3972,9 @@ function getLastMessageTimestamp(chatId) {
|
|
|
3937
3972
|
const last = rows[rows.length - 1];
|
|
3938
3973
|
return (/* @__PURE__ */ new Date(last.created_at + (last.created_at.includes("Z") ? "" : "Z"))).getTime();
|
|
3939
3974
|
}
|
|
3975
|
+
function getCachedLog(chatId) {
|
|
3976
|
+
return cache.get(chatId) ?? [];
|
|
3977
|
+
}
|
|
3940
3978
|
function getLoggedChatIds() {
|
|
3941
3979
|
return getUnsummarizedChatIds();
|
|
3942
3980
|
}
|
|
@@ -4073,7 +4111,7 @@ Key details: ${keyDetails}`;
|
|
|
4073
4111
|
return false;
|
|
4074
4112
|
}
|
|
4075
4113
|
}
|
|
4076
|
-
async function summarizeWithFallbackChain(chatId, targetBackendId) {
|
|
4114
|
+
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend) {
|
|
4077
4115
|
const pairCount = getMessagePairCount(chatId);
|
|
4078
4116
|
if (pairCount < MIN_PAIRS) {
|
|
4079
4117
|
clearLog(chatId);
|
|
@@ -4082,6 +4120,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId) {
|
|
|
4082
4120
|
const entries = getLog(chatId);
|
|
4083
4121
|
if (entries.length === 0) return false;
|
|
4084
4122
|
const tried = /* @__PURE__ */ new Set();
|
|
4123
|
+
if (excludeBackend) {
|
|
4124
|
+
const excluded = getAdapter(excludeBackend);
|
|
4125
|
+
tried.add(`${excluded.id}:${excluded.summarizerModel}`);
|
|
4126
|
+
}
|
|
4085
4127
|
try {
|
|
4086
4128
|
const config2 = getSummarizer(chatId);
|
|
4087
4129
|
if (config2.backend !== "off") {
|
|
@@ -4193,6 +4235,9 @@ Be specific. The new assistant has no prior context. Cover any domain \u2014 per
|
|
|
4193
4235
|
|
|
4194
4236
|
// src/gemini/quota.ts
|
|
4195
4237
|
function classifyGeminiQuota(errorText) {
|
|
4238
|
+
for (const p of CAPACITY_PATTERNS) {
|
|
4239
|
+
if (p.test(errorText)) return "rate_limited";
|
|
4240
|
+
}
|
|
4196
4241
|
for (const p of RATE_LIMITED_PATTERNS) {
|
|
4197
4242
|
if (p.test(errorText)) return "rate_limited";
|
|
4198
4243
|
}
|
|
@@ -4204,7 +4249,7 @@ function classifyGeminiQuota(errorText) {
|
|
|
4204
4249
|
}
|
|
4205
4250
|
return "unknown";
|
|
4206
4251
|
}
|
|
4207
|
-
var RATE_LIMITED_PATTERNS, BILLING_PATTERNS, DAILY_QUOTA_PATTERNS, GEMINI_SLOTS_EXHAUSTED_MSG;
|
|
4252
|
+
var RATE_LIMITED_PATTERNS, BILLING_PATTERNS, DAILY_QUOTA_PATTERNS, CAPACITY_PATTERNS, GEMINI_SLOTS_EXHAUSTED_MSG, GEMINI_NO_SLOTS_FOR_MODE_MSG, GEMINI_ALL_SLOTS_COOLDOWN_MSG;
|
|
4208
4253
|
var init_quota = __esm({
|
|
4209
4254
|
"src/gemini/quota.ts"() {
|
|
4210
4255
|
"use strict";
|
|
@@ -4227,7 +4272,13 @@ var init_quota = __esm({
|
|
|
4227
4272
|
/RESOURCE_EXHAUSTED/,
|
|
4228
4273
|
/exhausted.*quota/i
|
|
4229
4274
|
];
|
|
4275
|
+
CAPACITY_PATTERNS = [
|
|
4276
|
+
/MODEL_CAPACITY_EXHAUSTED/,
|
|
4277
|
+
/No capacity available/i
|
|
4278
|
+
];
|
|
4230
4279
|
GEMINI_SLOTS_EXHAUSTED_MSG = "Gemini usage limit \u2014 all credential slots exhausted";
|
|
4280
|
+
GEMINI_NO_SLOTS_FOR_MODE_MSG = "Gemini rotation \u2014 no eligible slots for current rotation mode";
|
|
4281
|
+
GEMINI_ALL_SLOTS_COOLDOWN_MSG = "Gemini rotation \u2014 all eligible slots in cooldown";
|
|
4231
4282
|
}
|
|
4232
4283
|
});
|
|
4233
4284
|
|
|
@@ -6737,22 +6788,23 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
6737
6788
|
});
|
|
6738
6789
|
});
|
|
6739
6790
|
}
|
|
6740
|
-
async function spawnGeminiWithRotation(chatId, adapter,
|
|
6791
|
+
async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation) {
|
|
6741
6792
|
const geminiAdapter = adapter;
|
|
6742
|
-
const slots = getEligibleGeminiSlots();
|
|
6793
|
+
const slots = getEligibleGeminiSlots(rotationMode);
|
|
6743
6794
|
if (slots.length === 0) {
|
|
6744
|
-
return spawnQuery(adapter,
|
|
6795
|
+
return spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts);
|
|
6745
6796
|
}
|
|
6746
6797
|
const maxAttempts = Math.min(slots.length, 10);
|
|
6747
6798
|
let lastError;
|
|
6748
6799
|
for (let i = 0; i < maxAttempts; i++) {
|
|
6749
|
-
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0);
|
|
6800
|
+
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0, rotationMode);
|
|
6750
6801
|
if (!slot) break;
|
|
6751
6802
|
const slotLabel = slot.label || `slot-${slot.id}`;
|
|
6752
6803
|
log(`[agent:gemini-rotation] Trying ${slotLabel} (${slot.slotType}, attempt ${i + 1}/${maxAttempts})`);
|
|
6753
6804
|
if (i === 0) pinChatGeminiSlot(chatId, slot.id);
|
|
6805
|
+
const effectiveConfig = i === 0 ? configWithSession : baseConfig;
|
|
6754
6806
|
try {
|
|
6755
|
-
const result = await spawnQuery(adapter,
|
|
6807
|
+
const result = await spawnQuery(adapter, effectiveConfig, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
6756
6808
|
const combinedText = result.resultText || "";
|
|
6757
6809
|
if (combinedText && /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(combinedText)) {
|
|
6758
6810
|
throw new Error(combinedText);
|
|
@@ -6770,11 +6822,11 @@ async function spawnGeminiWithRotation(chatId, adapter, config2, model2, cancelS
|
|
|
6770
6822
|
markSlotExhausted(slot.id, effectiveClass);
|
|
6771
6823
|
lastError = err instanceof Error ? err : new Error(errMsg);
|
|
6772
6824
|
try {
|
|
6773
|
-
await summarizeWithFallbackChain(chatId);
|
|
6825
|
+
await summarizeWithFallbackChain(chatId, void 0, "gemini");
|
|
6774
6826
|
clearSession(chatId);
|
|
6775
6827
|
} catch {
|
|
6776
6828
|
}
|
|
6777
|
-
const nextSlot = getEligibleGeminiSlots()[0];
|
|
6829
|
+
const nextSlot = getEligibleGeminiSlots(rotationMode)[0];
|
|
6778
6830
|
if (nextSlot && onSlotRotation) {
|
|
6779
6831
|
const nextLabel = nextSlot.label || `slot-${nextSlot.id}`;
|
|
6780
6832
|
onSlotRotation(slotLabel, nextLabel);
|
|
@@ -6782,6 +6834,20 @@ async function spawnGeminiWithRotation(chatId, adapter, config2, model2, cancelS
|
|
|
6782
6834
|
}
|
|
6783
6835
|
}
|
|
6784
6836
|
}
|
|
6837
|
+
const allSlotsForMode = getGeminiSlots().filter((s) => {
|
|
6838
|
+
if (!s.enabled) return false;
|
|
6839
|
+
if (rotationMode === "accounts") return s.slotType === "oauth";
|
|
6840
|
+
if (rotationMode === "keys") return s.slotType === "api_key";
|
|
6841
|
+
return true;
|
|
6842
|
+
});
|
|
6843
|
+
if (allSlotsForMode.length === 0) {
|
|
6844
|
+
throw new Error(GEMINI_NO_SLOTS_FOR_MODE_MSG);
|
|
6845
|
+
}
|
|
6846
|
+
const soonest = allSlotsForMode.filter((s) => s.cooldownUntil).map((s) => new Date(s.cooldownUntil).getTime()).sort((a, b) => a - b)[0];
|
|
6847
|
+
if (soonest) {
|
|
6848
|
+
const hoursLeft = Math.max(1, Math.ceil((soonest - Date.now()) / 36e5));
|
|
6849
|
+
throw new Error(`${GEMINI_ALL_SLOTS_COOLDOWN_MSG}|${hoursLeft}h`);
|
|
6850
|
+
}
|
|
6785
6851
|
throw new Error(GEMINI_SLOTS_EXHAUSTED_MSG);
|
|
6786
6852
|
}
|
|
6787
6853
|
function askAgent(chatId, userMessage, opts) {
|
|
@@ -6833,11 +6899,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6833
6899
|
const spawnOpts = { onStream, onToolAction, onSubagentActivity };
|
|
6834
6900
|
const resolvedModel = model2 ?? adapter.defaultModel;
|
|
6835
6901
|
let result = { resultText: "", sessionId: void 0, input: 0, output: 0, cacheRead: 0, sawToolEvents: false, sawResultEvent: false };
|
|
6836
|
-
const
|
|
6902
|
+
const rotationMode = adapter.id === "gemini" ? getGeminiRotationMode() : "off";
|
|
6903
|
+
const useGeminiRotation = rotationMode !== "off" && getEligibleGeminiSlots(rotationMode).length > 0;
|
|
6837
6904
|
try {
|
|
6838
6905
|
if (useGeminiRotation) {
|
|
6839
6906
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
6840
|
-
result = await spawnGeminiWithRotation(chatId, adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts, rotationCb);
|
|
6907
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
|
|
6841
6908
|
} else {
|
|
6842
6909
|
result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
6843
6910
|
}
|
|
@@ -6848,7 +6915,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6848
6915
|
clearSession(chatId);
|
|
6849
6916
|
if (useGeminiRotation) {
|
|
6850
6917
|
const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
|
|
6851
|
-
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts, rotationCb);
|
|
6918
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
|
|
6852
6919
|
} else {
|
|
6853
6920
|
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
6854
6921
|
}
|
|
@@ -6877,7 +6944,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6877
6944
|
if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
|
|
6878
6945
|
log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
|
|
6879
6946
|
summarizeWithFallbackChain(chatId).then((saved) => {
|
|
6880
|
-
if (saved)
|
|
6947
|
+
if (saved) {
|
|
6948
|
+
clearSession(chatId);
|
|
6949
|
+
opts?.onCompaction?.(chatId);
|
|
6950
|
+
}
|
|
6881
6951
|
}).catch((err) => {
|
|
6882
6952
|
warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
|
|
6883
6953
|
});
|
|
@@ -10588,7 +10658,7 @@ async function handleCommand(msg, channel) {
|
|
|
10588
10658
|
case "help":
|
|
10589
10659
|
await channel.sendText(
|
|
10590
10660
|
chatId,
|
|
10591
|
-
"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/model_signature - Toggle model+thinking signature on responses\n/intent <msg> - Test intent classifier (chat vs agentic)\n/agents - List active sub-agents\n/agents mode [auto|native|claw] - Set agent mode (native vs orchestrated)\n/agents history - Native sub-agent activity (24h)\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",
|
|
10661
|
+
"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/gemini_accounts - Manage Gemini credentials & rotation\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/model_signature - Toggle model+thinking signature on responses\n/intent <msg> - Test intent classifier (chat vs agentic)\n/agents - List active sub-agents\n/agents mode [auto|native|claw] - Set agent mode (native vs orchestrated)\n/agents history - Native sub-agent activity (24h)\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",
|
|
10592
10662
|
"plain"
|
|
10593
10663
|
);
|
|
10594
10664
|
break;
|
|
@@ -10751,12 +10821,24 @@ Tap to toggle:`,
|
|
|
10751
10821
|
const iStats = getIntentStats();
|
|
10752
10822
|
const iTotal = iStats.chat + iStats.agentic;
|
|
10753
10823
|
const intentLine = iTotal > 0 ? `\u26A1 ${iStats.chat} chat \xB7 ${iStats.agentic} agentic (${(iStats.chat / iTotal * 100).toFixed(0)}% fast-path)` : `\u26A1 No messages classified yet`;
|
|
10824
|
+
let geminiSlotInfo = "";
|
|
10825
|
+
if ((backendId ?? "claude") === "gemini") {
|
|
10826
|
+
try {
|
|
10827
|
+
const active = getNextGeminiSlot(chatId);
|
|
10828
|
+
const totalSlots = getEligibleGeminiSlots().length;
|
|
10829
|
+
if (active) {
|
|
10830
|
+
const label2 = active.label || `slot-${active.id}`;
|
|
10831
|
+
geminiSlotInfo = ` \xB7 \u{1F511} ${label2} (${totalSlots} slots)`;
|
|
10832
|
+
}
|
|
10833
|
+
} catch {
|
|
10834
|
+
}
|
|
10835
|
+
}
|
|
10754
10836
|
const lines = [
|
|
10755
10837
|
`\u{1F43E} CC-Claw v${VERSION}`,
|
|
10756
10838
|
`\u23F1 Uptime: ${uptimeStr}`,
|
|
10757
10839
|
``,
|
|
10758
10840
|
`\u2501\u2501 Engine \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
|
|
10759
|
-
`\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}`,
|
|
10841
|
+
`\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}${geminiSlotInfo}`,
|
|
10760
10842
|
`\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
|
|
10761
10843
|
`\u{1F916} Agents: ${getAgentMode(chatId)}`,
|
|
10762
10844
|
`\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
|
|
@@ -10800,7 +10882,6 @@ Tap to toggle:`,
|
|
|
10800
10882
|
break;
|
|
10801
10883
|
}
|
|
10802
10884
|
case "claude":
|
|
10803
|
-
case "gemini":
|
|
10804
10885
|
case "codex": {
|
|
10805
10886
|
const backendId = command;
|
|
10806
10887
|
if (getAllBackendIds().includes(backendId)) {
|
|
@@ -10810,6 +10891,56 @@ Tap to toggle:`,
|
|
|
10810
10891
|
}
|
|
10811
10892
|
break;
|
|
10812
10893
|
}
|
|
10894
|
+
case "gemini_accounts": {
|
|
10895
|
+
const slots = getGeminiSlots();
|
|
10896
|
+
if (slots.length === 0) {
|
|
10897
|
+
await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", "html");
|
|
10898
|
+
break;
|
|
10899
|
+
}
|
|
10900
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10901
|
+
const currentMode = getGeminiRotationMode();
|
|
10902
|
+
const pinnedSlotId = getChatGeminiSlotId(chatId);
|
|
10903
|
+
const slotButtons = slots.filter((s) => s.enabled).map((s) => {
|
|
10904
|
+
const label2 = s.label || `slot-${s.id}`;
|
|
10905
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
10906
|
+
const marker = pinnedSlotId === s.id ? " \u2713" : "";
|
|
10907
|
+
return { label: `${icon} ${label2}${marker}`, data: `gslot:${s.id}` };
|
|
10908
|
+
});
|
|
10909
|
+
const rows = [];
|
|
10910
|
+
for (let i = 0; i < slotButtons.length; i += 2) {
|
|
10911
|
+
rows.push(slotButtons.slice(i, i + 2));
|
|
10912
|
+
}
|
|
10913
|
+
rows.push([{ label: "\u{1F504} Auto (rotation)", data: "gslot:auto" }]);
|
|
10914
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only", keys: "\u{1F511} Only" };
|
|
10915
|
+
const modeButtons = ["off", "all", "accounts", "keys"].map((m) => ({
|
|
10916
|
+
label: `${m === currentMode ? "\u2713 " : ""}${modeLabels[m]}`,
|
|
10917
|
+
data: `grotation:${m}`
|
|
10918
|
+
}));
|
|
10919
|
+
rows.push(modeButtons);
|
|
10920
|
+
await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
|
|
10921
|
+
} else {
|
|
10922
|
+
const currentMode = getGeminiRotationMode();
|
|
10923
|
+
const list = slots.filter((s) => s.enabled).map((s) => {
|
|
10924
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
10925
|
+
return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
|
|
10926
|
+
}).join("\n");
|
|
10927
|
+
await channel.sendText(chatId, `Slots:
|
|
10928
|
+
${list}
|
|
10929
|
+
|
|
10930
|
+
Rotation mode: ${currentMode}
|
|
10931
|
+
Use: /gemini_accounts <name> to pin`, "plain");
|
|
10932
|
+
}
|
|
10933
|
+
break;
|
|
10934
|
+
}
|
|
10935
|
+
case "gemini": {
|
|
10936
|
+
const backendId = command;
|
|
10937
|
+
if (getAllBackendIds().includes(backendId)) {
|
|
10938
|
+
await sendBackendSwitchConfirmation(chatId, backendId, channel);
|
|
10939
|
+
} else {
|
|
10940
|
+
await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
|
|
10941
|
+
}
|
|
10942
|
+
break;
|
|
10943
|
+
}
|
|
10813
10944
|
case "model": {
|
|
10814
10945
|
let adapter;
|
|
10815
10946
|
try {
|
|
@@ -12125,7 +12256,12 @@ async function handleText(msg, channel) {
|
|
|
12125
12256
|
});
|
|
12126
12257
|
},
|
|
12127
12258
|
onSlotRotation: (cid, from, to) => {
|
|
12128
|
-
|
|
12259
|
+
const slots = getGeminiSlots();
|
|
12260
|
+
const fromSlot = slots.find((s) => (s.label || `slot-${s.id}`) === from);
|
|
12261
|
+
const toSlot = slots.find((s) => (s.label || `slot-${s.id}`) === to);
|
|
12262
|
+
const fromIcon = fromSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12263
|
+
const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12264
|
+
channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, "plain").catch(() => {
|
|
12129
12265
|
});
|
|
12130
12266
|
}
|
|
12131
12267
|
});
|
|
@@ -12165,6 +12301,32 @@ async function handleText(msg, channel) {
|
|
|
12165
12301
|
} catch (err) {
|
|
12166
12302
|
error("[router] Error:", err);
|
|
12167
12303
|
const errMsg = errorMessage(err);
|
|
12304
|
+
if (errMsg.includes(GEMINI_NO_SLOTS_FOR_MODE_MSG)) {
|
|
12305
|
+
const mode = getGeminiRotationMode();
|
|
12306
|
+
const modeLabel = mode === "accounts" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only" : mode === "keys" ? "\u{1F511} Only" : mode;
|
|
12307
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12308
|
+
const rows = [
|
|
12309
|
+
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
12310
|
+
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
12311
|
+
];
|
|
12312
|
+
await channel.sendKeyboard(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}). Switch backend?`, rows);
|
|
12313
|
+
} else {
|
|
12314
|
+
await channel.sendText(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}).`, "plain");
|
|
12315
|
+
}
|
|
12316
|
+
return;
|
|
12317
|
+
} else if (errMsg.includes(GEMINI_ALL_SLOTS_COOLDOWN_MSG)) {
|
|
12318
|
+
const timeLeft = errMsg.split("|")[1] || "?";
|
|
12319
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12320
|
+
const rows = [
|
|
12321
|
+
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
12322
|
+
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
12323
|
+
];
|
|
12324
|
+
await channel.sendKeyboard(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}. Switch backend?`, rows);
|
|
12325
|
+
} else {
|
|
12326
|
+
await channel.sendText(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}.`, "plain");
|
|
12327
|
+
}
|
|
12328
|
+
return;
|
|
12329
|
+
}
|
|
12168
12330
|
const errorClass = classifyError(err);
|
|
12169
12331
|
if (errorClass === "exhausted") {
|
|
12170
12332
|
if (await handleResponseExhaustion(errMsg, chatId, msg, channel)) return;
|
|
@@ -12499,7 +12661,7 @@ async function doBackendSwitch(chatId, backendId, channel) {
|
|
|
12499
12661
|
}
|
|
12500
12662
|
if (summarized) {
|
|
12501
12663
|
await channel.sendText(chatId, "\u{1F4BE} Context saved \u2014 session summarized to memory.", "plain");
|
|
12502
|
-
} else if (
|
|
12664
|
+
} else if (bridge) {
|
|
12503
12665
|
await channel.sendText(chatId, "\u{1F4AC} Context preserved.", "plain");
|
|
12504
12666
|
}
|
|
12505
12667
|
clearSession(chatId);
|
|
@@ -12771,6 +12933,73 @@ ${PERM_MODES[chosen]}`,
|
|
|
12771
12933
|
await channel.sendText(chatId, `Agent mode set to <b>${mode}</b>. Session cleared.`, "html");
|
|
12772
12934
|
}
|
|
12773
12935
|
return;
|
|
12936
|
+
} else if (data.startsWith("grotation:")) {
|
|
12937
|
+
const mode = data.split(":")[1];
|
|
12938
|
+
const validModes = ["off", "all", "accounts", "keys"];
|
|
12939
|
+
if (!validModes.includes(mode)) return;
|
|
12940
|
+
if (mode === "accounts") {
|
|
12941
|
+
const oauthSlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "oauth");
|
|
12942
|
+
if (oauthSlots.length === 0) {
|
|
12943
|
+
await channel.sendText(chatId, "\u26A0\uFE0F No OAuth accounts configured. Add one with <code>cc-claw gemini add-account</code> or choose a different mode.", "html");
|
|
12944
|
+
return;
|
|
12945
|
+
}
|
|
12946
|
+
} else if (mode === "keys") {
|
|
12947
|
+
const keySlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "api_key");
|
|
12948
|
+
if (keySlots.length === 0) {
|
|
12949
|
+
await channel.sendText(chatId, "\u26A0\uFE0F No API keys configured. Add one with <code>cc-claw gemini add-key</code> or choose a different mode.", "html");
|
|
12950
|
+
return;
|
|
12951
|
+
}
|
|
12952
|
+
}
|
|
12953
|
+
setGeminiRotationMode(mode);
|
|
12954
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Accounts only", keys: "\u{1F511} Keys only" };
|
|
12955
|
+
await channel.sendText(chatId, `Rotation mode set to <b>${modeLabels[mode]}</b>.`, "html");
|
|
12956
|
+
return;
|
|
12957
|
+
} else if (data.startsWith("gslot:")) {
|
|
12958
|
+
const val = data.split(":")[1];
|
|
12959
|
+
if (val === "auto") {
|
|
12960
|
+
clearChatGeminiSlot(chatId);
|
|
12961
|
+
await channel.sendText(chatId, "Gemini slot set to <b>\u{1F504} auto rotation</b>.", "html");
|
|
12962
|
+
} else {
|
|
12963
|
+
const slotId = parseInt(val, 10);
|
|
12964
|
+
const slots = getGeminiSlots();
|
|
12965
|
+
const slot = slots.find((s) => s.id === slotId);
|
|
12966
|
+
if (slot) {
|
|
12967
|
+
pinChatGeminiSlot(chatId, slotId);
|
|
12968
|
+
const label2 = slot.label || `slot-${slot.id}`;
|
|
12969
|
+
const icon = slot.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12970
|
+
await channel.sendText(chatId, `Pinned to ${icon} <b>${label2}</b>`, "html");
|
|
12971
|
+
}
|
|
12972
|
+
}
|
|
12973
|
+
return;
|
|
12974
|
+
} else if (data === "gopen:accounts") {
|
|
12975
|
+
const slots = getGeminiSlots();
|
|
12976
|
+
if (slots.length === 0) {
|
|
12977
|
+
await channel.sendText(chatId, "No Gemini credentials configured.\nAdd with: <code>cc-claw gemini add-key</code> or <code>cc-claw gemini add-account</code>", "html");
|
|
12978
|
+
return;
|
|
12979
|
+
}
|
|
12980
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12981
|
+
const currentMode = getGeminiRotationMode();
|
|
12982
|
+
const pinnedSlotId = getChatGeminiSlotId(chatId);
|
|
12983
|
+
const slotButtons = slots.filter((s) => s.enabled).map((s) => {
|
|
12984
|
+
const label2 = s.label || `slot-${s.id}`;
|
|
12985
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12986
|
+
const marker = pinnedSlotId === s.id ? " \u2713" : "";
|
|
12987
|
+
return { label: `${icon} ${label2}${marker}`, data: `gslot:${s.id}` };
|
|
12988
|
+
});
|
|
12989
|
+
const rows = [];
|
|
12990
|
+
for (let i = 0; i < slotButtons.length; i += 2) {
|
|
12991
|
+
rows.push(slotButtons.slice(i, i + 2));
|
|
12992
|
+
}
|
|
12993
|
+
rows.push([{ label: "\u{1F504} Auto (rotation)", data: "gslot:auto" }]);
|
|
12994
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only", keys: "\u{1F511} Only" };
|
|
12995
|
+
const modeButtons = ["off", "all", "accounts", "keys"].map((m) => ({
|
|
12996
|
+
label: `${m === currentMode ? "\u2713 " : ""}${modeLabels[m]}`,
|
|
12997
|
+
data: `grotation:${m}`
|
|
12998
|
+
}));
|
|
12999
|
+
rows.push(modeButtons);
|
|
13000
|
+
await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
|
|
13001
|
+
}
|
|
13002
|
+
return;
|
|
12774
13003
|
} else if (data.startsWith("model_sig:")) {
|
|
12775
13004
|
const value = data.slice(10);
|
|
12776
13005
|
setModelSignature(chatId, value);
|
|
@@ -13033,6 +13262,7 @@ var init_router = __esm({
|
|
|
13033
13262
|
init_format_time();
|
|
13034
13263
|
init_agent();
|
|
13035
13264
|
init_retry();
|
|
13265
|
+
init_quota();
|
|
13036
13266
|
init_classify();
|
|
13037
13267
|
init_version();
|
|
13038
13268
|
init_image_gen();
|
|
@@ -13855,22 +14085,16 @@ async function main() {
|
|
|
13855
14085
|
initDatabase();
|
|
13856
14086
|
pruneMessageLog(30, 2e3);
|
|
13857
14087
|
bootstrapBuiltinMcps(getDb());
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
14088
|
+
let pendingSummarizeNotify = null;
|
|
14089
|
+
const { getLoggedChatIds: getLoggedChatIds2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
|
|
14090
|
+
const pendingCount = getLoggedChatIds2().length;
|
|
14091
|
+
if (pendingCount > 0) {
|
|
14092
|
+
summarizeAllPending().then(() => {
|
|
14093
|
+
log(`[cc-claw] Background summarization complete (${pendingCount} session(s))`);
|
|
14094
|
+
if (pendingSummarizeNotify) pendingSummarizeNotify();
|
|
14095
|
+
}).catch((err) => {
|
|
14096
|
+
log(`[cc-claw] Background summarization failed: ${err}`);
|
|
13863
14097
|
});
|
|
13864
|
-
try {
|
|
13865
|
-
await Promise.race([
|
|
13866
|
-
summarizeAllPending(),
|
|
13867
|
-
timeoutPromise
|
|
13868
|
-
]);
|
|
13869
|
-
} finally {
|
|
13870
|
-
clearTimeout(timer);
|
|
13871
|
-
}
|
|
13872
|
-
} catch {
|
|
13873
|
-
log("[cc-claw] Session summarization skipped (timeout or backend unavailable)");
|
|
13874
14098
|
}
|
|
13875
14099
|
setBootTime();
|
|
13876
14100
|
log("[cc-claw] Database initialized (sessions preserved for resume)");
|
|
@@ -13883,6 +14107,17 @@ async function main() {
|
|
|
13883
14107
|
}
|
|
13884
14108
|
await channelRegistry.startAll(handleMessage);
|
|
13885
14109
|
log("[cc-claw] Channels started");
|
|
14110
|
+
if (pendingCount > 0) {
|
|
14111
|
+
const primaryChatId = (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim();
|
|
14112
|
+
pendingSummarizeNotify = () => {
|
|
14113
|
+
if (primaryChatId) {
|
|
14114
|
+
for (const ch of channelRegistry.list()) {
|
|
14115
|
+
ch.sendText(primaryChatId, "\u{1F504} Restarted \u2014 your conversations were summarized and saved.", "plain").catch(() => {
|
|
14116
|
+
});
|
|
14117
|
+
}
|
|
14118
|
+
}
|
|
14119
|
+
};
|
|
14120
|
+
}
|
|
13886
14121
|
const telegramChannel = channelRegistry.get("telegram");
|
|
13887
14122
|
if (telegramChannel && typeof telegramChannel.onCallback === "function") {
|
|
13888
14123
|
telegramChannel.onCallback(handleCallback);
|
|
@@ -14787,8 +15022,8 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
14787
15022
|
}
|
|
14788
15023
|
if (existsSync20(ERROR_LOG_PATH)) {
|
|
14789
15024
|
try {
|
|
14790
|
-
const { readFileSync:
|
|
14791
|
-
const logContent =
|
|
15025
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
15026
|
+
const logContent = readFileSync19(ERROR_LOG_PATH, "utf-8");
|
|
14792
15027
|
const recentLines = logContent.split("\n").filter(Boolean).slice(-100);
|
|
14793
15028
|
const last24h = Date.now() - 864e5;
|
|
14794
15029
|
const recentErrors = recentLines.filter((line) => {
|
|
@@ -14963,9 +15198,10 @@ __export(gemini_exports, {
|
|
|
14963
15198
|
geminiEnable: () => geminiEnable,
|
|
14964
15199
|
geminiList: () => geminiList,
|
|
14965
15200
|
geminiRemove: () => geminiRemove,
|
|
14966
|
-
geminiReorder: () => geminiReorder
|
|
15201
|
+
geminiReorder: () => geminiReorder,
|
|
15202
|
+
geminiRotation: () => geminiRotation
|
|
14967
15203
|
});
|
|
14968
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, chmodSync } from "fs";
|
|
15204
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, readFileSync as readFileSync15, chmodSync } from "fs";
|
|
14969
15205
|
import { join as join20 } from "path";
|
|
14970
15206
|
import { createInterface as createInterface4 } from "readline";
|
|
14971
15207
|
function requireDb() {
|
|
@@ -14982,12 +15218,32 @@ async function requireWriteDb() {
|
|
|
14982
15218
|
dbInitialized = true;
|
|
14983
15219
|
}
|
|
14984
15220
|
}
|
|
15221
|
+
async function resolveSlotId(idOrLabel) {
|
|
15222
|
+
const numeric = parseInt(idOrLabel, 10);
|
|
15223
|
+
if (!isNaN(numeric)) return numeric;
|
|
15224
|
+
const { getGeminiSlots: getGeminiSlots2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15225
|
+
const match = getGeminiSlots2().find(
|
|
15226
|
+
(s) => s.label?.toLowerCase() === idOrLabel.toLowerCase()
|
|
15227
|
+
);
|
|
15228
|
+
return match?.id ?? null;
|
|
15229
|
+
}
|
|
15230
|
+
function resolveOAuthEmail(configHome) {
|
|
15231
|
+
if (!configHome) return null;
|
|
15232
|
+
try {
|
|
15233
|
+
const accountsPath = join20(configHome, ".gemini", "google_accounts.json");
|
|
15234
|
+
if (!existsSync22(accountsPath)) return null;
|
|
15235
|
+
const accounts = JSON.parse(readFileSync15(accountsPath, "utf-8"));
|
|
15236
|
+
return accounts.active || null;
|
|
15237
|
+
} catch {
|
|
15238
|
+
return null;
|
|
15239
|
+
}
|
|
15240
|
+
}
|
|
14985
15241
|
async function geminiList(globalOpts) {
|
|
14986
15242
|
requireDb();
|
|
14987
15243
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
14988
15244
|
const readDb = openDatabaseReadOnly2();
|
|
14989
15245
|
const slots = readDb.prepare(`
|
|
14990
|
-
SELECT id, slot_type, label, priority, enabled, cooldown_until, last_used, consecutive_errors, created_at
|
|
15246
|
+
SELECT id, slot_type, label, config_home, priority, enabled, cooldown_until, last_used, consecutive_errors, created_at
|
|
14991
15247
|
FROM gemini_credentials ORDER BY priority ASC, id ASC
|
|
14992
15248
|
`).all();
|
|
14993
15249
|
readDb.close();
|
|
@@ -14995,7 +15251,11 @@ async function geminiList(globalOpts) {
|
|
|
14995
15251
|
output({ slots: [] }, () => "No Gemini credential slots configured.\nAdd one with: cc-claw gemini add-key or cc-claw gemini add-account");
|
|
14996
15252
|
return;
|
|
14997
15253
|
}
|
|
14998
|
-
|
|
15254
|
+
const enriched = slots.map((s) => ({
|
|
15255
|
+
...s,
|
|
15256
|
+
email: s.slot_type === "oauth" ? resolveOAuthEmail(s.config_home) : null
|
|
15257
|
+
}));
|
|
15258
|
+
output(enriched, (data) => {
|
|
14999
15259
|
const list = data;
|
|
15000
15260
|
const lines = ["", divider("Gemini Credential Slots"), ""];
|
|
15001
15261
|
for (const s of list) {
|
|
@@ -15003,8 +15263,9 @@ async function geminiList(globalOpts) {
|
|
|
15003
15263
|
const inCooldown = s.cooldown_until && s.cooldown_until > now;
|
|
15004
15264
|
const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
|
|
15005
15265
|
const label2 = s.label || `slot-${s.id}`;
|
|
15006
|
-
const type = s.slot_type === "oauth" ? "
|
|
15007
|
-
|
|
15266
|
+
const type = s.slot_type === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
15267
|
+
const emailStr = s.email ? ` ${muted(s.email)}` : "";
|
|
15268
|
+
lines.push(` ${icon} ${label2}${emailStr} ${muted(`(${type}, #${s.id}, priority ${s.priority})`)}`);
|
|
15008
15269
|
if (inCooldown) lines.push(` Cooldown until: ${warning(s.cooldown_until)}`);
|
|
15009
15270
|
if (s.consecutive_errors > 0) lines.push(` Consecutive errors: ${warning(String(s.consecutive_errors))}`);
|
|
15010
15271
|
if (s.last_used) lines.push(` Last used: ${muted(s.last_used)}`);
|
|
@@ -15023,6 +15284,22 @@ async function geminiAddKey(globalOpts, opts) {
|
|
|
15023
15284
|
outputError("EMPTY_KEY", "No key provided.");
|
|
15024
15285
|
process.exit(1);
|
|
15025
15286
|
}
|
|
15287
|
+
console.log(" Validating API key...");
|
|
15288
|
+
try {
|
|
15289
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key.trim())}`);
|
|
15290
|
+
if (!res.ok) {
|
|
15291
|
+
const body = await res.text().catch(() => "");
|
|
15292
|
+
if (res.status === 400 || res.status === 403) {
|
|
15293
|
+
outputError("INVALID_KEY", `API key is invalid or unauthorized (HTTP ${res.status}).`);
|
|
15294
|
+
process.exit(1);
|
|
15295
|
+
}
|
|
15296
|
+
console.log(warning(` Warning: validation returned HTTP ${res.status} \u2014 saving anyway. ${body.slice(0, 100)}`));
|
|
15297
|
+
} else {
|
|
15298
|
+
console.log(success(" \u2713 API key is valid."));
|
|
15299
|
+
}
|
|
15300
|
+
} catch (err) {
|
|
15301
|
+
console.log(warning(` Warning: could not validate key (network error) \u2014 saving anyway.`));
|
|
15302
|
+
}
|
|
15026
15303
|
const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15027
15304
|
const id = addGeminiSlot2({
|
|
15028
15305
|
slotType: "api_key",
|
|
@@ -15087,37 +15364,78 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
15087
15364
|
Added OAuth slot #${id} (${accountEmail})`)
|
|
15088
15365
|
);
|
|
15089
15366
|
}
|
|
15090
|
-
async function geminiRemove(globalOpts,
|
|
15367
|
+
async function geminiRemove(globalOpts, idOrLabel) {
|
|
15091
15368
|
await requireWriteDb();
|
|
15369
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15370
|
+
if (!slotId) {
|
|
15371
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15372
|
+
return;
|
|
15373
|
+
}
|
|
15092
15374
|
const { removeGeminiSlot: removeGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15093
|
-
const removed = removeGeminiSlot2(
|
|
15375
|
+
const removed = removeGeminiSlot2(slotId);
|
|
15094
15376
|
if (removed) {
|
|
15095
|
-
output({ removed: true, id:
|
|
15377
|
+
output({ removed: true, id: slotId }, () => success(`Removed slot "${idOrLabel}" (#${slotId})`));
|
|
15096
15378
|
} else {
|
|
15097
|
-
outputError("NOT_FOUND", `Slot
|
|
15379
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15098
15380
|
}
|
|
15099
15381
|
}
|
|
15100
|
-
async function geminiEnable(globalOpts,
|
|
15382
|
+
async function geminiEnable(globalOpts, idOrLabel) {
|
|
15101
15383
|
await requireWriteDb();
|
|
15384
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15385
|
+
if (!slotId) {
|
|
15386
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15387
|
+
return;
|
|
15388
|
+
}
|
|
15102
15389
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15103
|
-
setGeminiSlotEnabled2(
|
|
15104
|
-
output({ id:
|
|
15390
|
+
setGeminiSlotEnabled2(slotId, true);
|
|
15391
|
+
output({ id: slotId, enabled: true }, () => success(`Enabled slot "${idOrLabel}" (#${slotId})`));
|
|
15105
15392
|
}
|
|
15106
|
-
async function geminiDisable(globalOpts,
|
|
15393
|
+
async function geminiDisable(globalOpts, idOrLabel) {
|
|
15107
15394
|
await requireWriteDb();
|
|
15395
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15396
|
+
if (!slotId) {
|
|
15397
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15398
|
+
return;
|
|
15399
|
+
}
|
|
15108
15400
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15109
|
-
setGeminiSlotEnabled2(
|
|
15110
|
-
output({ id:
|
|
15401
|
+
setGeminiSlotEnabled2(slotId, false);
|
|
15402
|
+
output({ id: slotId, enabled: false }, () => warning(`Disabled slot "${idOrLabel}" (#${slotId})`));
|
|
15111
15403
|
}
|
|
15112
|
-
async function geminiReorder(globalOpts,
|
|
15404
|
+
async function geminiReorder(globalOpts, idOrLabel, priority) {
|
|
15113
15405
|
await requireWriteDb();
|
|
15406
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15407
|
+
if (!slotId) {
|
|
15408
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15409
|
+
return;
|
|
15410
|
+
}
|
|
15114
15411
|
const { reorderGeminiSlot: reorderGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15115
|
-
reorderGeminiSlot2(
|
|
15412
|
+
reorderGeminiSlot2(slotId, parseInt(priority, 10));
|
|
15116
15413
|
output(
|
|
15117
|
-
{ id:
|
|
15118
|
-
() => success(`Slot #${
|
|
15414
|
+
{ id: slotId, priority: parseInt(priority, 10) },
|
|
15415
|
+
() => success(`Slot "${idOrLabel}" (#${slotId}) priority set to ${priority}`)
|
|
15119
15416
|
);
|
|
15120
15417
|
}
|
|
15418
|
+
async function geminiRotation(globalOpts, mode) {
|
|
15419
|
+
const validModes = ["off", "all", "accounts", "keys"];
|
|
15420
|
+
if (!mode) {
|
|
15421
|
+
requireDb();
|
|
15422
|
+
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15423
|
+
const readDb = openDatabaseReadOnly2();
|
|
15424
|
+
const row = readDb.prepare("SELECT value FROM meta WHERE key = 'gemini_rotation_mode'").get();
|
|
15425
|
+
readDb.close();
|
|
15426
|
+
const current = row?.value ?? "all";
|
|
15427
|
+
output({ mode: current }, () => `Gemini rotation mode: ${success(current)}`);
|
|
15428
|
+
return;
|
|
15429
|
+
}
|
|
15430
|
+
if (!validModes.includes(mode)) {
|
|
15431
|
+
outputError("INVALID_MODE", `Invalid mode: "${mode}". Valid modes: ${validModes.join(", ")}`);
|
|
15432
|
+
process.exit(1);
|
|
15433
|
+
}
|
|
15434
|
+
await requireWriteDb();
|
|
15435
|
+
const { setGeminiRotationMode: setGeminiRotationMode2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15436
|
+
setGeminiRotationMode2(mode);
|
|
15437
|
+
output({ mode }, () => success(`Gemini rotation mode set to "${mode}"`));
|
|
15438
|
+
}
|
|
15121
15439
|
var dbInitialized;
|
|
15122
15440
|
var init_gemini2 = __esm({
|
|
15123
15441
|
"src/cli/commands/gemini.ts"() {
|
|
@@ -16116,7 +16434,7 @@ __export(config_exports, {
|
|
|
16116
16434
|
configList: () => configList,
|
|
16117
16435
|
configSet: () => configSet
|
|
16118
16436
|
});
|
|
16119
|
-
import { existsSync as existsSync30, readFileSync as
|
|
16437
|
+
import { existsSync as existsSync30, readFileSync as readFileSync16 } from "fs";
|
|
16120
16438
|
async function configList(globalOpts) {
|
|
16121
16439
|
if (!existsSync30(DB_PATH)) {
|
|
16122
16440
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
@@ -16202,7 +16520,7 @@ async function configEnv(_globalOpts) {
|
|
|
16202
16520
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
16203
16521
|
process.exit(1);
|
|
16204
16522
|
}
|
|
16205
|
-
const content =
|
|
16523
|
+
const content = readFileSync16(ENV_PATH, "utf-8");
|
|
16206
16524
|
const entries = {};
|
|
16207
16525
|
const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
|
|
16208
16526
|
for (const line of content.split("\n")) {
|
|
@@ -16860,11 +17178,11 @@ __export(chat_exports, {
|
|
|
16860
17178
|
chatSend: () => chatSend
|
|
16861
17179
|
});
|
|
16862
17180
|
import { request as httpRequest2 } from "http";
|
|
16863
|
-
import { readFileSync as
|
|
17181
|
+
import { readFileSync as readFileSync17, existsSync as existsSync38 } from "fs";
|
|
16864
17182
|
function getToken2() {
|
|
16865
17183
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
16866
17184
|
try {
|
|
16867
|
-
if (existsSync38(TOKEN_PATH2)) return
|
|
17185
|
+
if (existsSync38(TOKEN_PATH2)) return readFileSync17(TOKEN_PATH2, "utf-8").trim();
|
|
16868
17186
|
} catch {
|
|
16869
17187
|
}
|
|
16870
17188
|
return null;
|
|
@@ -17290,7 +17608,7 @@ var init_completion = __esm({
|
|
|
17290
17608
|
|
|
17291
17609
|
// src/setup.ts
|
|
17292
17610
|
var setup_exports = {};
|
|
17293
|
-
import { existsSync as existsSync39, writeFileSync as writeFileSync8, readFileSync as
|
|
17611
|
+
import { existsSync as existsSync39, writeFileSync as writeFileSync8, readFileSync as readFileSync18, copyFileSync as copyFileSync3, mkdirSync as mkdirSync11, statSync as statSync6 } from "fs";
|
|
17294
17612
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
17295
17613
|
import { createInterface as createInterface6 } from "readline";
|
|
17296
17614
|
import { join as join21 } from "path";
|
|
@@ -17374,7 +17692,7 @@ async function setup() {
|
|
|
17374
17692
|
if (envSource) {
|
|
17375
17693
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
17376
17694
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
17377
|
-
const existing =
|
|
17695
|
+
const existing = readFileSync18(envSource, "utf-8");
|
|
17378
17696
|
for (const line of existing.split("\n")) {
|
|
17379
17697
|
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
17380
17698
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
@@ -17732,22 +18050,26 @@ gemini.command("add-account").description("Add an OAuth account slot (opens brow
|
|
|
17732
18050
|
const { geminiAddAccount: geminiAddAccount2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17733
18051
|
await geminiAddAccount2(program.opts(), opts);
|
|
17734
18052
|
});
|
|
17735
|
-
gemini.command("remove <id>").description("Remove a credential slot").action(async (id) => {
|
|
18053
|
+
gemini.command("remove <id-or-label>").description("Remove a credential slot (by ID or label)").action(async (id) => {
|
|
17736
18054
|
const { geminiRemove: geminiRemove2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17737
18055
|
await geminiRemove2(program.opts(), id);
|
|
17738
18056
|
});
|
|
17739
|
-
gemini.command("enable <id>").description("Re-enable a disabled slot").action(async (id) => {
|
|
18057
|
+
gemini.command("enable <id-or-label>").description("Re-enable a disabled slot (by ID or label)").action(async (id) => {
|
|
17740
18058
|
const { geminiEnable: geminiEnable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17741
18059
|
await geminiEnable2(program.opts(), id);
|
|
17742
18060
|
});
|
|
17743
|
-
gemini.command("disable <id>").description("Disable a slot (
|
|
18061
|
+
gemini.command("disable <id-or-label>").description("Disable a slot (by ID or label)").action(async (id) => {
|
|
17744
18062
|
const { geminiDisable: geminiDisable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17745
18063
|
await geminiDisable2(program.opts(), id);
|
|
17746
18064
|
});
|
|
17747
|
-
gemini.command("reorder <id> <priority>").description("Set slot priority (lower = preferred)").action(async (id, priority) => {
|
|
18065
|
+
gemini.command("reorder <id-or-label> <priority>").description("Set slot priority (by ID or label, lower = preferred)").action(async (id, priority) => {
|
|
17748
18066
|
const { geminiReorder: geminiReorder2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17749
18067
|
await geminiReorder2(program.opts(), id, priority);
|
|
17750
18068
|
});
|
|
18069
|
+
gemini.command("rotation [mode]").description("Get or set rotation mode (off, all, accounts, keys)").action(async (mode) => {
|
|
18070
|
+
const { geminiRotation: geminiRotation2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
18071
|
+
await geminiRotation2(program.opts(), mode);
|
|
18072
|
+
});
|
|
17751
18073
|
var backend = program.command("backend").description("Manage AI backend");
|
|
17752
18074
|
backend.command("list").description("Available backends with status").action(async () => {
|
|
17753
18075
|
const { backendList: backendList2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
package/package.json
CHANGED