cc-claw 0.6.1 → 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 +346 -58
- 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() {
|
|
@@ -14982,12 +15200,32 @@ async function requireWriteDb() {
|
|
|
14982
15200
|
dbInitialized = true;
|
|
14983
15201
|
}
|
|
14984
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
|
+
}
|
|
14985
15223
|
async function geminiList(globalOpts) {
|
|
14986
15224
|
requireDb();
|
|
14987
15225
|
const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
14988
15226
|
const readDb = openDatabaseReadOnly2();
|
|
14989
15227
|
const slots = readDb.prepare(`
|
|
14990
|
-
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
|
|
14991
15229
|
FROM gemini_credentials ORDER BY priority ASC, id ASC
|
|
14992
15230
|
`).all();
|
|
14993
15231
|
readDb.close();
|
|
@@ -14995,7 +15233,11 @@ async function geminiList(globalOpts) {
|
|
|
14995
15233
|
output({ slots: [] }, () => "No Gemini credential slots configured.\nAdd one with: cc-claw gemini add-key or cc-claw gemini add-account");
|
|
14996
15234
|
return;
|
|
14997
15235
|
}
|
|
14998
|
-
|
|
15236
|
+
const enriched = slots.map((s) => ({
|
|
15237
|
+
...s,
|
|
15238
|
+
email: s.slot_type === "oauth" ? resolveOAuthEmail(s.config_home) : null
|
|
15239
|
+
}));
|
|
15240
|
+
output(enriched, (data) => {
|
|
14999
15241
|
const list = data;
|
|
15000
15242
|
const lines = ["", divider("Gemini Credential Slots"), ""];
|
|
15001
15243
|
for (const s of list) {
|
|
@@ -15003,8 +15245,9 @@ async function geminiList(globalOpts) {
|
|
|
15003
15245
|
const inCooldown = s.cooldown_until && s.cooldown_until > now;
|
|
15004
15246
|
const icon = !s.enabled ? error2("\u25CB disabled") : inCooldown ? warning("\u25D1 cooldown") : success("\u25CF active");
|
|
15005
15247
|
const label2 = s.label || `slot-${s.id}`;
|
|
15006
|
-
const type = s.slot_type === "oauth" ? "
|
|
15007
|
-
|
|
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})`)}`);
|
|
15008
15251
|
if (inCooldown) lines.push(` Cooldown until: ${warning(s.cooldown_until)}`);
|
|
15009
15252
|
if (s.consecutive_errors > 0) lines.push(` Consecutive errors: ${warning(String(s.consecutive_errors))}`);
|
|
15010
15253
|
if (s.last_used) lines.push(` Last used: ${muted(s.last_used)}`);
|
|
@@ -15087,37 +15330,78 @@ async function geminiAddAccount(globalOpts, opts) {
|
|
|
15087
15330
|
Added OAuth slot #${id} (${accountEmail})`)
|
|
15088
15331
|
);
|
|
15089
15332
|
}
|
|
15090
|
-
async function geminiRemove(globalOpts,
|
|
15333
|
+
async function geminiRemove(globalOpts, idOrLabel) {
|
|
15091
15334
|
await requireWriteDb();
|
|
15335
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15336
|
+
if (!slotId) {
|
|
15337
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15338
|
+
return;
|
|
15339
|
+
}
|
|
15092
15340
|
const { removeGeminiSlot: removeGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15093
|
-
const removed = removeGeminiSlot2(
|
|
15341
|
+
const removed = removeGeminiSlot2(slotId);
|
|
15094
15342
|
if (removed) {
|
|
15095
|
-
output({ removed: true, id:
|
|
15343
|
+
output({ removed: true, id: slotId }, () => success(`Removed slot "${idOrLabel}" (#${slotId})`));
|
|
15096
15344
|
} else {
|
|
15097
|
-
outputError("NOT_FOUND", `Slot
|
|
15345
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15098
15346
|
}
|
|
15099
15347
|
}
|
|
15100
|
-
async function geminiEnable(globalOpts,
|
|
15348
|
+
async function geminiEnable(globalOpts, idOrLabel) {
|
|
15101
15349
|
await requireWriteDb();
|
|
15350
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15351
|
+
if (!slotId) {
|
|
15352
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15353
|
+
return;
|
|
15354
|
+
}
|
|
15102
15355
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15103
|
-
setGeminiSlotEnabled2(
|
|
15104
|
-
output({ id:
|
|
15356
|
+
setGeminiSlotEnabled2(slotId, true);
|
|
15357
|
+
output({ id: slotId, enabled: true }, () => success(`Enabled slot "${idOrLabel}" (#${slotId})`));
|
|
15105
15358
|
}
|
|
15106
|
-
async function geminiDisable(globalOpts,
|
|
15359
|
+
async function geminiDisable(globalOpts, idOrLabel) {
|
|
15107
15360
|
await requireWriteDb();
|
|
15361
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15362
|
+
if (!slotId) {
|
|
15363
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15364
|
+
return;
|
|
15365
|
+
}
|
|
15108
15366
|
const { setGeminiSlotEnabled: setGeminiSlotEnabled2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15109
|
-
setGeminiSlotEnabled2(
|
|
15110
|
-
output({ id:
|
|
15367
|
+
setGeminiSlotEnabled2(slotId, false);
|
|
15368
|
+
output({ id: slotId, enabled: false }, () => warning(`Disabled slot "${idOrLabel}" (#${slotId})`));
|
|
15111
15369
|
}
|
|
15112
|
-
async function geminiReorder(globalOpts,
|
|
15370
|
+
async function geminiReorder(globalOpts, idOrLabel, priority) {
|
|
15113
15371
|
await requireWriteDb();
|
|
15372
|
+
const slotId = await resolveSlotId(idOrLabel);
|
|
15373
|
+
if (!slotId) {
|
|
15374
|
+
outputError("NOT_FOUND", `Slot "${idOrLabel}" not found.`);
|
|
15375
|
+
return;
|
|
15376
|
+
}
|
|
15114
15377
|
const { reorderGeminiSlot: reorderGeminiSlot2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
|
|
15115
|
-
reorderGeminiSlot2(
|
|
15378
|
+
reorderGeminiSlot2(slotId, parseInt(priority, 10));
|
|
15116
15379
|
output(
|
|
15117
|
-
{ id:
|
|
15118
|
-
() => success(`Slot #${
|
|
15380
|
+
{ id: slotId, priority: parseInt(priority, 10) },
|
|
15381
|
+
() => success(`Slot "${idOrLabel}" (#${slotId}) priority set to ${priority}`)
|
|
15119
15382
|
);
|
|
15120
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
|
+
}
|
|
15121
15405
|
var dbInitialized;
|
|
15122
15406
|
var init_gemini2 = __esm({
|
|
15123
15407
|
"src/cli/commands/gemini.ts"() {
|
|
@@ -16116,7 +16400,7 @@ __export(config_exports, {
|
|
|
16116
16400
|
configList: () => configList,
|
|
16117
16401
|
configSet: () => configSet
|
|
16118
16402
|
});
|
|
16119
|
-
import { existsSync as existsSync30, readFileSync as
|
|
16403
|
+
import { existsSync as existsSync30, readFileSync as readFileSync16 } from "fs";
|
|
16120
16404
|
async function configList(globalOpts) {
|
|
16121
16405
|
if (!existsSync30(DB_PATH)) {
|
|
16122
16406
|
outputError("DB_NOT_FOUND", "Database not found.");
|
|
@@ -16202,7 +16486,7 @@ async function configEnv(_globalOpts) {
|
|
|
16202
16486
|
outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
|
|
16203
16487
|
process.exit(1);
|
|
16204
16488
|
}
|
|
16205
|
-
const content =
|
|
16489
|
+
const content = readFileSync16(ENV_PATH, "utf-8");
|
|
16206
16490
|
const entries = {};
|
|
16207
16491
|
const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
|
|
16208
16492
|
for (const line of content.split("\n")) {
|
|
@@ -16860,11 +17144,11 @@ __export(chat_exports, {
|
|
|
16860
17144
|
chatSend: () => chatSend
|
|
16861
17145
|
});
|
|
16862
17146
|
import { request as httpRequest2 } from "http";
|
|
16863
|
-
import { readFileSync as
|
|
17147
|
+
import { readFileSync as readFileSync17, existsSync as existsSync38 } from "fs";
|
|
16864
17148
|
function getToken2() {
|
|
16865
17149
|
if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
|
|
16866
17150
|
try {
|
|
16867
|
-
if (existsSync38(TOKEN_PATH2)) return
|
|
17151
|
+
if (existsSync38(TOKEN_PATH2)) return readFileSync17(TOKEN_PATH2, "utf-8").trim();
|
|
16868
17152
|
} catch {
|
|
16869
17153
|
}
|
|
16870
17154
|
return null;
|
|
@@ -17290,7 +17574,7 @@ var init_completion = __esm({
|
|
|
17290
17574
|
|
|
17291
17575
|
// src/setup.ts
|
|
17292
17576
|
var setup_exports = {};
|
|
17293
|
-
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";
|
|
17294
17578
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
17295
17579
|
import { createInterface as createInterface6 } from "readline";
|
|
17296
17580
|
import { join as join21 } from "path";
|
|
@@ -17374,7 +17658,7 @@ async function setup() {
|
|
|
17374
17658
|
if (envSource) {
|
|
17375
17659
|
console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
|
|
17376
17660
|
console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
|
|
17377
|
-
const existing =
|
|
17661
|
+
const existing = readFileSync18(envSource, "utf-8");
|
|
17378
17662
|
for (const line of existing.split("\n")) {
|
|
17379
17663
|
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
17380
17664
|
if (match) env[match[1].trim()] = match[2].trim();
|
|
@@ -17732,22 +18016,26 @@ gemini.command("add-account").description("Add an OAuth account slot (opens brow
|
|
|
17732
18016
|
const { geminiAddAccount: geminiAddAccount2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17733
18017
|
await geminiAddAccount2(program.opts(), opts);
|
|
17734
18018
|
});
|
|
17735
|
-
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) => {
|
|
17736
18020
|
const { geminiRemove: geminiRemove2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17737
18021
|
await geminiRemove2(program.opts(), id);
|
|
17738
18022
|
});
|
|
17739
|
-
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) => {
|
|
17740
18024
|
const { geminiEnable: geminiEnable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17741
18025
|
await geminiEnable2(program.opts(), id);
|
|
17742
18026
|
});
|
|
17743
|
-
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) => {
|
|
17744
18028
|
const { geminiDisable: geminiDisable2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17745
18029
|
await geminiDisable2(program.opts(), id);
|
|
17746
18030
|
});
|
|
17747
|
-
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) => {
|
|
17748
18032
|
const { geminiReorder: geminiReorder2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
|
|
17749
18033
|
await geminiReorder2(program.opts(), id, priority);
|
|
17750
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
|
+
});
|
|
17751
18039
|
var backend = program.command("backend").description("Manage AI backend");
|
|
17752
18040
|
backend.command("list").description("Available backends with status").action(async () => {
|
|
17753
18041
|
const { backendList: backendList2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
package/package.json
CHANGED