cc-claw 0.6.0 → 0.7.0
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 +364 -66
- 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.0" : (() => {
|
|
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) {
|
|
@@ -4073,7 +4098,7 @@ Key details: ${keyDetails}`;
|
|
|
4073
4098
|
return false;
|
|
4074
4099
|
}
|
|
4075
4100
|
}
|
|
4076
|
-
async function summarizeWithFallbackChain(chatId, targetBackendId) {
|
|
4101
|
+
async function summarizeWithFallbackChain(chatId, targetBackendId, excludeBackend) {
|
|
4077
4102
|
const pairCount = getMessagePairCount(chatId);
|
|
4078
4103
|
if (pairCount < MIN_PAIRS) {
|
|
4079
4104
|
clearLog(chatId);
|
|
@@ -4082,6 +4107,10 @@ async function summarizeWithFallbackChain(chatId, targetBackendId) {
|
|
|
4082
4107
|
const entries = getLog(chatId);
|
|
4083
4108
|
if (entries.length === 0) return false;
|
|
4084
4109
|
const tried = /* @__PURE__ */ new Set();
|
|
4110
|
+
if (excludeBackend) {
|
|
4111
|
+
const excluded = getAdapter(excludeBackend);
|
|
4112
|
+
tried.add(`${excluded.id}:${excluded.summarizerModel}`);
|
|
4113
|
+
}
|
|
4085
4114
|
try {
|
|
4086
4115
|
const config2 = getSummarizer(chatId);
|
|
4087
4116
|
if (config2.backend !== "off") {
|
|
@@ -4193,6 +4222,9 @@ Be specific. The new assistant has no prior context. Cover any domain \u2014 per
|
|
|
4193
4222
|
|
|
4194
4223
|
// src/gemini/quota.ts
|
|
4195
4224
|
function classifyGeminiQuota(errorText) {
|
|
4225
|
+
for (const p of CAPACITY_PATTERNS) {
|
|
4226
|
+
if (p.test(errorText)) return "rate_limited";
|
|
4227
|
+
}
|
|
4196
4228
|
for (const p of RATE_LIMITED_PATTERNS) {
|
|
4197
4229
|
if (p.test(errorText)) return "rate_limited";
|
|
4198
4230
|
}
|
|
@@ -4204,7 +4236,7 @@ function classifyGeminiQuota(errorText) {
|
|
|
4204
4236
|
}
|
|
4205
4237
|
return "unknown";
|
|
4206
4238
|
}
|
|
4207
|
-
var RATE_LIMITED_PATTERNS, BILLING_PATTERNS, DAILY_QUOTA_PATTERNS, GEMINI_SLOTS_EXHAUSTED_MSG;
|
|
4239
|
+
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
4240
|
var init_quota = __esm({
|
|
4209
4241
|
"src/gemini/quota.ts"() {
|
|
4210
4242
|
"use strict";
|
|
@@ -4227,7 +4259,13 @@ var init_quota = __esm({
|
|
|
4227
4259
|
/RESOURCE_EXHAUSTED/,
|
|
4228
4260
|
/exhausted.*quota/i
|
|
4229
4261
|
];
|
|
4262
|
+
CAPACITY_PATTERNS = [
|
|
4263
|
+
/MODEL_CAPACITY_EXHAUSTED/,
|
|
4264
|
+
/No capacity available/i
|
|
4265
|
+
];
|
|
4230
4266
|
GEMINI_SLOTS_EXHAUSTED_MSG = "Gemini usage limit \u2014 all credential slots exhausted";
|
|
4267
|
+
GEMINI_NO_SLOTS_FOR_MODE_MSG = "Gemini rotation \u2014 no eligible slots for current rotation mode";
|
|
4268
|
+
GEMINI_ALL_SLOTS_COOLDOWN_MSG = "Gemini rotation \u2014 all eligible slots in cooldown";
|
|
4231
4269
|
}
|
|
4232
4270
|
});
|
|
4233
4271
|
|
|
@@ -6737,22 +6775,23 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
|
|
|
6737
6775
|
});
|
|
6738
6776
|
});
|
|
6739
6777
|
}
|
|
6740
|
-
async function spawnGeminiWithRotation(chatId, adapter,
|
|
6778
|
+
async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation) {
|
|
6741
6779
|
const geminiAdapter = adapter;
|
|
6742
|
-
const slots = getEligibleGeminiSlots();
|
|
6780
|
+
const slots = getEligibleGeminiSlots(rotationMode);
|
|
6743
6781
|
if (slots.length === 0) {
|
|
6744
|
-
return spawnQuery(adapter,
|
|
6782
|
+
return spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts);
|
|
6745
6783
|
}
|
|
6746
6784
|
const maxAttempts = Math.min(slots.length, 10);
|
|
6747
6785
|
let lastError;
|
|
6748
6786
|
for (let i = 0; i < maxAttempts; i++) {
|
|
6749
|
-
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0);
|
|
6787
|
+
const { env, slot } = geminiAdapter.getEnvForSlot(chatId, void 0, rotationMode);
|
|
6750
6788
|
if (!slot) break;
|
|
6751
6789
|
const slotLabel = slot.label || `slot-${slot.id}`;
|
|
6752
6790
|
log(`[agent:gemini-rotation] Trying ${slotLabel} (${slot.slotType}, attempt ${i + 1}/${maxAttempts})`);
|
|
6753
6791
|
if (i === 0) pinChatGeminiSlot(chatId, slot.id);
|
|
6792
|
+
const effectiveConfig = i === 0 ? configWithSession : baseConfig;
|
|
6754
6793
|
try {
|
|
6755
|
-
const result = await spawnQuery(adapter,
|
|
6794
|
+
const result = await spawnQuery(adapter, effectiveConfig, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
|
|
6756
6795
|
const combinedText = result.resultText || "";
|
|
6757
6796
|
if (combinedText && /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(combinedText)) {
|
|
6758
6797
|
throw new Error(combinedText);
|
|
@@ -6770,11 +6809,11 @@ async function spawnGeminiWithRotation(chatId, adapter, config2, model2, cancelS
|
|
|
6770
6809
|
markSlotExhausted(slot.id, effectiveClass);
|
|
6771
6810
|
lastError = err instanceof Error ? err : new Error(errMsg);
|
|
6772
6811
|
try {
|
|
6773
|
-
await summarizeWithFallbackChain(chatId);
|
|
6812
|
+
await summarizeWithFallbackChain(chatId, void 0, "gemini");
|
|
6774
6813
|
clearSession(chatId);
|
|
6775
6814
|
} catch {
|
|
6776
6815
|
}
|
|
6777
|
-
const nextSlot = getEligibleGeminiSlots()[0];
|
|
6816
|
+
const nextSlot = getEligibleGeminiSlots(rotationMode)[0];
|
|
6778
6817
|
if (nextSlot && onSlotRotation) {
|
|
6779
6818
|
const nextLabel = nextSlot.label || `slot-${nextSlot.id}`;
|
|
6780
6819
|
onSlotRotation(slotLabel, nextLabel);
|
|
@@ -6782,6 +6821,20 @@ async function spawnGeminiWithRotation(chatId, adapter, config2, model2, cancelS
|
|
|
6782
6821
|
}
|
|
6783
6822
|
}
|
|
6784
6823
|
}
|
|
6824
|
+
const allSlotsForMode = getGeminiSlots().filter((s) => {
|
|
6825
|
+
if (!s.enabled) return false;
|
|
6826
|
+
if (rotationMode === "accounts") return s.slotType === "oauth";
|
|
6827
|
+
if (rotationMode === "keys") return s.slotType === "api_key";
|
|
6828
|
+
return true;
|
|
6829
|
+
});
|
|
6830
|
+
if (allSlotsForMode.length === 0) {
|
|
6831
|
+
throw new Error(GEMINI_NO_SLOTS_FOR_MODE_MSG);
|
|
6832
|
+
}
|
|
6833
|
+
const soonest = allSlotsForMode.filter((s) => s.cooldownUntil).map((s) => new Date(s.cooldownUntil).getTime()).sort((a, b) => a - b)[0];
|
|
6834
|
+
if (soonest) {
|
|
6835
|
+
const hoursLeft = Math.max(1, Math.ceil((soonest - Date.now()) / 36e5));
|
|
6836
|
+
throw new Error(`${GEMINI_ALL_SLOTS_COOLDOWN_MSG}|${hoursLeft}h`);
|
|
6837
|
+
}
|
|
6785
6838
|
throw new Error(GEMINI_SLOTS_EXHAUSTED_MSG);
|
|
6786
6839
|
}
|
|
6787
6840
|
function askAgent(chatId, userMessage, opts) {
|
|
@@ -6833,11 +6886,12 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6833
6886
|
const spawnOpts = { onStream, onToolAction, onSubagentActivity };
|
|
6834
6887
|
const resolvedModel = model2 ?? adapter.defaultModel;
|
|
6835
6888
|
let result = { resultText: "", sessionId: void 0, input: 0, output: 0, cacheRead: 0, sawToolEvents: false, sawResultEvent: false };
|
|
6836
|
-
const
|
|
6889
|
+
const rotationMode = adapter.id === "gemini" ? getGeminiRotationMode() : "off";
|
|
6890
|
+
const useGeminiRotation = rotationMode !== "off" && getEligibleGeminiSlots(rotationMode).length > 0;
|
|
6837
6891
|
try {
|
|
6838
6892
|
if (useGeminiRotation) {
|
|
6839
6893
|
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);
|
|
6894
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
|
|
6841
6895
|
} else {
|
|
6842
6896
|
result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
6843
6897
|
}
|
|
@@ -6848,7 +6902,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6848
6902
|
clearSession(chatId);
|
|
6849
6903
|
if (useGeminiRotation) {
|
|
6850
6904
|
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);
|
|
6905
|
+
result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb);
|
|
6852
6906
|
} else {
|
|
6853
6907
|
result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
|
|
6854
6908
|
}
|
|
@@ -6877,7 +6931,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
|
|
|
6877
6931
|
if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
|
|
6878
6932
|
log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
|
|
6879
6933
|
summarizeWithFallbackChain(chatId).then((saved) => {
|
|
6880
|
-
if (saved)
|
|
6934
|
+
if (saved) {
|
|
6935
|
+
clearSession(chatId);
|
|
6936
|
+
opts?.onCompaction?.(chatId);
|
|
6937
|
+
}
|
|
6881
6938
|
}).catch((err) => {
|
|
6882
6939
|
warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
|
|
6883
6940
|
});
|
|
@@ -10588,7 +10645,7 @@ async function handleCommand(msg, channel) {
|
|
|
10588
10645
|
case "help":
|
|
10589
10646
|
await channel.sendText(
|
|
10590
10647
|
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",
|
|
10648
|
+
"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 slot - Select Gemini credential (account/key)\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
10649
|
"plain"
|
|
10593
10650
|
);
|
|
10594
10651
|
break;
|
|
@@ -10751,12 +10808,24 @@ Tap to toggle:`,
|
|
|
10751
10808
|
const iStats = getIntentStats();
|
|
10752
10809
|
const iTotal = iStats.chat + iStats.agentic;
|
|
10753
10810
|
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`;
|
|
10811
|
+
let geminiSlotInfo = "";
|
|
10812
|
+
if ((backendId ?? "claude") === "gemini") {
|
|
10813
|
+
try {
|
|
10814
|
+
const active = getNextGeminiSlot(chatId);
|
|
10815
|
+
const totalSlots = getEligibleGeminiSlots().length;
|
|
10816
|
+
if (active) {
|
|
10817
|
+
const label2 = active.label || `slot-${active.id}`;
|
|
10818
|
+
geminiSlotInfo = ` \xB7 \u{1F511} ${label2} (${totalSlots} slots)`;
|
|
10819
|
+
}
|
|
10820
|
+
} catch {
|
|
10821
|
+
}
|
|
10822
|
+
}
|
|
10754
10823
|
const lines = [
|
|
10755
10824
|
`\u{1F43E} CC-Claw v${VERSION}`,
|
|
10756
10825
|
`\u23F1 Uptime: ${uptimeStr}`,
|
|
10757
10826
|
``,
|
|
10758
10827
|
`\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)}`,
|
|
10828
|
+
`\u{1F9E0} ${adapter?.displayName ?? backendId ?? "not set"} \xB7 ${formatModelShort(model2)}${geminiSlotInfo}`,
|
|
10760
10829
|
`\u{1F4AD} Think: ${thinking2} \xB7 Mode: ${mode}`,
|
|
10761
10830
|
`\u{1F916} Agents: ${getAgentMode(chatId)}`,
|
|
10762
10831
|
`\u{1F507} Voice: ${voice2 ? "on" : "off"} \xB7 Sig: ${modelSig}`,
|
|
@@ -10800,7 +10869,6 @@ Tap to toggle:`,
|
|
|
10800
10869
|
break;
|
|
10801
10870
|
}
|
|
10802
10871
|
case "claude":
|
|
10803
|
-
case "gemini":
|
|
10804
10872
|
case "codex": {
|
|
10805
10873
|
const backendId = command;
|
|
10806
10874
|
if (getAllBackendIds().includes(backendId)) {
|
|
@@ -10810,6 +10878,56 @@ Tap to toggle:`,
|
|
|
10810
10878
|
}
|
|
10811
10879
|
break;
|
|
10812
10880
|
}
|
|
10881
|
+
case "gemini_accounts": {
|
|
10882
|
+
const slots = getGeminiSlots();
|
|
10883
|
+
if (slots.length === 0) {
|
|
10884
|
+
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");
|
|
10885
|
+
break;
|
|
10886
|
+
}
|
|
10887
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
10888
|
+
const currentMode = getGeminiRotationMode();
|
|
10889
|
+
const pinnedSlotId = getChatGeminiSlotId(chatId);
|
|
10890
|
+
const slotButtons = slots.filter((s) => s.enabled).map((s) => {
|
|
10891
|
+
const label2 = s.label || `slot-${s.id}`;
|
|
10892
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
10893
|
+
const marker = pinnedSlotId === s.id ? " \u2713" : "";
|
|
10894
|
+
return { label: `${icon} ${label2}${marker}`, data: `gslot:${s.id}` };
|
|
10895
|
+
});
|
|
10896
|
+
const rows = [];
|
|
10897
|
+
for (let i = 0; i < slotButtons.length; i += 2) {
|
|
10898
|
+
rows.push(slotButtons.slice(i, i + 2));
|
|
10899
|
+
}
|
|
10900
|
+
rows.push([{ label: "\u{1F504} Auto (rotation)", data: "gslot:auto" }]);
|
|
10901
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only", keys: "\u{1F511} Only" };
|
|
10902
|
+
const modeButtons = ["off", "all", "accounts", "keys"].map((m) => ({
|
|
10903
|
+
label: `${m === currentMode ? "\u2713 " : ""}${modeLabels[m]}`,
|
|
10904
|
+
data: `grotation:${m}`
|
|
10905
|
+
}));
|
|
10906
|
+
rows.push(modeButtons);
|
|
10907
|
+
await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
|
|
10908
|
+
} else {
|
|
10909
|
+
const currentMode = getGeminiRotationMode();
|
|
10910
|
+
const list = slots.filter((s) => s.enabled).map((s) => {
|
|
10911
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
10912
|
+
return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
|
|
10913
|
+
}).join("\n");
|
|
10914
|
+
await channel.sendText(chatId, `Slots:
|
|
10915
|
+
${list}
|
|
10916
|
+
|
|
10917
|
+
Rotation mode: ${currentMode}
|
|
10918
|
+
Use: /gemini_accounts <name> to pin`, "plain");
|
|
10919
|
+
}
|
|
10920
|
+
break;
|
|
10921
|
+
}
|
|
10922
|
+
case "gemini": {
|
|
10923
|
+
const backendId = command;
|
|
10924
|
+
if (getAllBackendIds().includes(backendId)) {
|
|
10925
|
+
await sendBackendSwitchConfirmation(chatId, backendId, channel);
|
|
10926
|
+
} else {
|
|
10927
|
+
await channel.sendText(chatId, `Backend "${command}" is not available.`, "plain");
|
|
10928
|
+
}
|
|
10929
|
+
break;
|
|
10930
|
+
}
|
|
10813
10931
|
case "model": {
|
|
10814
10932
|
let adapter;
|
|
10815
10933
|
try {
|
|
@@ -12125,7 +12243,12 @@ async function handleText(msg, channel) {
|
|
|
12125
12243
|
});
|
|
12126
12244
|
},
|
|
12127
12245
|
onSlotRotation: (cid, from, to) => {
|
|
12128
|
-
|
|
12246
|
+
const slots = getGeminiSlots();
|
|
12247
|
+
const fromSlot = slots.find((s) => (s.label || `slot-${s.id}`) === from);
|
|
12248
|
+
const toSlot = slots.find((s) => (s.label || `slot-${s.id}`) === to);
|
|
12249
|
+
const fromIcon = fromSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12250
|
+
const toIcon = toSlot?.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12251
|
+
channel.sendText(cid, `\u26A0\uFE0F Quota reached on ${fromIcon} ${from} \u2014 switched to ${toIcon} ${to}. Context saved.`, "plain").catch(() => {
|
|
12129
12252
|
});
|
|
12130
12253
|
}
|
|
12131
12254
|
});
|
|
@@ -12165,6 +12288,32 @@ async function handleText(msg, channel) {
|
|
|
12165
12288
|
} catch (err) {
|
|
12166
12289
|
error("[router] Error:", err);
|
|
12167
12290
|
const errMsg = errorMessage(err);
|
|
12291
|
+
if (errMsg.includes(GEMINI_NO_SLOTS_FOR_MODE_MSG)) {
|
|
12292
|
+
const mode = getGeminiRotationMode();
|
|
12293
|
+
const modeLabel = mode === "accounts" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only" : mode === "keys" ? "\u{1F511} Only" : mode;
|
|
12294
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12295
|
+
const rows = [
|
|
12296
|
+
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
12297
|
+
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
12298
|
+
];
|
|
12299
|
+
await channel.sendKeyboard(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}). Switch backend?`, rows);
|
|
12300
|
+
} else {
|
|
12301
|
+
await channel.sendText(chatId, `\u26A0\uFE0F No eligible slots for rotation mode (${modeLabel}).`, "plain");
|
|
12302
|
+
}
|
|
12303
|
+
return;
|
|
12304
|
+
} else if (errMsg.includes(GEMINI_ALL_SLOTS_COOLDOWN_MSG)) {
|
|
12305
|
+
const timeLeft = errMsg.split("|")[1] || "?";
|
|
12306
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12307
|
+
const rows = [
|
|
12308
|
+
[{ label: "Claude", data: "backend:claude" }, { label: "Codex", data: "backend:codex" }],
|
|
12309
|
+
[{ label: "\u2699\uFE0F Change rotation mode", data: "gopen:accounts" }]
|
|
12310
|
+
];
|
|
12311
|
+
await channel.sendKeyboard(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}. Switch backend?`, rows);
|
|
12312
|
+
} else {
|
|
12313
|
+
await channel.sendText(chatId, `\u26A0\uFE0F All eligible slots in cooldown. Next available in ~${timeLeft}.`, "plain");
|
|
12314
|
+
}
|
|
12315
|
+
return;
|
|
12316
|
+
}
|
|
12168
12317
|
const errorClass = classifyError(err);
|
|
12169
12318
|
if (errorClass === "exhausted") {
|
|
12170
12319
|
if (await handleResponseExhaustion(errMsg, chatId, msg, channel)) return;
|
|
@@ -12771,6 +12920,73 @@ ${PERM_MODES[chosen]}`,
|
|
|
12771
12920
|
await channel.sendText(chatId, `Agent mode set to <b>${mode}</b>. Session cleared.`, "html");
|
|
12772
12921
|
}
|
|
12773
12922
|
return;
|
|
12923
|
+
} else if (data.startsWith("grotation:")) {
|
|
12924
|
+
const mode = data.split(":")[1];
|
|
12925
|
+
const validModes = ["off", "all", "accounts", "keys"];
|
|
12926
|
+
if (!validModes.includes(mode)) return;
|
|
12927
|
+
if (mode === "accounts") {
|
|
12928
|
+
const oauthSlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "oauth");
|
|
12929
|
+
if (oauthSlots.length === 0) {
|
|
12930
|
+
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");
|
|
12931
|
+
return;
|
|
12932
|
+
}
|
|
12933
|
+
} else if (mode === "keys") {
|
|
12934
|
+
const keySlots = getGeminiSlots().filter((s) => s.enabled && s.slotType === "api_key");
|
|
12935
|
+
if (keySlots.length === 0) {
|
|
12936
|
+
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");
|
|
12937
|
+
return;
|
|
12938
|
+
}
|
|
12939
|
+
}
|
|
12940
|
+
setGeminiRotationMode(mode);
|
|
12941
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Accounts only", keys: "\u{1F511} Keys only" };
|
|
12942
|
+
await channel.sendText(chatId, `Rotation mode set to <b>${modeLabels[mode]}</b>.`, "html");
|
|
12943
|
+
return;
|
|
12944
|
+
} else if (data.startsWith("gslot:")) {
|
|
12945
|
+
const val = data.split(":")[1];
|
|
12946
|
+
if (val === "auto") {
|
|
12947
|
+
clearChatGeminiSlot(chatId);
|
|
12948
|
+
await channel.sendText(chatId, "Gemini slot set to <b>\u{1F504} auto rotation</b>.", "html");
|
|
12949
|
+
} else {
|
|
12950
|
+
const slotId = parseInt(val, 10);
|
|
12951
|
+
const slots = getGeminiSlots();
|
|
12952
|
+
const slot = slots.find((s) => s.id === slotId);
|
|
12953
|
+
if (slot) {
|
|
12954
|
+
pinChatGeminiSlot(chatId, slotId);
|
|
12955
|
+
const label2 = slot.label || `slot-${slot.id}`;
|
|
12956
|
+
const icon = slot.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12957
|
+
await channel.sendText(chatId, `Pinned to ${icon} <b>${label2}</b>`, "html");
|
|
12958
|
+
}
|
|
12959
|
+
}
|
|
12960
|
+
return;
|
|
12961
|
+
} else if (data === "gopen:accounts") {
|
|
12962
|
+
const slots = getGeminiSlots();
|
|
12963
|
+
if (slots.length === 0) {
|
|
12964
|
+
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");
|
|
12965
|
+
return;
|
|
12966
|
+
}
|
|
12967
|
+
if (typeof channel.sendKeyboard === "function") {
|
|
12968
|
+
const currentMode = getGeminiRotationMode();
|
|
12969
|
+
const pinnedSlotId = getChatGeminiSlotId(chatId);
|
|
12970
|
+
const slotButtons = slots.filter((s) => s.enabled).map((s) => {
|
|
12971
|
+
const label2 = s.label || `slot-${s.id}`;
|
|
12972
|
+
const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
12973
|
+
const marker = pinnedSlotId === s.id ? " \u2713" : "";
|
|
12974
|
+
return { label: `${icon} ${label2}${marker}`, data: `gslot:${s.id}` };
|
|
12975
|
+
});
|
|
12976
|
+
const rows = [];
|
|
12977
|
+
for (let i = 0; i < slotButtons.length; i += 2) {
|
|
12978
|
+
rows.push(slotButtons.slice(i, i + 2));
|
|
12979
|
+
}
|
|
12980
|
+
rows.push([{ label: "\u{1F504} Auto (rotation)", data: "gslot:auto" }]);
|
|
12981
|
+
const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only", keys: "\u{1F511} Only" };
|
|
12982
|
+
const modeButtons = ["off", "all", "accounts", "keys"].map((m) => ({
|
|
12983
|
+
label: `${m === currentMode ? "\u2713 " : ""}${modeLabels[m]}`,
|
|
12984
|
+
data: `grotation:${m}`
|
|
12985
|
+
}));
|
|
12986
|
+
rows.push(modeButtons);
|
|
12987
|
+
await channel.sendKeyboard(chatId, "Gemini Accounts & Rotation:", rows);
|
|
12988
|
+
}
|
|
12989
|
+
return;
|
|
12774
12990
|
} else if (data.startsWith("model_sig:")) {
|
|
12775
12991
|
const value = data.slice(10);
|
|
12776
12992
|
setModelSignature(chatId, value);
|
|
@@ -13033,6 +13249,7 @@ var init_router = __esm({
|
|
|
13033
13249
|
init_format_time();
|
|
13034
13250
|
init_agent();
|
|
13035
13251
|
init_retry();
|
|
13252
|
+
init_quota();
|
|
13036
13253
|
init_classify();
|
|
13037
13254
|
init_version();
|
|
13038
13255
|
init_image_gen();
|
|
@@ -14787,8 +15004,8 @@ async function doctorCommand(globalOpts, localOpts) {
|
|
|
14787
15004
|
}
|
|
14788
15005
|
if (existsSync20(ERROR_LOG_PATH)) {
|
|
14789
15006
|
try {
|
|
14790
|
-
const { readFileSync:
|
|
14791
|
-
const logContent =
|
|
15007
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
15008
|
+
const logContent = readFileSync19(ERROR_LOG_PATH, "utf-8");
|
|
14792
15009
|
const recentLines = logContent.split("\n").filter(Boolean).slice(-100);
|
|
14793
15010
|
const last24h = Date.now() - 864e5;
|
|
14794
15011
|
const recentErrors = recentLines.filter((line) => {
|
|
@@ -14963,9 +15180,10 @@ __export(gemini_exports, {
|
|
|
14963
15180
|
geminiEnable: () => geminiEnable,
|
|
14964
15181
|
geminiList: () => geminiList,
|
|
14965
15182
|
geminiRemove: () => geminiRemove,
|
|
14966
|
-
geminiReorder: () => geminiReorder
|
|
15183
|
+
geminiReorder: () => geminiReorder,
|
|
15184
|
+
geminiRotation: () => geminiRotation
|
|
14967
15185
|
});
|
|
14968
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, chmodSync } from "fs";
|
|
15186
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7, readFileSync as readFileSync15, chmodSync } from "fs";
|
|
14969
15187
|
import { join as join20 } from "path";
|
|
14970
15188
|
import { createInterface as createInterface4 } from "readline";
|
|
14971
15189
|
function requireDb() {
|
|
@@ -14974,12 +15192,40 @@ function requireDb() {
|
|
|
14974
15192
|
process.exit(1);
|
|
14975
15193
|
}
|
|
14976
15194
|
}
|
|
15195
|
+
async function requireWriteDb() {
|
|
15196
|
+
requireDb();
|
|
15197
|
+
if (!dbInitialized) {
|
|
15198
|
+
const { initDatabase: initDatabase2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15199
|
+
initDatabase2();
|
|
15200
|
+
dbInitialized = true;
|
|
15201
|
+
}
|
|
15202
|
+
}
|
|
15203
|
+
async function resolveSlotId(idOrLabel) {
|
|
15204
|
+
const numeric = parseInt(idOrLabel, 10);
|
|
15205
|
+
if (!isNaN(numeric)) return numeric;
|
|
15206
|
+
const { getGeminiSlots: getGeminiSlots2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15207
|
+
const match = getGeminiSlots2().find(
|
|
15208
|
+
(s) => s.label?.toLowerCase() === idOrLabel.toLowerCase()
|
|
15209
|
+
);
|
|
15210
|
+
return match?.id ?? null;
|
|
15211
|
+
}
|
|
15212
|
+
function resolveOAuthEmail(configHome) {
|
|
15213
|
+
if (!configHome) return null;
|
|
15214
|
+
try {
|
|
15215
|
+
const accountsPath = join20(configHome, ".gemini", "google_accounts.json");
|
|
15216
|
+
if (!existsSync22(accountsPath)) return null;
|
|
15217
|
+
const accounts = JSON.parse(readFileSync15(accountsPath, "utf-8"));
|
|
15218
|
+
return accounts.active || null;
|
|
15219
|
+
} catch {
|
|
15220
|
+
return null;
|
|
15221
|
+
}
|
|
15222
|
+
}
|
|
14977
15223
|
async function geminiList(globalOpts) {
|
|
14978
15224
|
requireDb();
|
|
14979
15225
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
14980
15226
|
const readDb = openDatabaseReadOnly2();
|
|
14981
15227
|
const slots = readDb.prepare(`
|
|
14982
|
-
SELECT id, slot_type, label, priority, enabled, cooldown_until, last_used, consecutive_errors, created_at
|
|
15228
|
+
SELECT id, slot_type, label, config_home, priority, enabled, cooldown_until, last_used, consecutive_errors, created_at
|
|
14983
15229
|
FROM gemini_credentials ORDER BY priority ASC, id ASC
|
|
14984
15230
|
`).all();
|
|
14985
15231
|
readDb.close();
|
|
@@ -14987,7 +15233,11 @@ async function geminiList(globalOpts) {
|
|
|
14987
15233
|
output({ slots: [] }, () => "No Gemini credential slots configured.\nAdd one with: cc-claw gemini add-key or cc-claw gemini add-account");
|
|
14988
15234
|
return;
|
|
14989
15235
|
}
|
|
14990
|
-
|
|
15236
|
+
const enriched = slots.map((s) => ({
|
|
15237
|
+
...s,
|
|
15238
|
+
email: s.slot_type === "oauth" ? resolveOAuthEmail(s.config_home) : null
|
|
15239
|
+
}));
|
|
15240
|
+
output(enriched, (data) => {
|
|
14991
15241
|
const list = data;
|
|
14992
15242
|
const lines = ["", divider("Gemini Credential Slots"), ""];
|
|
14993
15243
|
for (const s of list) {
|
|
@@ -14995,8 +15245,9 @@ async function geminiList(globalOpts) {
|
|
|
14995
15245
|
const inCooldown = s.cooldown_until && s.cooldown_until > now;
|
|
14996
15246
|
const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
|
|
14997
15247
|
const label2 = s.label || `slot-${s.id}`;
|
|
14998
|
-
const type = s.slot_type === "oauth" ? "
|
|
14999
|
-
|
|
15248
|
+
const type = s.slot_type === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
|
|
15249
|
+
const emailStr = s.email ? ` ${muted(s.email)}` : "";
|
|
15250
|
+
lines.push(` ${icon} ${label2}${emailStr} ${muted(`(${type}, #${s.id}, priority ${s.priority})`)}`);
|
|
15000
15251
|
if (inCooldown) lines.push(` Cooldown until: ${warning(s.cooldown_until)}`);
|
|
15001
15252
|
if (s.consecutive_errors > 0) lines.push(` Consecutive errors: ${warning(String(s.consecutive_errors))}`);
|
|
15002
15253
|
if (s.last_used) lines.push(` Last used: ${muted(s.last_used)}`);
|
|
@@ -15006,7 +15257,7 @@ async function geminiList(globalOpts) {
|
|
|
15006
15257
|
});
|
|
15007
15258
|
}
|
|
15008
15259
|
async function geminiAddKey(globalOpts, opts) {
|
|
15009
|
-
|
|
15260
|
+
await requireWriteDb();
|
|
15010
15261
|
const rl2 = createInterface4({ input: process.stdin, output: process.stdout });
|
|
15011
15262
|
const ask2 = (q) => new Promise((r) => rl2.question(q, r));
|
|
15012
15263
|
const key = await ask2("Paste your Gemini API key: ");
|
|
@@ -15028,7 +15279,7 @@ async function geminiAddKey(globalOpts, opts) {
|
|
|
15028
15279
|
);
|
|
15029
15280
|
}
|
|
15030
15281
|
async function geminiAddAccount(globalOpts, opts) {
|
|
15031
|
-
|
|
15282
|
+
await requireWriteDb();
|
|
15032
15283
|
const slotsDir = join20(CC_CLAW_HOME, "gemini-slots");
|
|
15033
15284
|
if (!existsSync22(slotsDir)) mkdirSync9(slotsDir, { recursive: true });
|
|
15034
15285
|
const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
@@ -15079,42 +15330,85 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
15079
15330
|
Added OAuth slot #${id} (${accountEmail})`)
|
|
15080
15331
|
);
|
|
15081
15332
|
}
|
|
15082
|
-
async function geminiRemove(globalOpts,
|
|
15083
|
-
|
|
15333
|
+
async function geminiRemove(globalOpts, idOrLabel) {
|
|
15334
|
+
await requireWriteDb();
|
|
15335
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15336
|
+
if (!slotId) {
|
|
15337
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15338
|
+
return;
|
|
15339
|
+
}
|
|
15084
15340
|
const { removeGeminiSlot: removeGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15085
|
-
const removed = removeGeminiSlot2(
|
|
15341
|
+
const removed = removeGeminiSlot2(slotId);
|
|
15086
15342
|
if (removed) {
|
|
15087
|
-
output({ removed: true, id:
|
|
15343
|
+
output({ removed: true, id: slotId }, () => success(`Removed slot "${idOrLabel}" (#${slotId})`));
|
|
15088
15344
|
} else {
|
|
15089
|
-
outputError("NOT_FOUND", `Slot
|
|
15345
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15090
15346
|
}
|
|
15091
15347
|
}
|
|
15092
|
-
async function geminiEnable(globalOpts,
|
|
15093
|
-
|
|
15348
|
+
async function geminiEnable(globalOpts, idOrLabel) {
|
|
15349
|
+
await requireWriteDb();
|
|
15350
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15351
|
+
if (!slotId) {
|
|
15352
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15353
|
+
return;
|
|
15354
|
+
}
|
|
15094
15355
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15095
|
-
setGeminiSlotEnabled2(
|
|
15096
|
-
output({ id:
|
|
15097
|
-
}
|
|
15098
|
-
async function geminiDisable(globalOpts,
|
|
15099
|
-
|
|
15356
|
+
setGeminiSlotEnabled2(slotId, true);
|
|
15357
|
+
output({ id: slotId, enabled: true }, () => success(`Enabled slot "${idOrLabel}" (#${slotId})`));
|
|
15358
|
+
}
|
|
15359
|
+
async function geminiDisable(globalOpts, idOrLabel) {
|
|
15360
|
+
await requireWriteDb();
|
|
15361
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15362
|
+
if (!slotId) {
|
|
15363
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15364
|
+
return;
|
|
15365
|
+
}
|
|
15100
15366
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15101
|
-
setGeminiSlotEnabled2(
|
|
15102
|
-
output({ id:
|
|
15103
|
-
}
|
|
15104
|
-
async function geminiReorder(globalOpts,
|
|
15105
|
-
|
|
15367
|
+
setGeminiSlotEnabled2(slotId, false);
|
|
15368
|
+
output({ id: slotId, enabled: false }, () => warning(`Disabled slot "${idOrLabel}" (#${slotId})`));
|
|
15369
|
+
}
|
|
15370
|
+
async function geminiReorder(globalOpts, idOrLabel, priority) {
|
|
15371
|
+
await requireWriteDb();
|
|
15372
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15373
|
+
if (!slotId) {
|
|
15374
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15375
|
+
return;
|
|
15376
|
+
}
|
|
15106
15377
|
const { reorderGeminiSlot: reorderGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15107
|
-
reorderGeminiSlot2(
|
|
15378
|
+
reorderGeminiSlot2(slotId, parseInt(priority, 10));
|
|
15108
15379
|
output(
|
|
15109
|
-
{ id:
|
|
15110
|
-
() => success(`Slot #${
|
|
15380
|
+
{ id: slotId, priority: parseInt(priority, 10) },
|
|
15381
|
+
() => success(`Slot "${idOrLabel}" (#${slotId}) priority set to ${priority}`)
|
|
15111
15382
|
);
|
|
15112
15383
|
}
|
|
15384
|
+
async function geminiRotation(globalOpts, mode) {
|
|
15385
|
+
const validModes = ["off", "all", "accounts", "keys"];
|
|
15386
|
+
if (!mode) {
|
|
15387
|
+
requireDb();
|
|
15388
|
+
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15389
|
+
const readDb = openDatabaseReadOnly2();
|
|
15390
|
+
const row = readDb.prepare("SELECT value FROM meta WHERE key = 'gemini_rotation_mode'").get();
|
|
15391
|
+
readDb.close();
|
|
15392
|
+
const current = row?.value ?? "all";
|
|
15393
|
+
output({ mode: current }, () => `Gemini rotation mode: ${success(current)}`);
|
|
15394
|
+
return;
|
|
15395
|
+
}
|
|
15396
|
+
if (!validModes.includes(mode)) {
|
|
15397
|
+
outputError("INVALID_MODE", `Invalid mode: "${mode}". Valid modes: ${validModes.join(", ")}`);
|
|
15398
|
+
process.exit(1);
|
|
15399
|
+
}
|
|
15400
|
+
await requireWriteDb();
|
|
15401
|
+
const { setGeminiRotationMode: setGeminiRotationMode2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15402
|
+
setGeminiRotationMode2(mode);
|
|
15403
|
+
output({ mode }, () => success(`Gemini rotation mode set to "${mode}"`));
|
|
15404
|
+
}
|
|
15405
|
+
var dbInitialized;
|
|
15113
15406
|
var init_gemini2 = __esm({
|
|
15114
15407
|
"src/cli/commands/gemini.ts"() {
|
|
15115
15408
|
"use strict";
|
|
15116
15409
|
init_format();
|
|
15117
15410
|
init_paths();
|
|
15411
|
+
dbInitialized = false;
|
|
15118
15412
|
}
|
|
15119
15413
|
});
|
|
15120
15414
|
|
|
@@ -16106,7 +16400,7 @@ __export(config_exports, {
|
|
|
16106
16400
|
configList: () => configList,
|
|
16107
16401
|
configSet: () => configSet
|
|
16108
16402
|
});
|
|
16109
|
-
import { existsSync as existsSync30, readFileSync as
|
|
16403
|
+
import { existsSync as existsSync30, readFileSync as readFileSync16 } from "fs";
|
|
16110
16404
|
async function configList(globalOpts) {
|
|
16111
16405
|
if (!existsSync30(DB_PATH)) {
|
|
16112
16406
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
@@ -16192,7 +16486,7 @@ async function configEnv(_globalOpts) {
|
|
|
16192
16486
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
16193
16487
|
process.exit(1);
|
|
16194
16488
|
}
|
|
16195
|
-
const content =
|
|
16489
|
+
const content = readFileSync16(ENV_PATH, "utf-8");
|
|
16196
16490
|
const entries = {};
|
|
16197
16491
|
const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
|
|
16198
16492
|
for (const line of content.split("\n")) {
|
|
@@ -16850,11 +17144,11 @@ __export(chat_exports, {
|
|
|
16850
17144
|
chatSend: () => chatSend
|
|
16851
17145
|
});
|
|
16852
17146
|
import { request as httpRequest2 } from "http";
|
|
16853
|
-
import { readFileSync as
|
|
17147
|
+
import { readFileSync as readFileSync17, existsSync as existsSync38 } from "fs";
|
|
16854
17148
|
function getToken2() {
|
|
16855
17149
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
16856
17150
|
try {
|
|
16857
|
-
if (existsSync38(TOKEN_PATH2)) return
|
|
17151
|
+
if (existsSync38(TOKEN_PATH2)) return readFileSync17(TOKEN_PATH2, "utf-8").trim();
|
|
16858
17152
|
} catch {
|
|
16859
17153
|
}
|
|
16860
17154
|
return null;
|
|
@@ -17280,7 +17574,7 @@ var init_completion = __esm({
|
|
|
17280
17574
|
|
|
17281
17575
|
// src/setup.ts
|
|
17282
17576
|
var setup_exports = {};
|
|
17283
|
-
import { existsSync as existsSync39, writeFileSync as writeFileSync8, readFileSync as
|
|
17577
|
+
import { existsSync as existsSync39, writeFileSync as writeFileSync8, readFileSync as readFileSync18, copyFileSync as copyFileSync3, mkdirSync as mkdirSync11, statSync as statSync6 } from "fs";
|
|
17284
17578
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
17285
17579
|
import { createInterface as createInterface6 } from "readline";
|
|
17286
17580
|
import { join as join21 } from "path";
|
|
@@ -17364,7 +17658,7 @@ async function setup() {
|
|
|
17364
17658
|
if (envSource) {
|
|
17365
17659
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
17366
17660
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
17367
|
-
const existing =
|
|
17661
|
+
const existing = readFileSync18(envSource, "utf-8");
|
|
17368
17662
|
for (const line of existing.split("\n")) {
|
|
17369
17663
|
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
17370
17664
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
@@ -17722,22 +18016,26 @@ gemini.command("add-account").description("Add an OAuth account slot (opens brow
|
|
|
17722
18016
|
const { geminiAddAccount: geminiAddAccount2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17723
18017
|
await geminiAddAccount2(program.opts(), opts);
|
|
17724
18018
|
});
|
|
17725
|
-
gemini.command("remove <id>").description("Remove a credential slot").action(async (id) => {
|
|
18019
|
+
gemini.command("remove <id-or-label>").description("Remove a credential slot (by ID or label)").action(async (id) => {
|
|
17726
18020
|
const { geminiRemove: geminiRemove2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17727
18021
|
await geminiRemove2(program.opts(), id);
|
|
17728
18022
|
});
|
|
17729
|
-
gemini.command("enable <id>").description("Re-enable a disabled slot").action(async (id) => {
|
|
18023
|
+
gemini.command("enable <id-or-label>").description("Re-enable a disabled slot (by ID or label)").action(async (id) => {
|
|
17730
18024
|
const { geminiEnable: geminiEnable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17731
18025
|
await geminiEnable2(program.opts(), id);
|
|
17732
18026
|
});
|
|
17733
|
-
gemini.command("disable <id>").description("Disable a slot (
|
|
18027
|
+
gemini.command("disable <id-or-label>").description("Disable a slot (by ID or label)").action(async (id) => {
|
|
17734
18028
|
const { geminiDisable: geminiDisable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17735
18029
|
await geminiDisable2(program.opts(), id);
|
|
17736
18030
|
});
|
|
17737
|
-
gemini.command("reorder <id> <priority>").description("Set slot priority (lower = preferred)").action(async (id, priority) => {
|
|
18031
|
+
gemini.command("reorder <id-or-label> <priority>").description("Set slot priority (by ID or label, lower = preferred)").action(async (id, priority) => {
|
|
17738
18032
|
const { geminiReorder: geminiReorder2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17739
18033
|
await geminiReorder2(program.opts(), id, priority);
|
|
17740
18034
|
});
|
|
18035
|
+
gemini.command("rotation [mode]").description("Get or set rotation mode (off, all, accounts, keys)").action(async (mode) => {
|
|
18036
|
+
const { geminiRotation: geminiRotation2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
18037
|
+
await geminiRotation2(program.opts(), mode);
|
|
18038
|
+
});
|
|
17741
18039
|
var backend = program.command("backend").description("Manage AI backend");
|
|
17742
18040
|
backend.command("list").description("Available backends with status").action(async () => {
|
|
17743
18041
|
const { backendList: backendList2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
package/package.json
CHANGED