@punkcode/cli 0.1.13 → 0.1.15
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/cli.js +85 -144
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -493,13 +493,11 @@ function getModel() {
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
// src/lib/session.ts
|
|
496
|
-
import { readdir as readdir2, readFile as readFile2
|
|
496
|
+
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
497
497
|
import { join as join2 } from "path";
|
|
498
498
|
import { homedir as homedir2 } from "os";
|
|
499
|
+
import { listSessions as sdkListSessions } from "@anthropic-ai/claude-agent-sdk";
|
|
499
500
|
var CLAUDE_DIR = join2(homedir2(), ".claude", "projects");
|
|
500
|
-
function pathToProjectDir(dir) {
|
|
501
|
-
return dir.replace(/\//g, "-");
|
|
502
|
-
}
|
|
503
501
|
async function loadSession(sessionId) {
|
|
504
502
|
const sessionFile = `${sessionId}.jsonl`;
|
|
505
503
|
let projectDirs;
|
|
@@ -547,143 +545,26 @@ async function attachSubagentData(messages, subagentsDir) {
|
|
|
547
545
|
}
|
|
548
546
|
}));
|
|
549
547
|
}
|
|
550
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
551
|
-
function isValidSessionUUID(name) {
|
|
552
|
-
return UUID_RE.test(name);
|
|
553
|
-
}
|
|
554
548
|
async function listSessions(workingDirectory) {
|
|
555
|
-
let projectDirs;
|
|
556
549
|
try {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
550
|
+
const sdkSessions = await sdkListSessions({
|
|
551
|
+
...workingDirectory && { dir: workingDirectory },
|
|
552
|
+
limit: 50
|
|
553
|
+
});
|
|
554
|
+
return sdkSessions.map((s) => ({
|
|
555
|
+
sessionId: s.sessionId,
|
|
556
|
+
project: workingDirectory ?? s.cwd ?? "",
|
|
557
|
+
title: s.summary,
|
|
558
|
+
lastModified: s.lastModified,
|
|
559
|
+
cwd: s.cwd,
|
|
560
|
+
gitBranch: s.gitBranch,
|
|
561
|
+
fileSize: s.fileSize
|
|
562
|
+
}));
|
|
562
563
|
} catch {
|
|
563
564
|
return [];
|
|
564
565
|
}
|
|
565
|
-
const candidates = [];
|
|
566
|
-
for (const projectDir of projectDirs) {
|
|
567
|
-
const projectPath = join2(CLAUDE_DIR, projectDir);
|
|
568
|
-
let files;
|
|
569
|
-
try {
|
|
570
|
-
files = await readdir2(projectPath);
|
|
571
|
-
} catch {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
for (const file of files) {
|
|
575
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
576
|
-
const sessionId = file.replace(".jsonl", "");
|
|
577
|
-
if (!isValidSessionUUID(sessionId)) continue;
|
|
578
|
-
candidates.push({
|
|
579
|
-
sessionId,
|
|
580
|
-
// When a workingDirectory is provided, return the original path so it
|
|
581
|
-
// matches the device's defaultWorkingDirectory on the mobile side.
|
|
582
|
-
project: workingDirectory ?? projectDir,
|
|
583
|
-
filePath: join2(projectPath, file)
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const results = await Promise.all(
|
|
588
|
-
candidates.map(async (c) => {
|
|
589
|
-
try {
|
|
590
|
-
const fileStat = await stat(c.filePath);
|
|
591
|
-
const titleInfo = await extractTitle(c.filePath);
|
|
592
|
-
return {
|
|
593
|
-
sessionId: c.sessionId,
|
|
594
|
-
project: c.project,
|
|
595
|
-
title: titleInfo.title,
|
|
596
|
-
lastModified: fileStat.mtimeMs,
|
|
597
|
-
...titleInfo.summary && { summary: titleInfo.summary }
|
|
598
|
-
};
|
|
599
|
-
} catch {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
})
|
|
603
|
-
);
|
|
604
|
-
const sessions = results.filter((s) => s !== null);
|
|
605
|
-
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
606
|
-
return sessions.slice(0, 50);
|
|
607
|
-
}
|
|
608
|
-
var HEAD_READ_BYTES = 8192;
|
|
609
|
-
var TAIL_READ_BYTES = 16384;
|
|
610
|
-
async function extractTitle(filePath) {
|
|
611
|
-
const fh = await open(filePath, "r");
|
|
612
|
-
try {
|
|
613
|
-
const tailResult = await extractFromTail(fh, filePath);
|
|
614
|
-
if (tailResult) return tailResult;
|
|
615
|
-
const firstMsg = await extractFirstUserMessage(fh);
|
|
616
|
-
return { title: firstMsg };
|
|
617
|
-
} finally {
|
|
618
|
-
await fh.close();
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
async function extractFromTail(fh, filePath) {
|
|
622
|
-
const fileStat = await stat(filePath);
|
|
623
|
-
const fileSize = fileStat.size;
|
|
624
|
-
if (fileSize === 0) return null;
|
|
625
|
-
const readSize = Math.min(TAIL_READ_BYTES, fileSize);
|
|
626
|
-
const offset = fileSize - readSize;
|
|
627
|
-
const buf = Buffer.alloc(readSize);
|
|
628
|
-
const { bytesRead } = await fh.read(buf, 0, readSize, offset);
|
|
629
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
630
|
-
const lines = chunk.split("\n");
|
|
631
|
-
let customTitle = null;
|
|
632
|
-
let summary = null;
|
|
633
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
634
|
-
const line = lines[i].trim();
|
|
635
|
-
if (!line) continue;
|
|
636
|
-
try {
|
|
637
|
-
const entry = JSON.parse(line);
|
|
638
|
-
if (entry.type === "custom-title" && entry.title && !customTitle) {
|
|
639
|
-
customTitle = entry.title;
|
|
640
|
-
}
|
|
641
|
-
if (entry.type === "summary" && entry.summary && !summary) {
|
|
642
|
-
summary = typeof entry.summary === "string" ? entry.summary : null;
|
|
643
|
-
}
|
|
644
|
-
if (customTitle && summary) break;
|
|
645
|
-
} catch {
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
if (customTitle) return { title: customTitle, summary: summary ?? void 0 };
|
|
649
|
-
if (summary) return { title: summary, summary };
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
async function extractFirstUserMessage(fh) {
|
|
653
|
-
const buf = Buffer.alloc(HEAD_READ_BYTES);
|
|
654
|
-
const { bytesRead } = await fh.read(buf, 0, HEAD_READ_BYTES, 0);
|
|
655
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
656
|
-
const lines = chunk.split("\n");
|
|
657
|
-
const metaUuids = /* @__PURE__ */ new Set();
|
|
658
|
-
for (const line of lines.slice(0, 20)) {
|
|
659
|
-
if (!line.trim()) continue;
|
|
660
|
-
try {
|
|
661
|
-
const entry = JSON.parse(line);
|
|
662
|
-
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
663
|
-
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
664
|
-
metaUuids.add(entry.uuid);
|
|
665
|
-
}
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
if (entry.type === "user" && entry.message?.role === "user") {
|
|
669
|
-
const content = entry.message.content;
|
|
670
|
-
if (typeof content === "string" && content.trim()) {
|
|
671
|
-
return content.trim().slice(0, 100);
|
|
672
|
-
}
|
|
673
|
-
if (Array.isArray(content)) {
|
|
674
|
-
const textBlock = content.find(
|
|
675
|
-
(b) => b.type === "text" && "text" in b && b.text && !b.text.startsWith("[Request interrupted")
|
|
676
|
-
);
|
|
677
|
-
if (textBlock && "text" in textBlock && typeof textBlock.text === "string") {
|
|
678
|
-
return textBlock.text.slice(0, 100);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
} catch {
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
return "Untitled session";
|
|
686
566
|
}
|
|
567
|
+
var TOOL_RESULT_PREVIEW_BYTES = 2048;
|
|
687
568
|
var ANSI_RE = /\u001b\[\d*m/g;
|
|
688
569
|
function stripAnsi(text) {
|
|
689
570
|
return text.replace(ANSI_RE, "");
|
|
@@ -692,6 +573,7 @@ function parseSessionFile(content) {
|
|
|
692
573
|
const messages = [];
|
|
693
574
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
694
575
|
const metaUuids = /* @__PURE__ */ new Set();
|
|
576
|
+
const taskNotifications = /* @__PURE__ */ new Map();
|
|
695
577
|
for (const line of lines) {
|
|
696
578
|
try {
|
|
697
579
|
const entry = JSON.parse(line);
|
|
@@ -761,6 +643,21 @@ function parseSessionFile(content) {
|
|
|
761
643
|
}
|
|
762
644
|
continue;
|
|
763
645
|
}
|
|
646
|
+
if (entry.type === "user" && typeof entry.message?.content === "string") {
|
|
647
|
+
const raw = entry.message.content;
|
|
648
|
+
const notifMatch = raw.match(/<task-notification>([\s\S]*?)<\/task-notification>/);
|
|
649
|
+
if (notifMatch) {
|
|
650
|
+
const inner = notifMatch[1];
|
|
651
|
+
const toolUseId = inner.match(/<tool-use-id>(.*?)<\/tool-use-id>/)?.[1];
|
|
652
|
+
if (toolUseId) {
|
|
653
|
+
const taskId = inner.match(/<task-id>(.*?)<\/task-id>/)?.[1] ?? "";
|
|
654
|
+
const status = inner.match(/<status>(.*?)<\/status>/)?.[1] ?? "completed";
|
|
655
|
+
const summary = inner.match(/<summary>([\s\S]*?)<\/summary>/)?.[1]?.trim() ?? "";
|
|
656
|
+
taskNotifications.set(toolUseId, { taskId, status, summary });
|
|
657
|
+
}
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
764
661
|
if ((entry.type === "user" || entry.type === "assistant") && entry.message) {
|
|
765
662
|
const msgContent = entry.message.content;
|
|
766
663
|
messages.push({
|
|
@@ -809,12 +706,27 @@ function parseSessionFile(content) {
|
|
|
809
706
|
);
|
|
810
707
|
if (toolUse) {
|
|
811
708
|
if (dataUri) toolUse.imageUri = dataUri;
|
|
812
|
-
if (resultText) toolUse.result = resultText;
|
|
709
|
+
if (resultText) toolUse.result = resultText.length > TOOL_RESULT_PREVIEW_BYTES ? resultText.slice(0, TOOL_RESULT_PREVIEW_BYTES) + "\u2026" : resultText;
|
|
813
710
|
break;
|
|
814
711
|
}
|
|
815
712
|
}
|
|
816
713
|
}
|
|
817
714
|
}
|
|
715
|
+
if (taskNotifications.size > 0) {
|
|
716
|
+
for (const msg of messages) {
|
|
717
|
+
if (msg.role !== "assistant") continue;
|
|
718
|
+
const blocks = msg.content;
|
|
719
|
+
if (!Array.isArray(blocks)) continue;
|
|
720
|
+
for (const block of blocks) {
|
|
721
|
+
if (block.type !== "tool_use" || block.name !== "Task") continue;
|
|
722
|
+
const notif = taskNotifications.get(block.id);
|
|
723
|
+
if (notif) {
|
|
724
|
+
block.taskStatus = notif.status;
|
|
725
|
+
block.taskSummary = notif.summary;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
818
730
|
const merged = [];
|
|
819
731
|
for (const msg of messages) {
|
|
820
732
|
if (msg.role === "user") {
|
|
@@ -1261,7 +1173,8 @@ async function connect(server, options) {
|
|
|
1261
1173
|
reconnection: true,
|
|
1262
1174
|
reconnectionAttempts: Infinity,
|
|
1263
1175
|
reconnectionDelay: 1e3,
|
|
1264
|
-
reconnectionDelayMax: 5e3
|
|
1176
|
+
reconnectionDelayMax: 5e3,
|
|
1177
|
+
perMessageDeflate: { threshold: 1024 }
|
|
1265
1178
|
});
|
|
1266
1179
|
const activeSessions = /* @__PURE__ */ new Map();
|
|
1267
1180
|
const sleepLock = preventIdleSleep();
|
|
@@ -1361,6 +1274,9 @@ async function connect(server, options) {
|
|
|
1361
1274
|
logger.error(detail, `Connection error: ${reason}`);
|
|
1362
1275
|
logger.debug({ err }, "Connection error (raw)");
|
|
1363
1276
|
});
|
|
1277
|
+
socket.on("error", (err) => {
|
|
1278
|
+
logger.error({ err }, "Socket error");
|
|
1279
|
+
});
|
|
1364
1280
|
const refreshInterval = setInterval(async () => {
|
|
1365
1281
|
try {
|
|
1366
1282
|
const token = await refreshIdToken();
|
|
@@ -1439,6 +1355,7 @@ function formatConnectionError(err) {
|
|
|
1439
1355
|
return { reason, ...result };
|
|
1440
1356
|
}
|
|
1441
1357
|
function send(socket, event, msg) {
|
|
1358
|
+
if (!socket.connected) return;
|
|
1442
1359
|
socket.emit(event, msg);
|
|
1443
1360
|
}
|
|
1444
1361
|
function handlePrompt(socket, msg, activeSessions) {
|
|
@@ -1518,14 +1435,38 @@ async function handleListSessions(socket, msg, defaultCwd) {
|
|
|
1518
1435
|
send(socket, "response", { type: "sessions_list", sessions, requestId: id });
|
|
1519
1436
|
logger.info({ count: sessions.length }, "Listed sessions");
|
|
1520
1437
|
}
|
|
1438
|
+
var DEFAULT_HISTORY_LIMIT = 30;
|
|
1439
|
+
var MAX_PAYLOAD_BYTES = 19.5 * 1024 * 1024;
|
|
1440
|
+
function fitToPayloadLimit(messages) {
|
|
1441
|
+
if (Buffer.byteLength(JSON.stringify(messages), "utf8") <= MAX_PAYLOAD_BYTES) {
|
|
1442
|
+
return messages;
|
|
1443
|
+
}
|
|
1444
|
+
let lo = 0;
|
|
1445
|
+
let hi = messages.length;
|
|
1446
|
+
while (lo < hi) {
|
|
1447
|
+
const mid = Math.floor((lo + hi + 1) / 2);
|
|
1448
|
+
const slice = messages.slice(messages.length - mid);
|
|
1449
|
+
if (Buffer.byteLength(JSON.stringify(slice), "utf8") <= MAX_PAYLOAD_BYTES) {
|
|
1450
|
+
lo = mid;
|
|
1451
|
+
} else {
|
|
1452
|
+
hi = mid - 1;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return messages.slice(messages.length - lo);
|
|
1456
|
+
}
|
|
1521
1457
|
async function handleLoadSession(socket, msg) {
|
|
1522
|
-
const { id, sessionId } = msg;
|
|
1458
|
+
const { id, sessionId, limit = DEFAULT_HISTORY_LIMIT } = msg;
|
|
1523
1459
|
const log2 = createChildLogger({ sessionId });
|
|
1524
1460
|
log2.info("Loading session...");
|
|
1525
|
-
const
|
|
1526
|
-
if (
|
|
1527
|
-
|
|
1528
|
-
|
|
1461
|
+
const all = await loadSession(sessionId);
|
|
1462
|
+
if (all) {
|
|
1463
|
+
const sliced = limit > 0 && all.length > limit ? all.slice(-limit) : all;
|
|
1464
|
+
const messages = fitToPayloadLimit(sliced);
|
|
1465
|
+
if (messages.length < sliced.length) {
|
|
1466
|
+
log2.warn({ requested: sliced.length, sent: messages.length }, "Session payload trimmed to fit size limit");
|
|
1467
|
+
}
|
|
1468
|
+
send(socket, "response", { type: "history", messages, total: all.length, requestId: id });
|
|
1469
|
+
log2.info({ count: messages.length, total: all.length }, "Session loaded");
|
|
1529
1470
|
} else {
|
|
1530
1471
|
send(socket, "response", { type: "session_not_found", session_id: sessionId, requestId: id });
|
|
1531
1472
|
log2.warn("Session not found");
|
|
@@ -1627,9 +1568,9 @@ function handleCheckPath(socket, msg) {
|
|
|
1627
1568
|
let isDirectory = false;
|
|
1628
1569
|
let parentExists = false;
|
|
1629
1570
|
try {
|
|
1630
|
-
const
|
|
1571
|
+
const stat = fs3.statSync(checkTarget);
|
|
1631
1572
|
exists = true;
|
|
1632
|
-
isDirectory =
|
|
1573
|
+
isDirectory = stat.isDirectory();
|
|
1633
1574
|
} catch {
|
|
1634
1575
|
}
|
|
1635
1576
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@punkcode/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Control Claude Code from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"vitest": "^4.0.18"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
56
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.62",
|
|
57
57
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
58
58
|
"commander": "^14.0.3",
|
|
59
59
|
"execa": "^9.6.1",
|