@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bot.js +62 -6
  3. 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
- const latest = execSync("npm view @inetafrica/open-claudia version", {
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@${latest} 2>&1`, {
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; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "1.14.8",
3
+ "version": "1.15.0",
4
4
  "description": "Your always-on AI coding assistant — Claude Code via Telegram",
5
5
  "main": "bot.js",
6
6
  "bin": {