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.
- package/dist/index.js +150 -5
- 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.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())
|
|
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
|
|
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
|
});
|