@inetafrica/open-claudia 1.14.8 → 1.15.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/CHANGELOG.md +7 -0
- package/bot.js +62 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.14.9
|
|
4
|
+
- Fix `/upgrade`: `latest` was const-scoped to the version-check try
|
|
5
|
+
block, so the install step ran with `latest` undefined and threw
|
|
6
|
+
`ReferenceError: latest is not defined`. Hoist the variable and fall
|
|
7
|
+
back to npm's `latest` dist-tag if `npm view` fails. Regression
|
|
8
|
+
introduced in 745c6cf — broke v1.14.7 and v1.14.8 upgrades.
|
|
9
|
+
|
|
3
10
|
## v1.14.8
|
|
4
11
|
- Fix: `/stop` and the 6h hard-timeout now walk the full descendant
|
|
5
12
|
process tree, not just the Claude CLI's process group. Background
|
package/bot.js
CHANGED
|
@@ -274,6 +274,7 @@ bot.setMyCommands([
|
|
|
274
274
|
{ command: "ask", description: "Toggle ask mode (Cursor only)" },
|
|
275
275
|
{ command: "sessions", description: "List conversations for this project" },
|
|
276
276
|
{ command: "compact", description: "Summarize conversation context" },
|
|
277
|
+
{ command: "usage", description: "Show token usage & cost for this session" },
|
|
277
278
|
{ command: "continue", description: "Resume last conversation" },
|
|
278
279
|
{ command: "worktree", description: "Toggle isolated git branch" },
|
|
279
280
|
{ command: "cron", description: "Manage scheduled tasks" },
|
|
@@ -325,6 +326,7 @@ function saveState() {
|
|
|
325
326
|
lastSessionId,
|
|
326
327
|
cursorSessionId,
|
|
327
328
|
settings,
|
|
329
|
+
sessionUsage,
|
|
328
330
|
};
|
|
329
331
|
try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) {}
|
|
330
332
|
}
|
|
@@ -402,6 +404,20 @@ let pendingClaudeAuthLabel = null;
|
|
|
402
404
|
let isFirstMessage = !lastSessionId; // Track if this is first message in session
|
|
403
405
|
let lastInputWasVoice = false; // Reply with voice when input was voice
|
|
404
406
|
|
|
407
|
+
// Per-session usage tracking (resets on /end or new session)
|
|
408
|
+
let sessionUsage = savedState.sessionUsage || {
|
|
409
|
+
turns: 0,
|
|
410
|
+
inputTokens: 0,
|
|
411
|
+
outputTokens: 0,
|
|
412
|
+
cacheReadTokens: 0,
|
|
413
|
+
cacheCreationTokens: 0,
|
|
414
|
+
costUsd: 0,
|
|
415
|
+
lastInputTokens: 0, // Last turn's input tokens (for compact suggestion)
|
|
416
|
+
};
|
|
417
|
+
function resetSessionUsage() {
|
|
418
|
+
sessionUsage = { turns: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0, lastInputTokens: 0 };
|
|
419
|
+
}
|
|
420
|
+
|
|
405
421
|
let settings = savedState.settings || {
|
|
406
422
|
model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude",
|
|
407
423
|
};
|
|
@@ -1324,6 +1340,17 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1324
1340
|
if (evt.type === "result" && evt.session_id) {
|
|
1325
1341
|
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
1326
1342
|
else { lastSessionId = evt.session_id; }
|
|
1343
|
+
if (evt.usage) {
|
|
1344
|
+
sessionUsage.turns += 1;
|
|
1345
|
+
sessionUsage.inputTokens += evt.usage.input_tokens || 0;
|
|
1346
|
+
sessionUsage.outputTokens += evt.usage.output_tokens || 0;
|
|
1347
|
+
sessionUsage.cacheReadTokens += evt.usage.cache_read_input_tokens || 0;
|
|
1348
|
+
sessionUsage.cacheCreationTokens += evt.usage.cache_creation_input_tokens || 0;
|
|
1349
|
+
sessionUsage.lastInputTokens = (evt.usage.input_tokens || 0) +
|
|
1350
|
+
(evt.usage.cache_read_input_tokens || 0) +
|
|
1351
|
+
(evt.usage.cache_creation_input_tokens || 0);
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof evt.total_cost_usd === "number") sessionUsage.costUsd += evt.total_cost_usd;
|
|
1327
1354
|
saveState();
|
|
1328
1355
|
}
|
|
1329
1356
|
if (evt.type === "result" && evt.result) assistantText = evt.result;
|
|
@@ -1384,6 +1411,15 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1384
1411
|
const voicePath = textToVoice(finalText);
|
|
1385
1412
|
if (voicePath) await sendVoice(voicePath);
|
|
1386
1413
|
}
|
|
1414
|
+
|
|
1415
|
+
// Suggest /compact when context gets large (once per threshold crossing)
|
|
1416
|
+
const ctx = sessionUsage.lastInputTokens;
|
|
1417
|
+
if (ctx > 150000 && !sessionUsage.warnedHighContext) {
|
|
1418
|
+
sessionUsage.warnedHighContext = true;
|
|
1419
|
+
await send(`Heads up: context is ${(ctx / 1000).toFixed(0)}k tokens. Use /compact to summarize or /end for a fresh start — keeps cost down.`);
|
|
1420
|
+
} else if (ctx < 80000 && sessionUsage.warnedHighContext) {
|
|
1421
|
+
sessionUsage.warnedHighContext = false;
|
|
1422
|
+
}
|
|
1387
1423
|
} catch (e) {
|
|
1388
1424
|
console.error("Final message delivery failed:", e.message);
|
|
1389
1425
|
await send("Task completed but failed to deliver the response. Send /continue to see the result.");
|
|
@@ -1548,9 +1584,12 @@ bot.onText(/\/upgrade$/, async (msg) => {
|
|
|
1548
1584
|
// Change to HOME first — npm install -g replaces the package directory
|
|
1549
1585
|
// which can delete the cwd out from under the running process
|
|
1550
1586
|
try { process.chdir(process.env.HOME || require("os").homedir()); } catch (e) { /* already gone */ }
|
|
1551
|
-
// Check if there's actually a newer version
|
|
1587
|
+
// Check if there's actually a newer version. `latest` is hoisted so
|
|
1588
|
+
// the install step below can reference it; if `npm view` fails we
|
|
1589
|
+
// fall back to npm's `latest` dist-tag.
|
|
1590
|
+
let latest = null;
|
|
1552
1591
|
try {
|
|
1553
|
-
|
|
1592
|
+
latest = execSync("npm view @inetafrica/open-claudia version", {
|
|
1554
1593
|
encoding: "utf-8", timeout: 15000,
|
|
1555
1594
|
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME || require("os").homedir() },
|
|
1556
1595
|
}).trim();
|
|
@@ -1562,8 +1601,9 @@ bot.onText(/\/upgrade$/, async (msg) => {
|
|
|
1562
1601
|
} catch (e) {
|
|
1563
1602
|
await send("Upgrading...");
|
|
1564
1603
|
}
|
|
1604
|
+
const installTarget = latest || "latest";
|
|
1565
1605
|
try {
|
|
1566
|
-
execSync(`npm install -g @inetafrica/open-claudia@${
|
|
1606
|
+
execSync(`npm install -g @inetafrica/open-claudia@${installTarget} 2>&1`, {
|
|
1567
1607
|
encoding: "utf-8", timeout: 120000,
|
|
1568
1608
|
cwd: process.env.HOME || require("os").homedir(),
|
|
1569
1609
|
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME || require("os").homedir() },
|
|
@@ -1756,12 +1796,28 @@ bot.onText(/\/status/, (msg) => {
|
|
|
1756
1796
|
bot.onText(/\/end/, (msg) => {
|
|
1757
1797
|
if (!isAuthorized(msg)) return;
|
|
1758
1798
|
if (currentSession) {
|
|
1759
|
-
const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings();
|
|
1799
|
+
const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); resetSessionUsage();
|
|
1760
1800
|
saveState();
|
|
1761
1801
|
send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New session", callback_data: "show:projects" }]] } });
|
|
1762
1802
|
} else send("No session.");
|
|
1763
1803
|
});
|
|
1764
1804
|
|
|
1805
|
+
bot.onText(/\/usage$/, (msg) => {
|
|
1806
|
+
if (!isAuthorized(msg)) return;
|
|
1807
|
+
const u = sessionUsage;
|
|
1808
|
+
if (u.turns === 0) return send("No usage yet in this session.");
|
|
1809
|
+
const fmt = (n) => n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n);
|
|
1810
|
+
const lines = [
|
|
1811
|
+
`*Session usage* (${u.turns} turn${u.turns === 1 ? "" : "s"})`,
|
|
1812
|
+
`Input: ${fmt(u.inputTokens)} · Output: ${fmt(u.outputTokens)}`,
|
|
1813
|
+
`Cache read: ${fmt(u.cacheReadTokens)} · Cache write: ${fmt(u.cacheCreationTokens)}`,
|
|
1814
|
+
`Cost: $${u.costUsd.toFixed(4)}`,
|
|
1815
|
+
`Last turn context: ${fmt(u.lastInputTokens)}`,
|
|
1816
|
+
];
|
|
1817
|
+
if (u.lastInputTokens > 100000) lines.push("\nTip: context is large. Use /compact to summarize, or /end for a clean slate.");
|
|
1818
|
+
send(lines.join("\n"), { parseMode: "Markdown" });
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1765
1821
|
bot.onText(/\/soul$/, async (msg) => {
|
|
1766
1822
|
if (!isAuthorized(msg)) return;
|
|
1767
1823
|
const soul = loadSoul();
|
|
@@ -1961,13 +2017,13 @@ bot.on("callback_query", async (q) => {
|
|
|
1961
2017
|
const sessions = loadSessions();
|
|
1962
2018
|
// Don't delete history, just start fresh
|
|
1963
2019
|
currentSession = { name: proj === "__workspace__" ? "Workspace" : proj, dir: proj === "__workspace__" ? WORKSPACE : path.join(WORKSPACE, proj) };
|
|
1964
|
-
lastSessionId = null; isFirstMessage = true; messageQueue = []; resetSettings();
|
|
2020
|
+
lastSessionId = null; isFirstMessage = true; messageQueue = []; resetSettings(); resetSessionUsage();
|
|
1965
2021
|
saveState();
|
|
1966
2022
|
await send(`Session: ${currentSession.name}\nNew conversation\n\nSend text, voice, or images.\n\n/sessions — switch conversation\n/session — switch project`);
|
|
1967
2023
|
return;
|
|
1968
2024
|
}
|
|
1969
2025
|
if (d === "a:continue") { if (currentSession) await runClaude("continue", currentSession.dir); else send("No session."); return; }
|
|
1970
|
-
if (d === "a:end") { if (currentSession) { const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); saveState(); await send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New", callback_data: "show:projects" }]] } }); } return; }
|
|
2026
|
+
if (d === "a:end") { if (currentSession) { const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); resetSessionUsage(); saveState(); await send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New", callback_data: "show:projects" }]] } }); } return; }
|
|
1971
2027
|
if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
|
|
1972
2028
|
if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
|
|
1973
2029
|
if (d.startsWith("b:")) { const b = d.slice(2); settings.budget = b === "none" ? null : parseFloat(b); await send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); return; }
|