codeam-cli 1.4.12 → 1.4.14
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/index.js +88 -21
- 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.
|
|
118
|
+
version: "1.4.14",
|
|
119
119
|
description: "Remote control Claude Code from your mobile device",
|
|
120
120
|
main: "dist/index.js",
|
|
121
121
|
bin: {
|
|
@@ -1383,6 +1383,19 @@ function post(endpoint, body) {
|
|
|
1383
1383
|
req.end();
|
|
1384
1384
|
});
|
|
1385
1385
|
}
|
|
1386
|
+
var MODEL_PRICING = {
|
|
1387
|
+
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
1388
|
+
"claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
1389
|
+
"claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
1390
|
+
"claude-3-5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
1391
|
+
"claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 }
|
|
1392
|
+
};
|
|
1393
|
+
function getPricing(model) {
|
|
1394
|
+
for (const [prefix, pricing] of Object.entries(MODEL_PRICING)) {
|
|
1395
|
+
if (model.startsWith(prefix)) return pricing;
|
|
1396
|
+
}
|
|
1397
|
+
return MODEL_PRICING["claude-sonnet-4"];
|
|
1398
|
+
}
|
|
1386
1399
|
var HistoryService = class {
|
|
1387
1400
|
constructor(pluginId, cwd) {
|
|
1388
1401
|
this.pluginId = pluginId;
|
|
@@ -1437,33 +1450,34 @@ var HistoryService = class {
|
|
|
1437
1450
|
}
|
|
1438
1451
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
1439
1452
|
if (files.length === 0) return null;
|
|
1440
|
-
let targetFile = files[0].name;
|
|
1441
1453
|
if (this.currentConversationId) {
|
|
1442
1454
|
const conversationFile = `${this.currentConversationId}.jsonl`;
|
|
1443
|
-
const
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1455
|
+
const idx = files.findIndex((f) => f.name === conversationFile);
|
|
1456
|
+
if (idx > 0) {
|
|
1457
|
+
const [target] = files.splice(idx, 1);
|
|
1458
|
+
files.unshift(target);
|
|
1446
1459
|
}
|
|
1447
1460
|
}
|
|
1461
|
+
const MAX_FILES_TO_TRY = 3;
|
|
1462
|
+
for (let i = 0; i < Math.min(files.length, MAX_FILES_TO_TRY); i++) {
|
|
1463
|
+
const result = this.extractUsageFromFile(path4.join(dir, files[i].name));
|
|
1464
|
+
if (result) return result;
|
|
1465
|
+
}
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
extractUsageFromFile(filePath) {
|
|
1448
1469
|
let raw;
|
|
1449
1470
|
try {
|
|
1450
|
-
raw = fs4.readFileSync(
|
|
1471
|
+
raw = fs4.readFileSync(filePath, "utf8");
|
|
1451
1472
|
} catch {
|
|
1452
1473
|
return null;
|
|
1453
1474
|
}
|
|
1454
1475
|
let lastUsage = null;
|
|
1455
1476
|
let lastModel = null;
|
|
1456
|
-
|
|
1457
|
-
let hasAnyMessages = false;
|
|
1458
|
-
const lines = raw.split("\n").filter(Boolean);
|
|
1459
|
-
for (const line of lines) {
|
|
1477
|
+
for (const line of raw.split("\n").filter(Boolean)) {
|
|
1460
1478
|
try {
|
|
1461
1479
|
const record = JSON.parse(line);
|
|
1462
|
-
if (record["type"] === "user" || record["type"] === "assistant") {
|
|
1463
|
-
hasAnyMessages = true;
|
|
1464
|
-
}
|
|
1465
1480
|
if (record["type"] === "assistant") {
|
|
1466
|
-
assistantMessageCount++;
|
|
1467
1481
|
const msg = record["message"];
|
|
1468
1482
|
if (msg?.["model"] === "<synthetic>") continue;
|
|
1469
1483
|
const usage = msg?.["usage"];
|
|
@@ -1475,18 +1489,70 @@ var HistoryService = class {
|
|
|
1475
1489
|
} catch {
|
|
1476
1490
|
}
|
|
1477
1491
|
}
|
|
1478
|
-
if (!lastUsage)
|
|
1479
|
-
if (hasAnyMessages) {
|
|
1480
|
-
return { used: 0, total: 2e5, percent: 0, model: lastModel, outputTokens: 0, cacheReadTokens: 0 };
|
|
1481
|
-
}
|
|
1482
|
-
return null;
|
|
1483
|
-
}
|
|
1492
|
+
if (!lastUsage) return null;
|
|
1484
1493
|
const inputTokens = (lastUsage["input_tokens"] ?? lastUsage["prompt_tokens"] ?? 0) + (lastUsage["cache_read_input_tokens"] ?? 0) + (lastUsage["cache_creation_input_tokens"] ?? 0);
|
|
1485
1494
|
const outputTokens = lastUsage["output_tokens"] ?? lastUsage["completion_tokens"] ?? 0;
|
|
1486
1495
|
const total = 2e5;
|
|
1487
1496
|
const percent = Math.min(100, Math.round(inputTokens / total * 100));
|
|
1488
1497
|
return { used: inputTokens, total, percent, model: lastModel, outputTokens, cacheReadTokens: lastUsage["cache_read_input_tokens"] ?? 0 };
|
|
1489
1498
|
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Estimate the total API cost for the current month across all projects.
|
|
1501
|
+
* Scans all JSONL files modified this month under ~/.claude/projects/.
|
|
1502
|
+
*/
|
|
1503
|
+
getMonthlyEstimatedCost() {
|
|
1504
|
+
const claudeDir = path4.join(os4.homedir(), ".claude", "projects");
|
|
1505
|
+
let projectDirs;
|
|
1506
|
+
try {
|
|
1507
|
+
projectDirs = fs4.readdirSync(claudeDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path4.join(claudeDir, e.name));
|
|
1508
|
+
} catch {
|
|
1509
|
+
return 0;
|
|
1510
|
+
}
|
|
1511
|
+
const now = /* @__PURE__ */ new Date();
|
|
1512
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
|
|
1513
|
+
let totalCost = 0;
|
|
1514
|
+
for (const projectDir of projectDirs) {
|
|
1515
|
+
let files;
|
|
1516
|
+
try {
|
|
1517
|
+
files = fs4.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
|
|
1518
|
+
try {
|
|
1519
|
+
return fs4.statSync(path4.join(projectDir, f)).mtimeMs >= monthStart;
|
|
1520
|
+
} catch {
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
} catch {
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
for (const file of files) {
|
|
1528
|
+
let raw;
|
|
1529
|
+
try {
|
|
1530
|
+
raw = fs4.readFileSync(path4.join(projectDir, file), "utf8");
|
|
1531
|
+
} catch {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
for (const line of raw.split("\n").filter(Boolean)) {
|
|
1535
|
+
try {
|
|
1536
|
+
const record = JSON.parse(line);
|
|
1537
|
+
if (record["type"] !== "assistant") continue;
|
|
1538
|
+
const msg = record["message"];
|
|
1539
|
+
if (!msg || msg["model"] === "<synthetic>") continue;
|
|
1540
|
+
const model = msg["model"] || "";
|
|
1541
|
+
const usage = msg["usage"];
|
|
1542
|
+
if (!usage) continue;
|
|
1543
|
+
const pricing = getPricing(model);
|
|
1544
|
+
const input = usage["input_tokens"] ?? 0;
|
|
1545
|
+
const output = usage["output_tokens"] ?? 0;
|
|
1546
|
+
const cacheRead = usage["cache_read_input_tokens"] ?? 0;
|
|
1547
|
+
const cacheWrite = usage["cache_creation_input_tokens"] ?? 0;
|
|
1548
|
+
totalCost += input / 1e6 * pricing.input + output / 1e6 * pricing.output + cacheRead / 1e6 * pricing.cacheRead + cacheWrite / 1e6 * pricing.cacheWrite;
|
|
1549
|
+
} catch {
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return Math.round(totalCost * 100) / 100;
|
|
1555
|
+
}
|
|
1490
1556
|
/**
|
|
1491
1557
|
* Read session list from disk and POST it to the API.
|
|
1492
1558
|
* Called once ~2 s after Claude spawns (non-blocking).
|
|
@@ -1626,7 +1692,8 @@ async function start() {
|
|
|
1626
1692
|
break;
|
|
1627
1693
|
case "get_context": {
|
|
1628
1694
|
const usage = historySvc.getCurrentUsage();
|
|
1629
|
-
const
|
|
1695
|
+
const monthlyCost = historySvc.getMonthlyEstimatedCost();
|
|
1696
|
+
const result = usage ? { ...usage, monthlyCost } : { used: 0, total: 2e5, percent: 0, model: null, outputTokens: 0, cacheReadTokens: 0, monthlyCost, error: "No usage data found" };
|
|
1630
1697
|
await relay.sendResult(cmd.id, "completed", result);
|
|
1631
1698
|
break;
|
|
1632
1699
|
}
|