@punkcode/cli 0.1.12 → 0.1.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/cli.js +265 -148
- 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;
|
|
@@ -512,149 +510,61 @@ async function loadSession(sessionId) {
|
|
|
512
510
|
const sessionPath = join2(CLAUDE_DIR, projectDir, sessionFile);
|
|
513
511
|
try {
|
|
514
512
|
const content = await readFile2(sessionPath, "utf-8");
|
|
515
|
-
|
|
513
|
+
const messages = parseSessionFile(content);
|
|
514
|
+
const subagentsDir = join2(CLAUDE_DIR, projectDir, sessionId, "subagents");
|
|
515
|
+
await attachSubagentData(messages, subagentsDir);
|
|
516
|
+
return messages;
|
|
516
517
|
} catch {
|
|
517
518
|
}
|
|
518
519
|
}
|
|
519
520
|
return null;
|
|
520
521
|
}
|
|
521
|
-
var
|
|
522
|
-
function
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
522
|
+
var AGENT_ID_RE = /agentId: (\w+)/;
|
|
523
|
+
async function attachSubagentData(messages, subagentsDir) {
|
|
524
|
+
const taskBlocks = [];
|
|
525
|
+
for (const msg of messages) {
|
|
526
|
+
if (msg.role !== "assistant") continue;
|
|
527
|
+
const blocks = msg.content;
|
|
528
|
+
if (!Array.isArray(blocks)) continue;
|
|
529
|
+
for (const block of blocks) {
|
|
530
|
+
if (block.type !== "tool_use" || block.name !== "Task") continue;
|
|
531
|
+
const result = block.result;
|
|
532
|
+
if (typeof result !== "string") continue;
|
|
533
|
+
const match = result.match(AGENT_ID_RE);
|
|
534
|
+
if (match) {
|
|
535
|
+
taskBlocks.push({ block, agentId: match[1] });
|
|
536
|
+
}
|
|
532
537
|
}
|
|
533
|
-
} catch {
|
|
534
|
-
return [];
|
|
535
538
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const projectPath = join2(CLAUDE_DIR, projectDir);
|
|
539
|
-
let files;
|
|
539
|
+
if (taskBlocks.length === 0) return;
|
|
540
|
+
await Promise.all(taskBlocks.map(async ({ block, agentId }) => {
|
|
540
541
|
try {
|
|
541
|
-
|
|
542
|
+
const content = await readFile2(join2(subagentsDir, `agent-${agentId}.jsonl`), "utf-8");
|
|
543
|
+
block.subagentMessages = parseSessionFile(content);
|
|
542
544
|
} catch {
|
|
543
|
-
continue;
|
|
544
545
|
}
|
|
545
|
-
|
|
546
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
547
|
-
const sessionId = file.replace(".jsonl", "");
|
|
548
|
-
if (!isValidSessionUUID(sessionId)) continue;
|
|
549
|
-
candidates.push({
|
|
550
|
-
sessionId,
|
|
551
|
-
// When a workingDirectory is provided, return the original path so it
|
|
552
|
-
// matches the device's defaultWorkingDirectory on the mobile side.
|
|
553
|
-
project: workingDirectory ?? projectDir,
|
|
554
|
-
filePath: join2(projectPath, file)
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
const results = await Promise.all(
|
|
559
|
-
candidates.map(async (c) => {
|
|
560
|
-
try {
|
|
561
|
-
const fileStat = await stat(c.filePath);
|
|
562
|
-
const titleInfo = await extractTitle(c.filePath);
|
|
563
|
-
return {
|
|
564
|
-
sessionId: c.sessionId,
|
|
565
|
-
project: c.project,
|
|
566
|
-
title: titleInfo.title,
|
|
567
|
-
lastModified: fileStat.mtimeMs,
|
|
568
|
-
...titleInfo.summary && { summary: titleInfo.summary }
|
|
569
|
-
};
|
|
570
|
-
} catch {
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
})
|
|
574
|
-
);
|
|
575
|
-
const sessions = results.filter((s) => s !== null);
|
|
576
|
-
sessions.sort((a, b) => b.lastModified - a.lastModified);
|
|
577
|
-
return sessions.slice(0, 50);
|
|
546
|
+
}));
|
|
578
547
|
}
|
|
579
|
-
|
|
580
|
-
var TAIL_READ_BYTES = 16384;
|
|
581
|
-
async function extractTitle(filePath) {
|
|
582
|
-
const fh = await open(filePath, "r");
|
|
548
|
+
async function listSessions(workingDirectory) {
|
|
583
549
|
try {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const { bytesRead } = await fh.read(buf, 0, readSize, offset);
|
|
600
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
601
|
-
const lines = chunk.split("\n");
|
|
602
|
-
let customTitle = null;
|
|
603
|
-
let summary = null;
|
|
604
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
605
|
-
const line = lines[i].trim();
|
|
606
|
-
if (!line) continue;
|
|
607
|
-
try {
|
|
608
|
-
const entry = JSON.parse(line);
|
|
609
|
-
if (entry.type === "custom-title" && entry.title && !customTitle) {
|
|
610
|
-
customTitle = entry.title;
|
|
611
|
-
}
|
|
612
|
-
if (entry.type === "summary" && entry.summary && !summary) {
|
|
613
|
-
summary = typeof entry.summary === "string" ? entry.summary : null;
|
|
614
|
-
}
|
|
615
|
-
if (customTitle && summary) break;
|
|
616
|
-
} catch {
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if (customTitle) return { title: customTitle, summary: summary ?? void 0 };
|
|
620
|
-
if (summary) return { title: summary, summary };
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
async function extractFirstUserMessage(fh) {
|
|
624
|
-
const buf = Buffer.alloc(HEAD_READ_BYTES);
|
|
625
|
-
const { bytesRead } = await fh.read(buf, 0, HEAD_READ_BYTES, 0);
|
|
626
|
-
const chunk = buf.toString("utf-8", 0, bytesRead);
|
|
627
|
-
const lines = chunk.split("\n");
|
|
628
|
-
const metaUuids = /* @__PURE__ */ new Set();
|
|
629
|
-
for (const line of lines.slice(0, 20)) {
|
|
630
|
-
if (!line.trim()) continue;
|
|
631
|
-
try {
|
|
632
|
-
const entry = JSON.parse(line);
|
|
633
|
-
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
634
|
-
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
635
|
-
metaUuids.add(entry.uuid);
|
|
636
|
-
}
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
if (entry.type === "user" && entry.message?.role === "user") {
|
|
640
|
-
const content = entry.message.content;
|
|
641
|
-
if (typeof content === "string" && content.trim()) {
|
|
642
|
-
return content.trim().slice(0, 100);
|
|
643
|
-
}
|
|
644
|
-
if (Array.isArray(content)) {
|
|
645
|
-
const textBlock = content.find(
|
|
646
|
-
(b) => b.type === "text" && "text" in b && b.text && !b.text.startsWith("[Request interrupted")
|
|
647
|
-
);
|
|
648
|
-
if (textBlock && "text" in textBlock && typeof textBlock.text === "string") {
|
|
649
|
-
return textBlock.text.slice(0, 100);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
} catch {
|
|
654
|
-
}
|
|
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
|
+
}));
|
|
563
|
+
} catch {
|
|
564
|
+
return [];
|
|
655
565
|
}
|
|
656
|
-
return "Untitled session";
|
|
657
566
|
}
|
|
567
|
+
var TOOL_RESULT_PREVIEW_BYTES = 2048;
|
|
658
568
|
var ANSI_RE = /\u001b\[\d*m/g;
|
|
659
569
|
function stripAnsi(text) {
|
|
660
570
|
return text.replace(ANSI_RE, "");
|
|
@@ -663,6 +573,7 @@ function parseSessionFile(content) {
|
|
|
663
573
|
const messages = [];
|
|
664
574
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
665
575
|
const metaUuids = /* @__PURE__ */ new Set();
|
|
576
|
+
const taskNotifications = /* @__PURE__ */ new Map();
|
|
666
577
|
for (const line of lines) {
|
|
667
578
|
try {
|
|
668
579
|
const entry = JSON.parse(line);
|
|
@@ -732,6 +643,21 @@ function parseSessionFile(content) {
|
|
|
732
643
|
}
|
|
733
644
|
continue;
|
|
734
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
|
+
}
|
|
735
661
|
if ((entry.type === "user" || entry.type === "assistant") && entry.message) {
|
|
736
662
|
const msgContent = entry.message.content;
|
|
737
663
|
messages.push({
|
|
@@ -780,9 +706,24 @@ function parseSessionFile(content) {
|
|
|
780
706
|
);
|
|
781
707
|
if (toolUse) {
|
|
782
708
|
if (dataUri) toolUse.imageUri = dataUri;
|
|
783
|
-
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;
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
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;
|
|
784
726
|
}
|
|
785
|
-
break;
|
|
786
727
|
}
|
|
787
728
|
}
|
|
788
729
|
}
|
|
@@ -915,7 +856,7 @@ function escapeRegex(str) {
|
|
|
915
856
|
|
|
916
857
|
// src/lib/directory-discovery.ts
|
|
917
858
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
918
|
-
async function findProjectDirectory(description, searchRoot, rejections) {
|
|
859
|
+
async function findProjectDirectory(description, searchRoot, rejections, signal) {
|
|
919
860
|
let prompt2 = `Find up to 3 project directories on this machine that best match: "${description}"
|
|
920
861
|
|
|
921
862
|
Search starting from ${searchRoot}. Rank by relevance. Include a brief reason for each match.`;
|
|
@@ -961,6 +902,8 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
961
902
|
}
|
|
962
903
|
}
|
|
963
904
|
});
|
|
905
|
+
const onAbort = () => q.close();
|
|
906
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
964
907
|
try {
|
|
965
908
|
for await (const msg of q) {
|
|
966
909
|
if (msg.type === "result") {
|
|
@@ -973,10 +916,10 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
973
916
|
}
|
|
974
917
|
const result = resultMsg.result?.trim();
|
|
975
918
|
if (result && result !== "null" && result.startsWith("/")) {
|
|
976
|
-
const
|
|
977
|
-
const name =
|
|
978
|
-
logger.info({ path:
|
|
979
|
-
return [{ path:
|
|
919
|
+
const path4 = result.split("\n")[0].trim();
|
|
920
|
+
const name = path4.split("/").pop() ?? path4;
|
|
921
|
+
logger.info({ path: path4, name }, "Project directory found (text fallback)");
|
|
922
|
+
return [{ path: path4, name, reason: "Best match" }];
|
|
980
923
|
}
|
|
981
924
|
logger.info("No matching directories found");
|
|
982
925
|
return [];
|
|
@@ -986,10 +929,81 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
986
929
|
}
|
|
987
930
|
}
|
|
988
931
|
} finally {
|
|
932
|
+
signal?.removeEventListener("abort", onAbort);
|
|
989
933
|
q.close();
|
|
990
934
|
}
|
|
991
935
|
return [];
|
|
992
936
|
}
|
|
937
|
+
async function suggestProjectLocation(description, searchRoot, name, signal) {
|
|
938
|
+
const prompt2 = `Suggest up to 3 suitable locations on this machine to create a new project.
|
|
939
|
+
|
|
940
|
+
User's description: "${description}"
|
|
941
|
+
${name ? `Desired project name: "${name}"` : ""}
|
|
942
|
+
|
|
943
|
+
Search starting from ${searchRoot}. Look at common project directories (e.g. ~/github, ~/projects, ~/code, ~/Desktop).
|
|
944
|
+
For each suggestion, provide the full path WHERE the project folder would be created (including the project name as the last segment), a human-readable name, and a brief reason.`;
|
|
945
|
+
const q = query2({
|
|
946
|
+
prompt: prompt2,
|
|
947
|
+
options: {
|
|
948
|
+
systemPrompt: {
|
|
949
|
+
type: "preset",
|
|
950
|
+
preset: "claude_code",
|
|
951
|
+
append: "IMPORTANT: When searching for directories, always try listing likely parent directories with ls FIRST (e.g. ls ~/github, ls ~/projects). Only use find as a last resort, and always with -maxdepth 3. Never scan the entire home directory. The path in each suggestion must be the FULL path including the new project folder name."
|
|
952
|
+
},
|
|
953
|
+
permissionMode: "bypassPermissions",
|
|
954
|
+
persistSession: false,
|
|
955
|
+
cwd: searchRoot,
|
|
956
|
+
outputFormat: {
|
|
957
|
+
type: "json_schema",
|
|
958
|
+
schema: {
|
|
959
|
+
type: "object",
|
|
960
|
+
properties: {
|
|
961
|
+
suggestions: {
|
|
962
|
+
type: "array",
|
|
963
|
+
items: {
|
|
964
|
+
type: "object",
|
|
965
|
+
properties: {
|
|
966
|
+
path: { type: "string", description: "Absolute path for the new project directory (including project folder name)" },
|
|
967
|
+
name: { type: "string", description: "Human-readable project name" },
|
|
968
|
+
reason: { type: "string", description: "Brief reason why this location is suitable" }
|
|
969
|
+
},
|
|
970
|
+
required: ["path", "name", "reason"]
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
required: ["suggestions"]
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
const onAbort = () => q.close();
|
|
980
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
981
|
+
try {
|
|
982
|
+
for await (const msg of q) {
|
|
983
|
+
if (msg.type === "result") {
|
|
984
|
+
const resultMsg = msg;
|
|
985
|
+
if (resultMsg.subtype === "success") {
|
|
986
|
+
const structured = resultMsg.structured_output;
|
|
987
|
+
if (structured?.suggestions?.length) {
|
|
988
|
+
logger.info({ count: structured.suggestions.length }, "Location suggestions found (structured)");
|
|
989
|
+
return structured.suggestions;
|
|
990
|
+
}
|
|
991
|
+
logger.info("No location suggestions found");
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
994
|
+
logger.warn({ subtype: resultMsg.subtype }, "Location suggestion query failed");
|
|
995
|
+
return [];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
} finally {
|
|
999
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1000
|
+
q.close();
|
|
1001
|
+
}
|
|
1002
|
+
return [];
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// src/commands/connect.ts
|
|
1006
|
+
import path3 from "path";
|
|
993
1007
|
|
|
994
1008
|
// src/lib/auth.ts
|
|
995
1009
|
import fs2 from "fs";
|
|
@@ -1215,7 +1229,22 @@ async function connect(server, options) {
|
|
|
1215
1229
|
});
|
|
1216
1230
|
socket.on("find-project", (msg) => {
|
|
1217
1231
|
if (msg.type === "find-project") {
|
|
1218
|
-
handleFindProject(socket, msg, defaultCwd);
|
|
1232
|
+
handleFindProject(socket, msg, defaultCwd, activeSessions);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
socket.on("suggest-project-location", (msg) => {
|
|
1236
|
+
if (msg.type === "suggest-project-location") {
|
|
1237
|
+
handleSuggestProjectLocation(socket, msg, activeSessions);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
socket.on("create-project", (msg) => {
|
|
1241
|
+
if (msg.type === "create-project") {
|
|
1242
|
+
handleCreateProject(socket, msg);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
socket.on("check-path", (msg) => {
|
|
1246
|
+
if (msg.type === "check-path") {
|
|
1247
|
+
handleCheckPath(socket, msg);
|
|
1219
1248
|
}
|
|
1220
1249
|
});
|
|
1221
1250
|
socket.on("cancel", (msg) => {
|
|
@@ -1244,6 +1273,9 @@ async function connect(server, options) {
|
|
|
1244
1273
|
logger.error(detail, `Connection error: ${reason}`);
|
|
1245
1274
|
logger.debug({ err }, "Connection error (raw)");
|
|
1246
1275
|
});
|
|
1276
|
+
socket.on("error", (err) => {
|
|
1277
|
+
logger.error({ err }, "Socket error");
|
|
1278
|
+
});
|
|
1247
1279
|
const refreshInterval = setInterval(async () => {
|
|
1248
1280
|
try {
|
|
1249
1281
|
const token = await refreshIdToken();
|
|
@@ -1322,6 +1354,7 @@ function formatConnectionError(err) {
|
|
|
1322
1354
|
return { reason, ...result };
|
|
1323
1355
|
}
|
|
1324
1356
|
function send(socket, event, msg) {
|
|
1357
|
+
if (!socket.connected) return;
|
|
1325
1358
|
socket.emit(event, msg);
|
|
1326
1359
|
}
|
|
1327
1360
|
function handlePrompt(socket, msg, activeSessions) {
|
|
@@ -1389,6 +1422,8 @@ function handleCancel(id, activeSessions) {
|
|
|
1389
1422
|
session.abort();
|
|
1390
1423
|
activeSessions.delete(id);
|
|
1391
1424
|
logger.info({ sessionId: id }, "Session cancelled");
|
|
1425
|
+
} else {
|
|
1426
|
+
logger.warn({ sessionId: id }, "Cancel: session not found in activeSessions");
|
|
1392
1427
|
}
|
|
1393
1428
|
}
|
|
1394
1429
|
async function handleListSessions(socket, msg, defaultCwd) {
|
|
@@ -1399,14 +1434,16 @@ async function handleListSessions(socket, msg, defaultCwd) {
|
|
|
1399
1434
|
send(socket, "response", { type: "sessions_list", sessions, requestId: id });
|
|
1400
1435
|
logger.info({ count: sessions.length }, "Listed sessions");
|
|
1401
1436
|
}
|
|
1437
|
+
var DEFAULT_HISTORY_LIMIT = 30;
|
|
1402
1438
|
async function handleLoadSession(socket, msg) {
|
|
1403
|
-
const { id, sessionId } = msg;
|
|
1439
|
+
const { id, sessionId, limit = DEFAULT_HISTORY_LIMIT } = msg;
|
|
1404
1440
|
const log2 = createChildLogger({ sessionId });
|
|
1405
1441
|
log2.info("Loading session...");
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1442
|
+
const all = await loadSession(sessionId);
|
|
1443
|
+
if (all) {
|
|
1444
|
+
const messages = limit > 0 && all.length > limit ? all.slice(-limit) : all;
|
|
1445
|
+
send(socket, "response", { type: "history", messages, total: all.length, requestId: id });
|
|
1446
|
+
log2.info({ count: messages.length, total: all.length }, "Session loaded");
|
|
1410
1447
|
} else {
|
|
1411
1448
|
send(socket, "response", { type: "session_not_found", session_id: sessionId, requestId: id });
|
|
1412
1449
|
log2.warn("Session not found");
|
|
@@ -1426,20 +1463,100 @@ async function handleGetCommands(socket, msg) {
|
|
|
1426
1463
|
log2.error({ err }, "Commands error");
|
|
1427
1464
|
}
|
|
1428
1465
|
}
|
|
1429
|
-
async function handleFindProject(socket, msg, _defaultCwd) {
|
|
1466
|
+
async function handleFindProject(socket, msg, _defaultCwd, activeSessions) {
|
|
1430
1467
|
const { id, description, rootDirectory, rejections } = msg;
|
|
1431
1468
|
const searchRoot = rootDirectory ?? os3.homedir();
|
|
1432
1469
|
const log2 = createChildLogger({ requestId: id });
|
|
1433
1470
|
log2.info({ description, searchRoot }, "Finding project directory...");
|
|
1471
|
+
const ac = new AbortController();
|
|
1472
|
+
const handle = {
|
|
1473
|
+
abort: () => ac.abort(),
|
|
1474
|
+
resolvePermission: () => {
|
|
1475
|
+
},
|
|
1476
|
+
setPermissionMode: async () => {
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
activeSessions.set(id, handle);
|
|
1434
1480
|
try {
|
|
1435
|
-
const suggestions = await findProjectDirectory(description, searchRoot, rejections);
|
|
1481
|
+
const suggestions = await findProjectDirectory(description, searchRoot, rejections, ac.signal);
|
|
1436
1482
|
send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
|
|
1437
1483
|
log2.info({ count: suggestions.length }, "Project suggestions sent");
|
|
1438
1484
|
} catch (err) {
|
|
1485
|
+
if (ac.signal.aborted) {
|
|
1486
|
+
log2.info("Find project cancelled");
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1439
1489
|
const message = err instanceof Error ? err.message : String(err);
|
|
1440
1490
|
send(socket, "response", { type: "error", message, requestId: id });
|
|
1441
1491
|
log2.error({ err }, "Find project error");
|
|
1492
|
+
} finally {
|
|
1493
|
+
activeSessions.delete(id);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async function handleSuggestProjectLocation(socket, msg, activeSessions) {
|
|
1497
|
+
const { id, description, name, rootDirectory } = msg;
|
|
1498
|
+
const searchRoot = rootDirectory ?? os3.homedir();
|
|
1499
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1500
|
+
log2.info({ description, name, searchRoot }, "Suggesting project location...");
|
|
1501
|
+
const ac = new AbortController();
|
|
1502
|
+
const handle = {
|
|
1503
|
+
abort: () => ac.abort(),
|
|
1504
|
+
resolvePermission: () => {
|
|
1505
|
+
},
|
|
1506
|
+
setPermissionMode: async () => {
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
activeSessions.set(id, handle);
|
|
1510
|
+
try {
|
|
1511
|
+
const suggestions = await suggestProjectLocation(description, searchRoot, name, ac.signal);
|
|
1512
|
+
send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
|
|
1513
|
+
log2.info({ count: suggestions.length }, "Location suggestions sent");
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
if (ac.signal.aborted) {
|
|
1516
|
+
log2.info("Suggest project location cancelled");
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1520
|
+
send(socket, "response", { type: "error", message, requestId: id });
|
|
1521
|
+
log2.error({ err }, "Suggest project location error");
|
|
1522
|
+
} finally {
|
|
1523
|
+
activeSessions.delete(id);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function handleCreateProject(socket, msg) {
|
|
1527
|
+
const { id, path: projectPath } = msg;
|
|
1528
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1529
|
+
log2.info({ path: projectPath }, "Creating project directory...");
|
|
1530
|
+
try {
|
|
1531
|
+
fs3.mkdirSync(projectPath, { recursive: true });
|
|
1532
|
+
send(socket, "response", { type: "project_created", path: projectPath, requestId: id });
|
|
1533
|
+
log2.info({ path: projectPath }, "Project directory created");
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1536
|
+
send(socket, "response", { type: "error", message, requestId: id });
|
|
1537
|
+
log2.error({ err }, "Create project error");
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function handleCheckPath(socket, msg) {
|
|
1541
|
+
const { id, path: checkTarget } = msg;
|
|
1542
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1543
|
+
log2.info({ path: checkTarget }, "Checking path...");
|
|
1544
|
+
let exists = false;
|
|
1545
|
+
let isDirectory = false;
|
|
1546
|
+
let parentExists = false;
|
|
1547
|
+
try {
|
|
1548
|
+
const stat = fs3.statSync(checkTarget);
|
|
1549
|
+
exists = true;
|
|
1550
|
+
isDirectory = stat.isDirectory();
|
|
1551
|
+
} catch {
|
|
1552
|
+
}
|
|
1553
|
+
try {
|
|
1554
|
+
const parentStat = fs3.statSync(path3.dirname(checkTarget));
|
|
1555
|
+
parentExists = parentStat.isDirectory();
|
|
1556
|
+
} catch {
|
|
1442
1557
|
}
|
|
1558
|
+
send(socket, "response", { type: "path_check", exists, isDirectory, parentExists, requestId: id });
|
|
1559
|
+
log2.info({ exists, isDirectory, parentExists }, "Path check complete");
|
|
1443
1560
|
}
|
|
1444
1561
|
async function handleGetContext(socket, msg, defaultCwd) {
|
|
1445
1562
|
const { id, sessionId } = msg;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@punkcode/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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",
|