@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.
Files changed (2) hide show
  1. package/dist/cli.js +209 -10
  2. 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
- return parseSessionFile(content);
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 path3 = result.split("\n")[0].trim();
977
- const name = path3.split("/").pop() ?? path3;
978
- logger.info({ path: path3, name }, "Project directory found (text fallback)");
979
- return [{ path: path3, name, reason: "Best match" }];
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {