codeam-cli 1.4.7 → 1.4.9

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 (2) hide show
  1. package/dist/index.js +150 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -115,7 +115,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
115
115
  // package.json
116
116
  var package_default = {
117
117
  name: "codeam-cli",
118
- version: "1.4.7",
118
+ version: "1.4.9",
119
119
  description: "Remote control Claude Code from your mobile device",
120
120
  main: "dist/index.js",
121
121
  bin: {
@@ -1121,9 +1121,10 @@ function filterChrome(lines) {
1121
1121
  return result;
1122
1122
  }
1123
1123
  var OutputService = class _OutputService {
1124
- constructor(sessionId, pluginId) {
1124
+ constructor(sessionId, pluginId, onSessionIdDetected) {
1125
1125
  this.sessionId = sessionId;
1126
1126
  this.pluginId = pluginId;
1127
+ this.onSessionIdDetected = onSessionIdDetected;
1127
1128
  }
1128
1129
  sessionId;
1129
1130
  pluginId;
@@ -1133,6 +1134,7 @@ var OutputService = class _OutputService {
1133
1134
  startTime = 0;
1134
1135
  active = false;
1135
1136
  lastPushTime = 0;
1137
+ onSessionIdDetected;
1136
1138
  static POLL_MS = 1e3;
1137
1139
  static IDLE_MS = 3e3;
1138
1140
  /** Shorter idle threshold for selector detection (UI is ready immediately). */
@@ -1170,7 +1172,26 @@ var OutputService = class _OutputService {
1170
1172
  if (!this.active) return;
1171
1173
  this.rawBuffer += raw;
1172
1174
  const printable = raw.replace(/\x1B\[[^@-~]*[@-~]/g, "").replace(/[\x00-\x1F\x7F]/g, "");
1173
- if (printable.trim()) this.lastPushTime = Date.now();
1175
+ if (printable.trim()) {
1176
+ this.lastPushTime = Date.now();
1177
+ this.tryExtractSessionId(printable);
1178
+ }
1179
+ }
1180
+ /** Extract Claude conversation ID from output text (e.g., from /cost command or session resume) */
1181
+ tryExtractSessionId(text) {
1182
+ const patterns = [
1183
+ /Resuming session[:\s]+([a-f0-9-]{36})/i,
1184
+ /Session[:\s]+([a-f0-9-]{36})/i,
1185
+ /Conversation[:\s]+([a-f0-9-]{36})/i,
1186
+ /Session\s+ID[:\s]+([a-f0-9-]{36})/i
1187
+ ];
1188
+ for (const pattern of patterns) {
1189
+ const match = text.match(pattern);
1190
+ if (match && this.onSessionIdDetected) {
1191
+ this.onSessionIdDetected(match[1]);
1192
+ return;
1193
+ }
1194
+ }
1174
1195
  }
1175
1196
  dispose() {
1176
1197
  this.stopPoll();
@@ -1366,9 +1387,119 @@ var HistoryService = class {
1366
1387
  }
1367
1388
  pluginId;
1368
1389
  cwd;
1390
+ currentConversationId = null;
1369
1391
  get projectDir() {
1370
1392
  return path4.join(os4.homedir(), ".claude", "projects", encodeCwd(this.cwd));
1371
1393
  }
1394
+ /** Set the current Claude conversation ID (extracted from /cost command or session start) */
1395
+ setCurrentConversationId(id) {
1396
+ this.currentConversationId = id;
1397
+ }
1398
+ /** Extract conversation ID from Claude output (e.g., from session resume messages) */
1399
+ tryExtractConversationIdFromOutput(output) {
1400
+ const patterns = [
1401
+ /Resuming session[:\s]+([a-f0-9-]{36})/i,
1402
+ /session[:\s]+([a-f0-9-]{36})/i,
1403
+ /conversation[:\s]+([a-f0-9-]{36})/i
1404
+ ];
1405
+ for (const pattern of patterns) {
1406
+ const match = output.match(pattern);
1407
+ if (match) {
1408
+ this.currentConversationId = match[1];
1409
+ return;
1410
+ }
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Read the most recently modified JSONL session file and extract the
1415
+ * context window usage from the last assistant message's usage field.
1416
+ *
1417
+ * Claude Code records token counts per-response:
1418
+ * input_tokens + cache_read_input_tokens + cache_creation_input_tokens
1419
+ * = total context tokens consumed in that request.
1420
+ */
1421
+ getCurrentUsage() {
1422
+ const dir = this.projectDir;
1423
+ console.log(`[HistoryService] Looking for sessions in: ${dir}`);
1424
+ console.log(`[HistoryService] Current conversation ID: ${this.currentConversationId ?? "not set"}`);
1425
+ let entries;
1426
+ try {
1427
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
1428
+ } catch (err) {
1429
+ console.log(`[HistoryService] Failed to read directory: ${err}`);
1430
+ return null;
1431
+ }
1432
+ const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
1433
+ try {
1434
+ return { name: e.name, mtime: fs4.statSync(path4.join(dir, e.name)).mtimeMs };
1435
+ } catch {
1436
+ return { name: e.name, mtime: 0 };
1437
+ }
1438
+ }).sort((a, b) => b.mtime - a.mtime);
1439
+ if (files.length === 0) {
1440
+ console.log(`[HistoryService] No .jsonl files found in directory`);
1441
+ return null;
1442
+ }
1443
+ let targetFile = files[0].name;
1444
+ if (this.currentConversationId) {
1445
+ const conversationFile = `${this.currentConversationId}.jsonl`;
1446
+ const exists = files.some((f) => f.name === conversationFile);
1447
+ if (exists) {
1448
+ targetFile = conversationFile;
1449
+ console.log(`[HistoryService] Using tracked conversation file: ${targetFile}`);
1450
+ } else {
1451
+ console.log(`[HistoryService] Tracked conversation file not found, using most recent: ${targetFile}`);
1452
+ }
1453
+ } else {
1454
+ console.log(`[HistoryService] No tracked conversation, using most recent: ${targetFile}`);
1455
+ }
1456
+ let raw;
1457
+ try {
1458
+ raw = fs4.readFileSync(path4.join(dir, targetFile), "utf8");
1459
+ } catch (err) {
1460
+ console.log(`[HistoryService] Failed to read file: ${err}`);
1461
+ return null;
1462
+ }
1463
+ let lastUsage = null;
1464
+ let lastModel = null;
1465
+ let assistantMessageCount = 0;
1466
+ let hasAnyMessages = false;
1467
+ const lines = raw.split("\n").filter(Boolean);
1468
+ console.log(`[HistoryService] File has ${lines.length} lines`);
1469
+ for (const line of lines) {
1470
+ try {
1471
+ const record = JSON.parse(line);
1472
+ if (record["type"] === "user" || record["type"] === "assistant") {
1473
+ hasAnyMessages = true;
1474
+ }
1475
+ if (record["type"] === "assistant") {
1476
+ assistantMessageCount++;
1477
+ const msg = record["message"];
1478
+ const usage = msg?.["usage"];
1479
+ if (usage && (usage["input_tokens"] !== void 0 || usage["prompt_tokens"] !== void 0)) {
1480
+ lastUsage = usage;
1481
+ console.log(`[HistoryService] Found usage data: ${JSON.stringify(lastUsage)}`);
1482
+ }
1483
+ if (msg?.["model"]) lastModel = msg["model"];
1484
+ }
1485
+ } catch {
1486
+ }
1487
+ }
1488
+ console.log(`[HistoryService] Processed ${assistantMessageCount} assistant messages, hasAnyMessages: ${hasAnyMessages}, lastUsage: ${lastUsage ? "found" : "not found"}, lastModel: ${lastModel}`);
1489
+ if (!lastUsage) {
1490
+ if (hasAnyMessages) {
1491
+ console.log(`[HistoryService] File has messages but no usage data (Claude Code may not have recorded usage yet)`);
1492
+ return { used: 0, total: 2e5, percent: 0, model: lastModel, outputTokens: 0, cacheReadTokens: 0 };
1493
+ }
1494
+ console.log(`[HistoryService] No messages or usage data found in session file`);
1495
+ return null;
1496
+ }
1497
+ const inputTokens = (lastUsage["input_tokens"] ?? lastUsage["prompt_tokens"] ?? 0) + (lastUsage["cache_read_input_tokens"] ?? 0) + (lastUsage["cache_creation_input_tokens"] ?? 0);
1498
+ const outputTokens = lastUsage["output_tokens"] ?? lastUsage["completion_tokens"] ?? 0;
1499
+ const total = 2e5;
1500
+ const percent = Math.min(100, Math.round(inputTokens / total * 100));
1501
+ return { used: inputTokens, total, percent, model: lastModel, outputTokens, cacheReadTokens: lastUsage["cache_read_input_tokens"] ?? 0 };
1502
+ }
1372
1503
  /**
1373
1504
  * Read session list from disk and POST it to the API.
1374
1505
  * Called once ~2 s after Claude spawns (non-blocking).
@@ -1453,8 +1584,15 @@ async function start() {
1453
1584
  const pluginId = session.pluginId ?? ensurePluginId();
1454
1585
  showInfo(`${session.userName} \xB7 ${import_picocolors2.default.cyan(session.plan)}`);
1455
1586
  showInfo("Launching Claude Code...\n");
1587
+ const cwd = process.cwd();
1588
+ console.log(`[CLI] Starting with cwd: ${cwd}`);
1589
+ console.log(`[CLI] Session: ${session.id}, Plugin: ${pluginId}`);
1456
1590
  const ws = new WebSocketService(session.id, pluginId);
1457
- const outputSvc = new OutputService(session.id, pluginId);
1591
+ const historySvc = new HistoryService(pluginId, cwd);
1592
+ const outputSvc = new OutputService(session.id, pluginId, (conversationId) => {
1593
+ console.log(`[CLI] Detected Claude conversation ID: ${conversationId}`);
1594
+ historySvc.setCurrentConversationId(conversationId);
1595
+ });
1458
1596
  function sendPrompt(prompt) {
1459
1597
  outputSvc.newTurn();
1460
1598
  claude.sendCommand(prompt);
@@ -1502,6 +1640,14 @@ async function start() {
1502
1640
  case "stop_task":
1503
1641
  claude.interrupt();
1504
1642
  break;
1643
+ case "get_context": {
1644
+ console.log(`[CLI] Received get_context command, fetching usage...`);
1645
+ const usage = historySvc.getCurrentUsage();
1646
+ const result = usage ?? { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, error: "No usage data found" };
1647
+ console.log(`[CLI] Sending context result: ${JSON.stringify(result)}`);
1648
+ await relay.sendResult(cmd.id, "completed", result);
1649
+ break;
1650
+ }
1505
1651
  case "resume_session": {
1506
1652
  const id = cmd.payload.id;
1507
1653
  const auto = cmd.payload.auto ?? false;
@@ -1591,7 +1737,6 @@ async function start() {
1591
1737
  }
1592
1738
  process.once("SIGINT", sigintHandler);
1593
1739
  claude.spawn();
1594
- const historySvc = new HistoryService(pluginId, process.cwd());
1595
1740
  setTimeout(() => {
1596
1741
  historySvc.load().catch(() => {
1597
1742
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {