codeam-cli 1.4.1 → 1.4.3
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 +223 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/commands/start.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
27
|
+
var fs5 = __toESM(require("fs"));
|
|
28
|
+
var os5 = __toESM(require("os"));
|
|
29
|
+
var path5 = __toESM(require("path"));
|
|
30
30
|
var import_crypto = require("crypto");
|
|
31
31
|
var import_picocolors2 = __toESM(require("picocolors"));
|
|
32
32
|
|
|
@@ -114,7 +114,7 @@ var import_picocolors = __toESM(require("picocolors"));
|
|
|
114
114
|
// package.json
|
|
115
115
|
var package_default = {
|
|
116
116
|
name: "codeam-cli",
|
|
117
|
-
version: "1.4.
|
|
117
|
+
version: "1.4.3",
|
|
118
118
|
description: "Remote control Claude Code from your mobile device",
|
|
119
119
|
main: "dist/index.js",
|
|
120
120
|
bin: {
|
|
@@ -594,13 +594,13 @@ var UnixPtyStrategy = class {
|
|
|
594
594
|
opts;
|
|
595
595
|
proc = null;
|
|
596
596
|
helperPath = null;
|
|
597
|
-
spawn(cmd, cwd) {
|
|
597
|
+
spawn(cmd, cwd, args2 = []) {
|
|
598
598
|
const python = findInPath("python3") ?? findInPath("python");
|
|
599
599
|
if (!python) {
|
|
600
600
|
console.error(
|
|
601
601
|
" \xB7 python3 not found; mobile command injection may be limited.\n"
|
|
602
602
|
);
|
|
603
|
-
this.spawnDirect(cmd, cwd);
|
|
603
|
+
this.spawnDirect(cmd, cwd, args2);
|
|
604
604
|
return;
|
|
605
605
|
}
|
|
606
606
|
const shell = process.env.SHELL || "/bin/sh";
|
|
@@ -608,7 +608,8 @@ var UnixPtyStrategy = class {
|
|
|
608
608
|
const rows = process.stdout.rows || 50;
|
|
609
609
|
this.helperPath = path3.join(os3.tmpdir(), "codeam-pty-helper.py");
|
|
610
610
|
fs3.writeFileSync(this.helperPath, PYTHON_PTY_HELPER, { mode: 420 });
|
|
611
|
-
|
|
611
|
+
const fullCmd = args2.length > 0 ? `${cmd} ${args2.join(" ")}` : cmd;
|
|
612
|
+
this.proc = (0, import_child_process.spawn)(python, [this.helperPath, shell, "-c", `exec ${fullCmd}`], {
|
|
612
613
|
stdio: ["pipe", "pipe", "inherit"],
|
|
613
614
|
cwd,
|
|
614
615
|
env: {
|
|
@@ -645,8 +646,8 @@ var UnixPtyStrategy = class {
|
|
|
645
646
|
* Python-unavailable fallback: direct spawn without PTY.
|
|
646
647
|
* Mobile command injection is limited (no real TTY for Claude).
|
|
647
648
|
*/
|
|
648
|
-
spawnDirect(cmd, cwd) {
|
|
649
|
-
this.proc = (0, import_child_process.spawn)(cmd,
|
|
649
|
+
spawnDirect(cmd, cwd, args2 = []) {
|
|
650
|
+
this.proc = (0, import_child_process.spawn)(cmd, args2, {
|
|
650
651
|
stdio: ["pipe", "inherit", "inherit"],
|
|
651
652
|
cwd,
|
|
652
653
|
env: process.env,
|
|
@@ -675,7 +676,12 @@ var UnixPtyStrategy = class {
|
|
|
675
676
|
this.proc?.stdin?.write(data);
|
|
676
677
|
}
|
|
677
678
|
kill() {
|
|
678
|
-
this.proc
|
|
679
|
+
const proc = this.proc;
|
|
680
|
+
this.proc = null;
|
|
681
|
+
if (proc) {
|
|
682
|
+
proc.removeAllListeners("exit");
|
|
683
|
+
proc.kill();
|
|
684
|
+
}
|
|
679
685
|
this.removeTempFile();
|
|
680
686
|
this.dispose();
|
|
681
687
|
}
|
|
@@ -719,8 +725,8 @@ var WindowsPtyStrategy = class {
|
|
|
719
725
|
}
|
|
720
726
|
opts;
|
|
721
727
|
proc = null;
|
|
722
|
-
spawn(cmd, cwd) {
|
|
723
|
-
this.proc = (0, import_child_process2.spawn)(cmd,
|
|
728
|
+
spawn(cmd, cwd, args2 = []) {
|
|
729
|
+
this.proc = (0, import_child_process2.spawn)(cmd, args2, {
|
|
724
730
|
stdio: ["pipe", "pipe", "inherit"],
|
|
725
731
|
cwd,
|
|
726
732
|
env: {
|
|
@@ -849,6 +855,17 @@ var ClaudeService = class {
|
|
|
849
855
|
kill() {
|
|
850
856
|
this.strategy.kill();
|
|
851
857
|
}
|
|
858
|
+
/**
|
|
859
|
+
* Kill the current Claude process and relaunch it resuming the given session.
|
|
860
|
+
* Pass auto=true to add --dangerously-skip-permissions (no confirmation prompts).
|
|
861
|
+
*/
|
|
862
|
+
restart(sessionId, auto = false) {
|
|
863
|
+
const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
|
|
864
|
+
this.strategy.kill();
|
|
865
|
+
const args2 = ["--resume", sessionId];
|
|
866
|
+
if (auto) args2.push("--dangerously-skip-permissions");
|
|
867
|
+
this.strategy.spawn(claudeCmd, this.opts.cwd, args2);
|
|
868
|
+
}
|
|
852
869
|
};
|
|
853
870
|
|
|
854
871
|
// src/services/output.service.ts
|
|
@@ -1126,6 +1143,22 @@ var OutputService = class _OutputService {
|
|
|
1126
1143
|
});
|
|
1127
1144
|
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
1128
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Like newTurn() but signals clients that a session is being resumed.
|
|
1148
|
+
* The resumedSessionId tells clients to fetch the conversation from the API.
|
|
1149
|
+
* Awaits the POST so callers can guarantee the signal is sent before restarting Claude.
|
|
1150
|
+
*/
|
|
1151
|
+
async newTurnResume(resumedSessionId) {
|
|
1152
|
+
this.stopPoll();
|
|
1153
|
+
this.rawBuffer = "";
|
|
1154
|
+
this.lastSentContent = "";
|
|
1155
|
+
this.lastPushTime = 0;
|
|
1156
|
+
this.active = true;
|
|
1157
|
+
this.startTime = Date.now();
|
|
1158
|
+
await this.postChunk({ clear: true });
|
|
1159
|
+
await this.postChunk({ type: "new_turn", resumedSessionId, content: "", done: false });
|
|
1160
|
+
this.pollTimer = setInterval(() => this.tick(), _OutputService.POLL_MS);
|
|
1161
|
+
}
|
|
1129
1162
|
push(raw) {
|
|
1130
1163
|
if (!this.active) return;
|
|
1131
1164
|
this.rawBuffer += raw;
|
|
@@ -1242,12 +1275,162 @@ var OutputService = class _OutputService {
|
|
|
1242
1275
|
}
|
|
1243
1276
|
};
|
|
1244
1277
|
|
|
1278
|
+
// src/services/history.service.ts
|
|
1279
|
+
var fs4 = __toESM(require("fs"));
|
|
1280
|
+
var path4 = __toESM(require("path"));
|
|
1281
|
+
var os4 = __toESM(require("os"));
|
|
1282
|
+
var https3 = __toESM(require("https"));
|
|
1283
|
+
var http3 = __toESM(require("http"));
|
|
1284
|
+
var API_BASE5 = process.env.CODEAM_API_URL ?? "https://codeagent-mobile-api.vercel.app";
|
|
1285
|
+
function encodeCwd(cwd) {
|
|
1286
|
+
return cwd.replace(/\//g, "-");
|
|
1287
|
+
}
|
|
1288
|
+
function extractText(content) {
|
|
1289
|
+
if (typeof content === "string") return content;
|
|
1290
|
+
if (Array.isArray(content)) {
|
|
1291
|
+
return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
|
|
1292
|
+
}
|
|
1293
|
+
return "";
|
|
1294
|
+
}
|
|
1295
|
+
function parseJsonl(filePath) {
|
|
1296
|
+
const messages = [];
|
|
1297
|
+
let raw;
|
|
1298
|
+
try {
|
|
1299
|
+
raw = fs4.readFileSync(filePath, "utf8");
|
|
1300
|
+
} catch {
|
|
1301
|
+
return messages;
|
|
1302
|
+
}
|
|
1303
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
1304
|
+
for (const line of lines) {
|
|
1305
|
+
try {
|
|
1306
|
+
const record = JSON.parse(line);
|
|
1307
|
+
const type = record["type"];
|
|
1308
|
+
const msg = record["message"];
|
|
1309
|
+
const ts = record["timestamp"];
|
|
1310
|
+
const timestamp = typeof ts === "string" ? new Date(ts).getTime() : typeof ts === "number" ? ts : Date.now();
|
|
1311
|
+
const uuid = record["uuid"] ?? `${Date.now()}-${Math.random()}`;
|
|
1312
|
+
if (type === "user" && msg) {
|
|
1313
|
+
const text = extractText(msg["content"]).trim();
|
|
1314
|
+
if (text) messages.push({ id: uuid, role: "user", text, timestamp });
|
|
1315
|
+
} else if (type === "assistant" && msg) {
|
|
1316
|
+
const text = extractText(msg["content"]).trim();
|
|
1317
|
+
if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
|
|
1318
|
+
}
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return messages;
|
|
1323
|
+
}
|
|
1324
|
+
function post(endpoint, body) {
|
|
1325
|
+
return new Promise((resolve) => {
|
|
1326
|
+
const payload = JSON.stringify(body);
|
|
1327
|
+
const u = new URL(`${API_BASE5}${endpoint}`);
|
|
1328
|
+
const transport = u.protocol === "https:" ? https3 : http3;
|
|
1329
|
+
const req = transport.request(
|
|
1330
|
+
{
|
|
1331
|
+
hostname: u.hostname,
|
|
1332
|
+
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
1333
|
+
path: u.pathname,
|
|
1334
|
+
method: "POST",
|
|
1335
|
+
headers: {
|
|
1336
|
+
"Content-Type": "application/json",
|
|
1337
|
+
"Content-Length": Buffer.byteLength(payload)
|
|
1338
|
+
},
|
|
1339
|
+
timeout: 8e3
|
|
1340
|
+
},
|
|
1341
|
+
(res) => {
|
|
1342
|
+
res.resume();
|
|
1343
|
+
resolve();
|
|
1344
|
+
}
|
|
1345
|
+
);
|
|
1346
|
+
req.on("error", () => resolve());
|
|
1347
|
+
req.on("timeout", () => {
|
|
1348
|
+
req.destroy();
|
|
1349
|
+
resolve();
|
|
1350
|
+
});
|
|
1351
|
+
req.write(payload);
|
|
1352
|
+
req.end();
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
var HistoryService = class {
|
|
1356
|
+
constructor(pluginId, cwd) {
|
|
1357
|
+
this.pluginId = pluginId;
|
|
1358
|
+
this.cwd = cwd;
|
|
1359
|
+
}
|
|
1360
|
+
pluginId;
|
|
1361
|
+
cwd;
|
|
1362
|
+
get projectDir() {
|
|
1363
|
+
return path4.join(os4.homedir(), ".claude", "projects", encodeCwd(this.cwd));
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Read session list from disk and POST it to the API.
|
|
1367
|
+
* Called once ~2 s after Claude spawns (non-blocking).
|
|
1368
|
+
*/
|
|
1369
|
+
async load() {
|
|
1370
|
+
const dir = this.projectDir;
|
|
1371
|
+
let entries;
|
|
1372
|
+
try {
|
|
1373
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1374
|
+
} catch {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const sessions2 = [];
|
|
1378
|
+
for (const entry of entries) {
|
|
1379
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
1380
|
+
const id = path4.basename(entry.name, ".jsonl");
|
|
1381
|
+
const filePath = path4.join(dir, entry.name);
|
|
1382
|
+
let mtime = Date.now();
|
|
1383
|
+
try {
|
|
1384
|
+
mtime = fs4.statSync(filePath).mtimeMs;
|
|
1385
|
+
} catch {
|
|
1386
|
+
}
|
|
1387
|
+
let summary = "";
|
|
1388
|
+
try {
|
|
1389
|
+
const raw = fs4.readFileSync(filePath, "utf8");
|
|
1390
|
+
for (const line of raw.split("\n")) {
|
|
1391
|
+
if (!line.trim()) continue;
|
|
1392
|
+
try {
|
|
1393
|
+
const record = JSON.parse(line);
|
|
1394
|
+
if (record["type"] === "user") {
|
|
1395
|
+
const msg = record["message"];
|
|
1396
|
+
const text = extractText(msg?.["content"]).trim();
|
|
1397
|
+
if (text) {
|
|
1398
|
+
summary = text.slice(0, 120);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
if (summary) sessions2.push({ id, summary, timestamp: mtime });
|
|
1408
|
+
}
|
|
1409
|
+
if (sessions2.length === 0) return;
|
|
1410
|
+
sessions2.sort((a, b) => b.timestamp - a.timestamp);
|
|
1411
|
+
await post("/api/sessions/claude-sessions", { pluginId: this.pluginId, sessions: sessions2 });
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Read a specific session's full conversation and POST it to the API.
|
|
1415
|
+
* Called before sending the resume signal so clients can fetch it immediately.
|
|
1416
|
+
*/
|
|
1417
|
+
async loadConversation(sessionId) {
|
|
1418
|
+
const filePath = path4.join(this.projectDir, `${sessionId}.jsonl`);
|
|
1419
|
+
const messages = parseJsonl(filePath);
|
|
1420
|
+
await post("/api/sessions/claude-conversation", {
|
|
1421
|
+
pluginId: this.pluginId,
|
|
1422
|
+
sessionId,
|
|
1423
|
+
messages
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1245
1428
|
// src/commands/start.ts
|
|
1246
1429
|
function saveFilesTemp(files) {
|
|
1247
1430
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
1248
1431
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
1249
|
-
const tmpPath =
|
|
1250
|
-
|
|
1432
|
+
const tmpPath = path5.join(os5.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
1433
|
+
fs5.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
1251
1434
|
return tmpPath;
|
|
1252
1435
|
});
|
|
1253
1436
|
}
|
|
@@ -1269,7 +1452,7 @@ async function start() {
|
|
|
1269
1452
|
outputSvc.newTurn();
|
|
1270
1453
|
claude.sendCommand(prompt);
|
|
1271
1454
|
}
|
|
1272
|
-
const relay = new CommandRelayService(pluginId, (cmd) => {
|
|
1455
|
+
const relay = new CommandRelayService(pluginId, async (cmd) => {
|
|
1273
1456
|
switch (cmd.type) {
|
|
1274
1457
|
case "start_task": {
|
|
1275
1458
|
const prompt = cmd.payload.prompt;
|
|
@@ -1283,7 +1466,7 @@ async function start() {
|
|
|
1283
1466
|
setTimeout(() => {
|
|
1284
1467
|
for (const p2 of paths) {
|
|
1285
1468
|
try {
|
|
1286
|
-
|
|
1469
|
+
fs5.unlinkSync(p2);
|
|
1287
1470
|
} catch {
|
|
1288
1471
|
}
|
|
1289
1472
|
}
|
|
@@ -1312,6 +1495,15 @@ async function start() {
|
|
|
1312
1495
|
case "stop_task":
|
|
1313
1496
|
claude.interrupt();
|
|
1314
1497
|
break;
|
|
1498
|
+
case "resume_session": {
|
|
1499
|
+
const id = cmd.payload.id;
|
|
1500
|
+
const auto = cmd.payload.auto ?? false;
|
|
1501
|
+
if (!id) break;
|
|
1502
|
+
await historySvc.loadConversation(id);
|
|
1503
|
+
await outputSvc.newTurnResume(id);
|
|
1504
|
+
claude.restart(id, auto);
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1315
1507
|
}
|
|
1316
1508
|
});
|
|
1317
1509
|
ws.addHandler({
|
|
@@ -1335,7 +1527,7 @@ async function start() {
|
|
|
1335
1527
|
setTimeout(() => {
|
|
1336
1528
|
for (const p2 of paths) {
|
|
1337
1529
|
try {
|
|
1338
|
-
|
|
1530
|
+
fs5.unlinkSync(p2);
|
|
1339
1531
|
} catch {
|
|
1340
1532
|
}
|
|
1341
1533
|
}
|
|
@@ -1356,6 +1548,15 @@ async function start() {
|
|
|
1356
1548
|
claude.sendEscape();
|
|
1357
1549
|
} else if (cmdType === "stop_task") {
|
|
1358
1550
|
claude.interrupt();
|
|
1551
|
+
} else if (cmdType === "resume_session") {
|
|
1552
|
+
const id = inner.id;
|
|
1553
|
+
const auto = inner.auto ?? false;
|
|
1554
|
+
if (id) {
|
|
1555
|
+
historySvc.loadConversation(id).then(() => outputSvc.newTurnResume(id)).then(() => {
|
|
1556
|
+
claude.restart(id, auto);
|
|
1557
|
+
}).catch(() => {
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1359
1560
|
}
|
|
1360
1561
|
}
|
|
1361
1562
|
});
|
|
@@ -1383,6 +1584,11 @@ async function start() {
|
|
|
1383
1584
|
}
|
|
1384
1585
|
process.once("SIGINT", sigintHandler);
|
|
1385
1586
|
claude.spawn();
|
|
1587
|
+
const historySvc = new HistoryService(pluginId, process.cwd());
|
|
1588
|
+
setTimeout(() => {
|
|
1589
|
+
historySvc.load().catch(() => {
|
|
1590
|
+
});
|
|
1591
|
+
}, 2e3);
|
|
1386
1592
|
}
|
|
1387
1593
|
|
|
1388
1594
|
// src/commands/pair.ts
|