@punkcode/cli 0.1.12 → 0.1.13
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 +209 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -512,12 +512,41 @@ async function loadSession(sessionId) {
|
|
|
512
512
|
const sessionPath = join2(CLAUDE_DIR, projectDir, sessionFile);
|
|
513
513
|
try {
|
|
514
514
|
const content = await readFile2(sessionPath, "utf-8");
|
|
515
|
-
|
|
515
|
+
const messages = parseSessionFile(content);
|
|
516
|
+
const subagentsDir = join2(CLAUDE_DIR, projectDir, sessionId, "subagents");
|
|
517
|
+
await attachSubagentData(messages, subagentsDir);
|
|
518
|
+
return messages;
|
|
516
519
|
} catch {
|
|
517
520
|
}
|
|
518
521
|
}
|
|
519
522
|
return null;
|
|
520
523
|
}
|
|
524
|
+
var AGENT_ID_RE = /agentId: (\w+)/;
|
|
525
|
+
async function attachSubagentData(messages, subagentsDir) {
|
|
526
|
+
const taskBlocks = [];
|
|
527
|
+
for (const msg of messages) {
|
|
528
|
+
if (msg.role !== "assistant") continue;
|
|
529
|
+
const blocks = msg.content;
|
|
530
|
+
if (!Array.isArray(blocks)) continue;
|
|
531
|
+
for (const block of blocks) {
|
|
532
|
+
if (block.type !== "tool_use" || block.name !== "Task") continue;
|
|
533
|
+
const result = block.result;
|
|
534
|
+
if (typeof result !== "string") continue;
|
|
535
|
+
const match = result.match(AGENT_ID_RE);
|
|
536
|
+
if (match) {
|
|
537
|
+
taskBlocks.push({ block, agentId: match[1] });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (taskBlocks.length === 0) return;
|
|
542
|
+
await Promise.all(taskBlocks.map(async ({ block, agentId }) => {
|
|
543
|
+
try {
|
|
544
|
+
const content = await readFile2(join2(subagentsDir, `agent-${agentId}.jsonl`), "utf-8");
|
|
545
|
+
block.subagentMessages = parseSessionFile(content);
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}));
|
|
549
|
+
}
|
|
521
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;
|
|
522
551
|
function isValidSessionUUID(name) {
|
|
523
552
|
return UUID_RE.test(name);
|
|
@@ -781,8 +810,8 @@ function parseSessionFile(content) {
|
|
|
781
810
|
if (toolUse) {
|
|
782
811
|
if (dataUri) toolUse.imageUri = dataUri;
|
|
783
812
|
if (resultText) toolUse.result = resultText;
|
|
813
|
+
break;
|
|
784
814
|
}
|
|
785
|
-
break;
|
|
786
815
|
}
|
|
787
816
|
}
|
|
788
817
|
}
|
|
@@ -915,7 +944,7 @@ function escapeRegex(str) {
|
|
|
915
944
|
|
|
916
945
|
// src/lib/directory-discovery.ts
|
|
917
946
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
918
|
-
async function findProjectDirectory(description, searchRoot, rejections) {
|
|
947
|
+
async function findProjectDirectory(description, searchRoot, rejections, signal) {
|
|
919
948
|
let prompt2 = `Find up to 3 project directories on this machine that best match: "${description}"
|
|
920
949
|
|
|
921
950
|
Search starting from ${searchRoot}. Rank by relevance. Include a brief reason for each match.`;
|
|
@@ -961,6 +990,8 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
961
990
|
}
|
|
962
991
|
}
|
|
963
992
|
});
|
|
993
|
+
const onAbort = () => q.close();
|
|
994
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
964
995
|
try {
|
|
965
996
|
for await (const msg of q) {
|
|
966
997
|
if (msg.type === "result") {
|
|
@@ -973,10 +1004,10 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
973
1004
|
}
|
|
974
1005
|
const result = resultMsg.result?.trim();
|
|
975
1006
|
if (result && result !== "null" && result.startsWith("/")) {
|
|
976
|
-
const
|
|
977
|
-
const name =
|
|
978
|
-
logger.info({ path:
|
|
979
|
-
return [{ path:
|
|
1007
|
+
const path4 = result.split("\n")[0].trim();
|
|
1008
|
+
const name = path4.split("/").pop() ?? path4;
|
|
1009
|
+
logger.info({ path: path4, name }, "Project directory found (text fallback)");
|
|
1010
|
+
return [{ path: path4, name, reason: "Best match" }];
|
|
980
1011
|
}
|
|
981
1012
|
logger.info("No matching directories found");
|
|
982
1013
|
return [];
|
|
@@ -986,10 +1017,81 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
|
|
|
986
1017
|
}
|
|
987
1018
|
}
|
|
988
1019
|
} finally {
|
|
1020
|
+
signal?.removeEventListener("abort", onAbort);
|
|
989
1021
|
q.close();
|
|
990
1022
|
}
|
|
991
1023
|
return [];
|
|
992
1024
|
}
|
|
1025
|
+
async function suggestProjectLocation(description, searchRoot, name, signal) {
|
|
1026
|
+
const prompt2 = `Suggest up to 3 suitable locations on this machine to create a new project.
|
|
1027
|
+
|
|
1028
|
+
User's description: "${description}"
|
|
1029
|
+
${name ? `Desired project name: "${name}"` : ""}
|
|
1030
|
+
|
|
1031
|
+
Search starting from ${searchRoot}. Look at common project directories (e.g. ~/github, ~/projects, ~/code, ~/Desktop).
|
|
1032
|
+
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.`;
|
|
1033
|
+
const q = query2({
|
|
1034
|
+
prompt: prompt2,
|
|
1035
|
+
options: {
|
|
1036
|
+
systemPrompt: {
|
|
1037
|
+
type: "preset",
|
|
1038
|
+
preset: "claude_code",
|
|
1039
|
+
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."
|
|
1040
|
+
},
|
|
1041
|
+
permissionMode: "bypassPermissions",
|
|
1042
|
+
persistSession: false,
|
|
1043
|
+
cwd: searchRoot,
|
|
1044
|
+
outputFormat: {
|
|
1045
|
+
type: "json_schema",
|
|
1046
|
+
schema: {
|
|
1047
|
+
type: "object",
|
|
1048
|
+
properties: {
|
|
1049
|
+
suggestions: {
|
|
1050
|
+
type: "array",
|
|
1051
|
+
items: {
|
|
1052
|
+
type: "object",
|
|
1053
|
+
properties: {
|
|
1054
|
+
path: { type: "string", description: "Absolute path for the new project directory (including project folder name)" },
|
|
1055
|
+
name: { type: "string", description: "Human-readable project name" },
|
|
1056
|
+
reason: { type: "string", description: "Brief reason why this location is suitable" }
|
|
1057
|
+
},
|
|
1058
|
+
required: ["path", "name", "reason"]
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
required: ["suggestions"]
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
const onAbort = () => q.close();
|
|
1068
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1069
|
+
try {
|
|
1070
|
+
for await (const msg of q) {
|
|
1071
|
+
if (msg.type === "result") {
|
|
1072
|
+
const resultMsg = msg;
|
|
1073
|
+
if (resultMsg.subtype === "success") {
|
|
1074
|
+
const structured = resultMsg.structured_output;
|
|
1075
|
+
if (structured?.suggestions?.length) {
|
|
1076
|
+
logger.info({ count: structured.suggestions.length }, "Location suggestions found (structured)");
|
|
1077
|
+
return structured.suggestions;
|
|
1078
|
+
}
|
|
1079
|
+
logger.info("No location suggestions found");
|
|
1080
|
+
return [];
|
|
1081
|
+
}
|
|
1082
|
+
logger.warn({ subtype: resultMsg.subtype }, "Location suggestion query failed");
|
|
1083
|
+
return [];
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
} finally {
|
|
1087
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1088
|
+
q.close();
|
|
1089
|
+
}
|
|
1090
|
+
return [];
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/commands/connect.ts
|
|
1094
|
+
import path3 from "path";
|
|
993
1095
|
|
|
994
1096
|
// src/lib/auth.ts
|
|
995
1097
|
import fs2 from "fs";
|
|
@@ -1215,7 +1317,22 @@ async function connect(server, options) {
|
|
|
1215
1317
|
});
|
|
1216
1318
|
socket.on("find-project", (msg) => {
|
|
1217
1319
|
if (msg.type === "find-project") {
|
|
1218
|
-
handleFindProject(socket, msg, defaultCwd);
|
|
1320
|
+
handleFindProject(socket, msg, defaultCwd, activeSessions);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
socket.on("suggest-project-location", (msg) => {
|
|
1324
|
+
if (msg.type === "suggest-project-location") {
|
|
1325
|
+
handleSuggestProjectLocation(socket, msg, activeSessions);
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
socket.on("create-project", (msg) => {
|
|
1329
|
+
if (msg.type === "create-project") {
|
|
1330
|
+
handleCreateProject(socket, msg);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
socket.on("check-path", (msg) => {
|
|
1334
|
+
if (msg.type === "check-path") {
|
|
1335
|
+
handleCheckPath(socket, msg);
|
|
1219
1336
|
}
|
|
1220
1337
|
});
|
|
1221
1338
|
socket.on("cancel", (msg) => {
|
|
@@ -1389,6 +1506,8 @@ function handleCancel(id, activeSessions) {
|
|
|
1389
1506
|
session.abort();
|
|
1390
1507
|
activeSessions.delete(id);
|
|
1391
1508
|
logger.info({ sessionId: id }, "Session cancelled");
|
|
1509
|
+
} else {
|
|
1510
|
+
logger.warn({ sessionId: id }, "Cancel: session not found in activeSessions");
|
|
1392
1511
|
}
|
|
1393
1512
|
}
|
|
1394
1513
|
async function handleListSessions(socket, msg, defaultCwd) {
|
|
@@ -1426,20 +1545,100 @@ async function handleGetCommands(socket, msg) {
|
|
|
1426
1545
|
log2.error({ err }, "Commands error");
|
|
1427
1546
|
}
|
|
1428
1547
|
}
|
|
1429
|
-
async function handleFindProject(socket, msg, _defaultCwd) {
|
|
1548
|
+
async function handleFindProject(socket, msg, _defaultCwd, activeSessions) {
|
|
1430
1549
|
const { id, description, rootDirectory, rejections } = msg;
|
|
1431
1550
|
const searchRoot = rootDirectory ?? os3.homedir();
|
|
1432
1551
|
const log2 = createChildLogger({ requestId: id });
|
|
1433
1552
|
log2.info({ description, searchRoot }, "Finding project directory...");
|
|
1553
|
+
const ac = new AbortController();
|
|
1554
|
+
const handle = {
|
|
1555
|
+
abort: () => ac.abort(),
|
|
1556
|
+
resolvePermission: () => {
|
|
1557
|
+
},
|
|
1558
|
+
setPermissionMode: async () => {
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
activeSessions.set(id, handle);
|
|
1434
1562
|
try {
|
|
1435
|
-
const suggestions = await findProjectDirectory(description, searchRoot, rejections);
|
|
1563
|
+
const suggestions = await findProjectDirectory(description, searchRoot, rejections, ac.signal);
|
|
1436
1564
|
send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
|
|
1437
1565
|
log2.info({ count: suggestions.length }, "Project suggestions sent");
|
|
1438
1566
|
} catch (err) {
|
|
1567
|
+
if (ac.signal.aborted) {
|
|
1568
|
+
log2.info("Find project cancelled");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1439
1571
|
const message = err instanceof Error ? err.message : String(err);
|
|
1440
1572
|
send(socket, "response", { type: "error", message, requestId: id });
|
|
1441
1573
|
log2.error({ err }, "Find project error");
|
|
1574
|
+
} finally {
|
|
1575
|
+
activeSessions.delete(id);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
async function handleSuggestProjectLocation(socket, msg, activeSessions) {
|
|
1579
|
+
const { id, description, name, rootDirectory } = msg;
|
|
1580
|
+
const searchRoot = rootDirectory ?? os3.homedir();
|
|
1581
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1582
|
+
log2.info({ description, name, searchRoot }, "Suggesting project location...");
|
|
1583
|
+
const ac = new AbortController();
|
|
1584
|
+
const handle = {
|
|
1585
|
+
abort: () => ac.abort(),
|
|
1586
|
+
resolvePermission: () => {
|
|
1587
|
+
},
|
|
1588
|
+
setPermissionMode: async () => {
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
activeSessions.set(id, handle);
|
|
1592
|
+
try {
|
|
1593
|
+
const suggestions = await suggestProjectLocation(description, searchRoot, name, ac.signal);
|
|
1594
|
+
send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
|
|
1595
|
+
log2.info({ count: suggestions.length }, "Location suggestions sent");
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
if (ac.signal.aborted) {
|
|
1598
|
+
log2.info("Suggest project location cancelled");
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1602
|
+
send(socket, "response", { type: "error", message, requestId: id });
|
|
1603
|
+
log2.error({ err }, "Suggest project location error");
|
|
1604
|
+
} finally {
|
|
1605
|
+
activeSessions.delete(id);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function handleCreateProject(socket, msg) {
|
|
1609
|
+
const { id, path: projectPath } = msg;
|
|
1610
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1611
|
+
log2.info({ path: projectPath }, "Creating project directory...");
|
|
1612
|
+
try {
|
|
1613
|
+
fs3.mkdirSync(projectPath, { recursive: true });
|
|
1614
|
+
send(socket, "response", { type: "project_created", path: projectPath, requestId: id });
|
|
1615
|
+
log2.info({ path: projectPath }, "Project directory created");
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1618
|
+
send(socket, "response", { type: "error", message, requestId: id });
|
|
1619
|
+
log2.error({ err }, "Create project error");
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function handleCheckPath(socket, msg) {
|
|
1623
|
+
const { id, path: checkTarget } = msg;
|
|
1624
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1625
|
+
log2.info({ path: checkTarget }, "Checking path...");
|
|
1626
|
+
let exists = false;
|
|
1627
|
+
let isDirectory = false;
|
|
1628
|
+
let parentExists = false;
|
|
1629
|
+
try {
|
|
1630
|
+
const stat2 = fs3.statSync(checkTarget);
|
|
1631
|
+
exists = true;
|
|
1632
|
+
isDirectory = stat2.isDirectory();
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
const parentStat = fs3.statSync(path3.dirname(checkTarget));
|
|
1637
|
+
parentExists = parentStat.isDirectory();
|
|
1638
|
+
} catch {
|
|
1442
1639
|
}
|
|
1640
|
+
send(socket, "response", { type: "path_check", exists, isDirectory, parentExists, requestId: id });
|
|
1641
|
+
log2.info({ exists, isDirectory, parentExists }, "Path check complete");
|
|
1443
1642
|
}
|
|
1444
1643
|
async function handleGetContext(socket, msg, defaultCwd) {
|
|
1445
1644
|
const { id, sessionId } = msg;
|