@punkcode/cli 0.1.11 → 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 +241 -16
  2. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -164,6 +164,7 @@ function runClaude(options, callbacks) {
164
164
  ...opts.model && { model: opts.model },
165
165
  ...opts.allowedTools && { allowedTools: opts.allowedTools },
166
166
  ...opts.disallowedTools && { disallowedTools: opts.disallowedTools },
167
+ ...opts.effort && { effort: opts.effort },
167
168
  ...opts.maxTurns && { maxTurns: opts.maxTurns },
168
169
  systemPrompt: opts.systemPrompt ?? { type: "preset", preset: "claude_code" },
169
170
  ...options.workingDirectory && { cwd: options.workingDirectory },
@@ -205,6 +206,8 @@ function runClaude(options, callbacks) {
205
206
  abort: () => {
206
207
  },
207
208
  resolvePermission: () => {
209
+ },
210
+ setPermissionMode: async () => {
208
211
  }
209
212
  };
210
213
  }
@@ -286,6 +289,10 @@ function runClaude(options, callbacks) {
286
289
  };
287
290
  logger.info({ sessionId: sessionInfo.sessionId, commands: sessionInfo.slashCommands.length }, "New chat session info");
288
291
  callbacks.onSessionCreated(sessionInfo);
292
+ } else if (sys.subtype === "task_started" && callbacks.onTaskStarted) {
293
+ callbacks.onTaskStarted(sys.task_id, sys.description ?? "", sys.tool_use_id);
294
+ } else if (sys.subtype === "task_notification" && callbacks.onTaskNotification) {
295
+ callbacks.onTaskNotification(sys.task_id, sys.status ?? "completed", sys.summary ?? "", sys.tool_use_id);
289
296
  }
290
297
  break;
291
298
  }
@@ -316,7 +323,8 @@ function runClaude(options, callbacks) {
316
323
  },
317
324
  resolvePermission: (toolUseId, allow, answers, feedback) => {
318
325
  pendingPermissions.get(toolUseId)?.({ allow, answers, feedback });
319
- }
326
+ },
327
+ setPermissionMode: (mode) => q.setPermissionMode(mode)
320
328
  };
321
329
  }
322
330
 
@@ -504,12 +512,41 @@ async function loadSession(sessionId) {
504
512
  const sessionPath = join2(CLAUDE_DIR, projectDir, sessionFile);
505
513
  try {
506
514
  const content = await readFile2(sessionPath, "utf-8");
507
- 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;
508
519
  } catch {
509
520
  }
510
521
  }
511
522
  return null;
512
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
+ }
513
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;
514
551
  function isValidSessionUUID(name) {
515
552
  return UUID_RE.test(name);
@@ -773,8 +810,8 @@ function parseSessionFile(content) {
773
810
  if (toolUse) {
774
811
  if (dataUri) toolUse.imageUri = dataUri;
775
812
  if (resultText) toolUse.result = resultText;
813
+ break;
776
814
  }
777
- break;
778
815
  }
779
816
  }
780
817
  }
@@ -907,7 +944,7 @@ function escapeRegex(str) {
907
944
 
908
945
  // src/lib/directory-discovery.ts
909
946
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
910
- async function findProjectDirectory(description, searchRoot, rejections) {
947
+ async function findProjectDirectory(description, searchRoot, rejections, signal) {
911
948
  let prompt2 = `Find up to 3 project directories on this machine that best match: "${description}"
912
949
 
913
950
  Search starting from ${searchRoot}. Rank by relevance. Include a brief reason for each match.`;
@@ -953,6 +990,8 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
953
990
  }
954
991
  }
955
992
  });
993
+ const onAbort = () => q.close();
994
+ signal?.addEventListener("abort", onAbort, { once: true });
956
995
  try {
957
996
  for await (const msg of q) {
958
997
  if (msg.type === "result") {
@@ -965,10 +1004,10 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
965
1004
  }
966
1005
  const result = resultMsg.result?.trim();
967
1006
  if (result && result !== "null" && result.startsWith("/")) {
968
- const path3 = result.split("\n")[0].trim();
969
- const name = path3.split("/").pop() ?? path3;
970
- logger.info({ path: path3, name }, "Project directory found (text fallback)");
971
- 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" }];
972
1011
  }
973
1012
  logger.info("No matching directories found");
974
1013
  return [];
@@ -978,11 +1017,82 @@ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason fo
978
1017
  }
979
1018
  }
980
1019
  } finally {
1020
+ signal?.removeEventListener("abort", onAbort);
1021
+ q.close();
1022
+ }
1023
+ return [];
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);
981
1088
  q.close();
982
1089
  }
983
1090
  return [];
984
1091
  }
985
1092
 
1093
+ // src/commands/connect.ts
1094
+ import path3 from "path";
1095
+
986
1096
  // src/lib/auth.ts
987
1097
  import fs2 from "fs";
988
1098
  import path2 from "path";
@@ -1167,6 +1277,16 @@ async function connect(server, options) {
1167
1277
  socket.emit("register", deviceInfo, (response) => {
1168
1278
  if (response.success) {
1169
1279
  logger.info({ deviceId }, "Registered");
1280
+ } else {
1281
+ logger.warn("Registration failed, retrying in 2s...");
1282
+ setTimeout(() => {
1283
+ if (socket.connected) {
1284
+ socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd), (r) => {
1285
+ if (r.success) logger.info({ deviceId }, "Registered (retry)");
1286
+ else logger.error("Registration failed after retry");
1287
+ });
1288
+ }
1289
+ }, 2e3);
1170
1290
  }
1171
1291
  });
1172
1292
  });
@@ -1197,7 +1317,22 @@ async function connect(server, options) {
1197
1317
  });
1198
1318
  socket.on("find-project", (msg) => {
1199
1319
  if (msg.type === "find-project") {
1200
- 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);
1201
1336
  }
1202
1337
  });
1203
1338
  socket.on("cancel", (msg) => {
@@ -1205,9 +1340,13 @@ async function connect(server, options) {
1205
1340
  });
1206
1341
  socket.on("permission-response", (msg) => {
1207
1342
  const log2 = createChildLogger({ sessionId: msg.requestId });
1208
- log2.info({ toolUseId: msg.toolUseId, allowed: msg.allow }, msg.allow ? "Permission accepted" : "Permission denied");
1343
+ log2.info({ toolUseId: msg.toolUseId, allowed: msg.allow, permissionMode: msg.permissionMode }, msg.allow ? "Permission accepted" : "Permission denied");
1209
1344
  const session = activeSessions.get(msg.requestId);
1210
1345
  if (session) {
1346
+ if (msg.permissionMode) {
1347
+ session.setPermissionMode(msg.permissionMode).catch(() => {
1348
+ });
1349
+ }
1211
1350
  session.resolvePermission(msg.toolUseId, msg.allow, msg.answers, msg.feedback);
1212
1351
  }
1213
1352
  });
@@ -1217,10 +1356,6 @@ async function connect(server, options) {
1217
1356
  socket.io.on("reconnect_attempt", () => {
1218
1357
  logger.info("Reconnecting...");
1219
1358
  });
1220
- socket.on("reconnect", (attemptNumber) => {
1221
- logger.info({ attemptNumber }, "Reconnected");
1222
- socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd));
1223
- });
1224
1359
  socket.on("connect_error", (err) => {
1225
1360
  const { reason, ...detail } = formatConnectionError(err);
1226
1361
  logger.error(detail, `Connection error: ${reason}`);
@@ -1352,6 +1487,14 @@ function handlePrompt(socket, msg, activeSessions) {
1352
1487
  reason: req.reason,
1353
1488
  blockedPath: req.blockedPath
1354
1489
  });
1490
+ },
1491
+ onTaskStarted: (taskId, description, toolUseId) => {
1492
+ log2.info({ taskId, toolUseId, description }, "Task started");
1493
+ send(socket, "response", { type: "task_started", taskId, description, toolUseId, requestId: id });
1494
+ },
1495
+ onTaskNotification: (taskId, status, summary, toolUseId) => {
1496
+ log2.info({ taskId, toolUseId, status, summary }, "Task notification");
1497
+ send(socket, "response", { type: "task_notification", taskId, status, summary, toolUseId, requestId: id });
1355
1498
  }
1356
1499
  }
1357
1500
  );
@@ -1363,6 +1506,8 @@ function handleCancel(id, activeSessions) {
1363
1506
  session.abort();
1364
1507
  activeSessions.delete(id);
1365
1508
  logger.info({ sessionId: id }, "Session cancelled");
1509
+ } else {
1510
+ logger.warn({ sessionId: id }, "Cancel: session not found in activeSessions");
1366
1511
  }
1367
1512
  }
1368
1513
  async function handleListSessions(socket, msg, defaultCwd) {
@@ -1400,20 +1545,100 @@ async function handleGetCommands(socket, msg) {
1400
1545
  log2.error({ err }, "Commands error");
1401
1546
  }
1402
1547
  }
1403
- async function handleFindProject(socket, msg, _defaultCwd) {
1548
+ async function handleFindProject(socket, msg, _defaultCwd, activeSessions) {
1404
1549
  const { id, description, rootDirectory, rejections } = msg;
1405
1550
  const searchRoot = rootDirectory ?? os3.homedir();
1406
1551
  const log2 = createChildLogger({ requestId: id });
1407
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);
1408
1562
  try {
1409
- const suggestions = await findProjectDirectory(description, searchRoot, rejections);
1563
+ const suggestions = await findProjectDirectory(description, searchRoot, rejections, ac.signal);
1410
1564
  send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
1411
1565
  log2.info({ count: suggestions.length }, "Project suggestions sent");
1412
1566
  } catch (err) {
1567
+ if (ac.signal.aborted) {
1568
+ log2.info("Find project cancelled");
1569
+ return;
1570
+ }
1413
1571
  const message = err instanceof Error ? err.message : String(err);
1414
1572
  send(socket, "response", { type: "error", message, requestId: id });
1415
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 {
1416
1639
  }
1640
+ send(socket, "response", { type: "path_check", exists, isDirectory, parentExists, requestId: id });
1641
+ log2.info({ exists, isDirectory, parentExists }, "Path check complete");
1417
1642
  }
1418
1643
  async function handleGetContext(socket, msg, defaultCwd) {
1419
1644
  const { id, sessionId } = msg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,8 +53,8 @@
53
53
  "vitest": "^4.0.18"
54
54
  },
55
55
  "dependencies": {
56
- "@anthropic-ai/claude-agent-sdk": "^0.2.37",
57
- "@anthropic-ai/sdk": "^0.72.1",
56
+ "@anthropic-ai/claude-agent-sdk": "^0.2.49",
57
+ "@anthropic-ai/sdk": "^0.78.0",
58
58
  "commander": "^14.0.3",
59
59
  "execa": "^9.6.1",
60
60
  "pino": "^10.3.1",