@punkcode/cli 0.1.10 → 0.1.12

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 +134 -11
  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,12 +323,14 @@ 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
 
323
331
  // src/commands/connect.ts
324
332
  import fs3 from "fs";
333
+ import os3 from "os";
325
334
 
326
335
  // src/lib/device-info.ts
327
336
  import os from "os";
@@ -904,6 +913,84 @@ function escapeRegex(str) {
904
913
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
905
914
  }
906
915
 
916
+ // src/lib/directory-discovery.ts
917
+ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
918
+ async function findProjectDirectory(description, searchRoot, rejections) {
919
+ let prompt2 = `Find up to 3 project directories on this machine that best match: "${description}"
920
+
921
+ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason for each match.`;
922
+ if (rejections?.length) {
923
+ prompt2 += "\n\nThe user rejected these previous suggestions:";
924
+ for (const r of rejections) {
925
+ prompt2 += `
926
+ - ${r.path}${r.feedback ? ` (user said: "${r.feedback}")` : ""}`;
927
+ }
928
+ prompt2 += "\n\nFind different matches based on their feedback.";
929
+ }
930
+ const q = query2({
931
+ prompt: prompt2,
932
+ options: {
933
+ systemPrompt: {
934
+ type: "preset",
935
+ preset: "claude_code",
936
+ 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."
937
+ },
938
+ permissionMode: "bypassPermissions",
939
+ persistSession: false,
940
+ cwd: searchRoot,
941
+ outputFormat: {
942
+ type: "json_schema",
943
+ schema: {
944
+ type: "object",
945
+ properties: {
946
+ suggestions: {
947
+ type: "array",
948
+ items: {
949
+ type: "object",
950
+ properties: {
951
+ path: { type: "string", description: "Absolute path to the project directory" },
952
+ name: { type: "string", description: "Human-readable project name" },
953
+ reason: { type: "string", description: 'Brief reason why this matches (e.g. "Direct match under github folder")' }
954
+ },
955
+ required: ["path", "name", "reason"]
956
+ }
957
+ }
958
+ },
959
+ required: ["suggestions"]
960
+ }
961
+ }
962
+ }
963
+ });
964
+ try {
965
+ for await (const msg of q) {
966
+ if (msg.type === "result") {
967
+ const resultMsg = msg;
968
+ if (resultMsg.subtype === "success") {
969
+ const structured = resultMsg.structured_output;
970
+ if (structured?.suggestions?.length) {
971
+ logger.info({ count: structured.suggestions.length }, "Project directories found (structured)");
972
+ return structured.suggestions;
973
+ }
974
+ const result = resultMsg.result?.trim();
975
+ 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" }];
980
+ }
981
+ logger.info("No matching directories found");
982
+ return [];
983
+ }
984
+ logger.warn({ subtype: resultMsg.subtype }, "Directory search query failed");
985
+ return [];
986
+ }
987
+ }
988
+ } finally {
989
+ q.close();
990
+ }
991
+ return [];
992
+ }
993
+
907
994
  // src/lib/auth.ts
908
995
  import fs2 from "fs";
909
996
  import path2 from "path";
@@ -1066,7 +1153,9 @@ async function connect(server, options) {
1066
1153
  const socket = io(url, {
1067
1154
  path: "/socket.io",
1068
1155
  transports: ["websocket"],
1069
- auth: { token: idToken },
1156
+ auth: (cb) => {
1157
+ refreshIdToken().then((token) => cb({ token })).catch(() => cb({ token: idToken }));
1158
+ },
1070
1159
  reconnection: true,
1071
1160
  reconnectionAttempts: Infinity,
1072
1161
  reconnectionDelay: 1e3,
@@ -1086,6 +1175,16 @@ async function connect(server, options) {
1086
1175
  socket.emit("register", deviceInfo, (response) => {
1087
1176
  if (response.success) {
1088
1177
  logger.info({ deviceId }, "Registered");
1178
+ } else {
1179
+ logger.warn("Registration failed, retrying in 2s...");
1180
+ setTimeout(() => {
1181
+ if (socket.connected) {
1182
+ socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd), (r) => {
1183
+ if (r.success) logger.info({ deviceId }, "Registered (retry)");
1184
+ else logger.error("Registration failed after retry");
1185
+ });
1186
+ }
1187
+ }, 2e3);
1089
1188
  }
1090
1189
  });
1091
1190
  });
@@ -1114,14 +1213,23 @@ async function connect(server, options) {
1114
1213
  handleGetCommands(socket, msg);
1115
1214
  }
1116
1215
  });
1216
+ socket.on("find-project", (msg) => {
1217
+ if (msg.type === "find-project") {
1218
+ handleFindProject(socket, msg, defaultCwd);
1219
+ }
1220
+ });
1117
1221
  socket.on("cancel", (msg) => {
1118
1222
  handleCancel(msg.id, activeSessions);
1119
1223
  });
1120
1224
  socket.on("permission-response", (msg) => {
1121
1225
  const log2 = createChildLogger({ sessionId: msg.requestId });
1122
- log2.info({ toolUseId: msg.toolUseId, allowed: msg.allow }, msg.allow ? "Permission accepted" : "Permission denied");
1226
+ log2.info({ toolUseId: msg.toolUseId, allowed: msg.allow, permissionMode: msg.permissionMode }, msg.allow ? "Permission accepted" : "Permission denied");
1123
1227
  const session = activeSessions.get(msg.requestId);
1124
1228
  if (session) {
1229
+ if (msg.permissionMode) {
1230
+ session.setPermissionMode(msg.permissionMode).catch(() => {
1231
+ });
1232
+ }
1125
1233
  session.resolvePermission(msg.toolUseId, msg.allow, msg.answers, msg.feedback);
1126
1234
  }
1127
1235
  });
@@ -1130,14 +1238,6 @@ async function connect(server, options) {
1130
1238
  });
1131
1239
  socket.io.on("reconnect_attempt", () => {
1132
1240
  logger.info("Reconnecting...");
1133
- refreshIdToken().then((token) => {
1134
- socket.auth = { token };
1135
- }).catch(() => {
1136
- });
1137
- });
1138
- socket.on("reconnect", (attemptNumber) => {
1139
- logger.info({ attemptNumber }, "Reconnected");
1140
- socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd));
1141
1241
  });
1142
1242
  socket.on("connect_error", (err) => {
1143
1243
  const { reason, ...detail } = formatConnectionError(err);
@@ -1270,6 +1370,14 @@ function handlePrompt(socket, msg, activeSessions) {
1270
1370
  reason: req.reason,
1271
1371
  blockedPath: req.blockedPath
1272
1372
  });
1373
+ },
1374
+ onTaskStarted: (taskId, description, toolUseId) => {
1375
+ log2.info({ taskId, toolUseId, description }, "Task started");
1376
+ send(socket, "response", { type: "task_started", taskId, description, toolUseId, requestId: id });
1377
+ },
1378
+ onTaskNotification: (taskId, status, summary, toolUseId) => {
1379
+ log2.info({ taskId, toolUseId, status, summary }, "Task notification");
1380
+ send(socket, "response", { type: "task_notification", taskId, status, summary, toolUseId, requestId: id });
1273
1381
  }
1274
1382
  }
1275
1383
  );
@@ -1318,6 +1426,21 @@ async function handleGetCommands(socket, msg) {
1318
1426
  log2.error({ err }, "Commands error");
1319
1427
  }
1320
1428
  }
1429
+ async function handleFindProject(socket, msg, _defaultCwd) {
1430
+ const { id, description, rootDirectory, rejections } = msg;
1431
+ const searchRoot = rootDirectory ?? os3.homedir();
1432
+ const log2 = createChildLogger({ requestId: id });
1433
+ log2.info({ description, searchRoot }, "Finding project directory...");
1434
+ try {
1435
+ const suggestions = await findProjectDirectory(description, searchRoot, rejections);
1436
+ send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
1437
+ log2.info({ count: suggestions.length }, "Project suggestions sent");
1438
+ } catch (err) {
1439
+ const message = err instanceof Error ? err.message : String(err);
1440
+ send(socket, "response", { type: "error", message, requestId: id });
1441
+ log2.error({ err }, "Find project error");
1442
+ }
1443
+ }
1321
1444
  async function handleGetContext(socket, msg, defaultCwd) {
1322
1445
  const { id, sessionId } = msg;
1323
1446
  const workingDirectory = msg.workingDirectory ?? defaultCwd;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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",