@punkcode/cli 0.1.9 → 0.1.11

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 +127 -16
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -320,6 +320,10 @@ function runClaude(options, callbacks) {
320
320
  };
321
321
  }
322
322
 
323
+ // src/commands/connect.ts
324
+ import fs3 from "fs";
325
+ import os3 from "os";
326
+
323
327
  // src/lib/device-info.ts
324
328
  import os from "os";
325
329
  import path from "path";
@@ -345,7 +349,15 @@ function getOrCreateDeviceId() {
345
349
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
346
350
  return id;
347
351
  }
348
- function collectDeviceInfo(deviceId, customName, customTags) {
352
+ function getDefaultWorkingDirectory() {
353
+ try {
354
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
355
+ if (config.defaultWorkingDirectory) return config.defaultWorkingDirectory;
356
+ } catch {
357
+ }
358
+ return path.join(os.homedir(), "punk");
359
+ }
360
+ function collectDeviceInfo(deviceId, customName, customTags, defaultCwd) {
349
361
  if (customName) {
350
362
  saveConfigField("deviceName", customName);
351
363
  }
@@ -361,7 +373,7 @@ function collectDeviceInfo(deviceId, customName, customTags) {
361
373
  arch: process.arch,
362
374
  username: os.userInfo().username,
363
375
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
364
- defaultWorkingDirectory: process.cwd(),
376
+ defaultWorkingDirectory: defaultCwd || getDefaultWorkingDirectory(),
365
377
  model: getModel(),
366
378
  cpuModel: cpus.length > 0 ? cpus[0].model : "Unknown",
367
379
  memoryGB: Math.round(os.totalmem() / 1024 ** 3),
@@ -893,6 +905,84 @@ function escapeRegex(str) {
893
905
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
894
906
  }
895
907
 
908
+ // src/lib/directory-discovery.ts
909
+ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
910
+ async function findProjectDirectory(description, searchRoot, rejections) {
911
+ let prompt2 = `Find up to 3 project directories on this machine that best match: "${description}"
912
+
913
+ Search starting from ${searchRoot}. Rank by relevance. Include a brief reason for each match.`;
914
+ if (rejections?.length) {
915
+ prompt2 += "\n\nThe user rejected these previous suggestions:";
916
+ for (const r of rejections) {
917
+ prompt2 += `
918
+ - ${r.path}${r.feedback ? ` (user said: "${r.feedback}")` : ""}`;
919
+ }
920
+ prompt2 += "\n\nFind different matches based on their feedback.";
921
+ }
922
+ const q = query2({
923
+ prompt: prompt2,
924
+ options: {
925
+ systemPrompt: {
926
+ type: "preset",
927
+ preset: "claude_code",
928
+ 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."
929
+ },
930
+ permissionMode: "bypassPermissions",
931
+ persistSession: false,
932
+ cwd: searchRoot,
933
+ outputFormat: {
934
+ type: "json_schema",
935
+ schema: {
936
+ type: "object",
937
+ properties: {
938
+ suggestions: {
939
+ type: "array",
940
+ items: {
941
+ type: "object",
942
+ properties: {
943
+ path: { type: "string", description: "Absolute path to the project directory" },
944
+ name: { type: "string", description: "Human-readable project name" },
945
+ reason: { type: "string", description: 'Brief reason why this matches (e.g. "Direct match under github folder")' }
946
+ },
947
+ required: ["path", "name", "reason"]
948
+ }
949
+ }
950
+ },
951
+ required: ["suggestions"]
952
+ }
953
+ }
954
+ }
955
+ });
956
+ try {
957
+ for await (const msg of q) {
958
+ if (msg.type === "result") {
959
+ const resultMsg = msg;
960
+ if (resultMsg.subtype === "success") {
961
+ const structured = resultMsg.structured_output;
962
+ if (structured?.suggestions?.length) {
963
+ logger.info({ count: structured.suggestions.length }, "Project directories found (structured)");
964
+ return structured.suggestions;
965
+ }
966
+ const result = resultMsg.result?.trim();
967
+ 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" }];
972
+ }
973
+ logger.info("No matching directories found");
974
+ return [];
975
+ }
976
+ logger.warn({ subtype: resultMsg.subtype }, "Directory search query failed");
977
+ return [];
978
+ }
979
+ }
980
+ } finally {
981
+ q.close();
982
+ }
983
+ return [];
984
+ }
985
+
896
986
  // src/lib/auth.ts
897
987
  import fs2 from "fs";
898
988
  import path2 from "path";
@@ -1037,6 +1127,8 @@ async function connect(server, options) {
1037
1127
  }
1038
1128
  logger.info("All checks passed");
1039
1129
  const deviceId = options.deviceId || getOrCreateDeviceId();
1130
+ const defaultCwd = options.cwd || getDefaultWorkingDirectory();
1131
+ fs3.mkdirSync(defaultCwd, { recursive: true });
1040
1132
  const url = buildUrl(server);
1041
1133
  let idToken;
1042
1134
  if (options.token) {
@@ -1053,7 +1145,9 @@ async function connect(server, options) {
1053
1145
  const socket = io(url, {
1054
1146
  path: "/socket.io",
1055
1147
  transports: ["websocket"],
1056
- auth: { token: idToken },
1148
+ auth: (cb) => {
1149
+ refreshIdToken().then((token) => cb({ token })).catch(() => cb({ token: idToken }));
1150
+ },
1057
1151
  reconnection: true,
1058
1152
  reconnectionAttempts: Infinity,
1059
1153
  reconnectionDelay: 1e3,
@@ -1069,7 +1163,7 @@ async function connect(server, options) {
1069
1163
  }, 3e4);
1070
1164
  socket.on("connect", () => {
1071
1165
  logger.info("Connected");
1072
- const deviceInfo = collectDeviceInfo(deviceId, options.name, options.tag);
1166
+ const deviceInfo = collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd);
1073
1167
  socket.emit("register", deviceInfo, (response) => {
1074
1168
  if (response.success) {
1075
1169
  logger.info({ deviceId }, "Registered");
@@ -1088,12 +1182,12 @@ async function connect(server, options) {
1088
1182
  });
1089
1183
  socket.on("list-sessions", async (msg) => {
1090
1184
  if (msg.type === "list-sessions") {
1091
- handleListSessions(socket, msg);
1185
+ handleListSessions(socket, msg, defaultCwd);
1092
1186
  }
1093
1187
  });
1094
1188
  socket.on("get-context", (msg) => {
1095
1189
  if (msg.type === "get-context") {
1096
- handleGetContext(socket, msg);
1190
+ handleGetContext(socket, msg, defaultCwd);
1097
1191
  }
1098
1192
  });
1099
1193
  socket.on("get-commands", (msg) => {
@@ -1101,6 +1195,11 @@ async function connect(server, options) {
1101
1195
  handleGetCommands(socket, msg);
1102
1196
  }
1103
1197
  });
1198
+ socket.on("find-project", (msg) => {
1199
+ if (msg.type === "find-project") {
1200
+ handleFindProject(socket, msg, defaultCwd);
1201
+ }
1202
+ });
1104
1203
  socket.on("cancel", (msg) => {
1105
1204
  handleCancel(msg.id, activeSessions);
1106
1205
  });
@@ -1117,14 +1216,10 @@ async function connect(server, options) {
1117
1216
  });
1118
1217
  socket.io.on("reconnect_attempt", () => {
1119
1218
  logger.info("Reconnecting...");
1120
- refreshIdToken().then((token) => {
1121
- socket.auth = { token };
1122
- }).catch(() => {
1123
- });
1124
1219
  });
1125
1220
  socket.on("reconnect", (attemptNumber) => {
1126
1221
  logger.info({ attemptNumber }, "Reconnected");
1127
- socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag));
1222
+ socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag, defaultCwd));
1128
1223
  });
1129
1224
  socket.on("connect_error", (err) => {
1130
1225
  const { reason, ...detail } = formatConnectionError(err);
@@ -1270,9 +1365,9 @@ function handleCancel(id, activeSessions) {
1270
1365
  logger.info({ sessionId: id }, "Session cancelled");
1271
1366
  }
1272
1367
  }
1273
- async function handleListSessions(socket, msg) {
1368
+ async function handleListSessions(socket, msg, defaultCwd) {
1274
1369
  const { id } = msg;
1275
- const workingDirectory = msg.workingDirectory ?? process.cwd();
1370
+ const workingDirectory = msg.workingDirectory ?? defaultCwd;
1276
1371
  logger.info("Listing sessions...");
1277
1372
  const sessions = await listSessions(workingDirectory);
1278
1373
  send(socket, "response", { type: "sessions_list", sessions, requestId: id });
@@ -1305,8 +1400,24 @@ async function handleGetCommands(socket, msg) {
1305
1400
  log2.error({ err }, "Commands error");
1306
1401
  }
1307
1402
  }
1308
- async function handleGetContext(socket, msg) {
1309
- const { id, sessionId, workingDirectory } = msg;
1403
+ async function handleFindProject(socket, msg, _defaultCwd) {
1404
+ const { id, description, rootDirectory, rejections } = msg;
1405
+ const searchRoot = rootDirectory ?? os3.homedir();
1406
+ const log2 = createChildLogger({ requestId: id });
1407
+ log2.info({ description, searchRoot }, "Finding project directory...");
1408
+ try {
1409
+ const suggestions = await findProjectDirectory(description, searchRoot, rejections);
1410
+ send(socket, "response", { type: "project_suggestions", suggestions, requestId: id });
1411
+ log2.info({ count: suggestions.length }, "Project suggestions sent");
1412
+ } catch (err) {
1413
+ const message = err instanceof Error ? err.message : String(err);
1414
+ send(socket, "response", { type: "error", message, requestId: id });
1415
+ log2.error({ err }, "Find project error");
1416
+ }
1417
+ }
1418
+ async function handleGetContext(socket, msg, defaultCwd) {
1419
+ const { id, sessionId } = msg;
1420
+ const workingDirectory = msg.workingDirectory ?? defaultCwd;
1310
1421
  const log2 = createChildLogger({ sessionId });
1311
1422
  log2.info("Getting context...");
1312
1423
  try {
@@ -1392,7 +1503,7 @@ function logout() {
1392
1503
 
1393
1504
  // src/commands/index.ts
1394
1505
  function registerCommands(program2) {
1395
- program2.command("connect").argument("[server]", "Backend server URL", "https://api.punkcode.dev").description("Connect to backend server").option("-t, --token <token>", "Authentication token").option("-d, --device-id <deviceId>", "Device identifier (defaults to hostname)").option("-n, --name <name>", "Custom device display name").option("--tag <tag>", "Device tag (repeatable, e.g. --tag home --tag mac --tag docker)", (val, acc) => [...acc, val], []).action(connect);
1506
+ program2.command("connect").argument("[server]", "Backend server URL", "https://api.punkcode.dev").description("Connect to backend server").option("-t, --token <token>", "Authentication token").option("-d, --device-id <deviceId>", "Device identifier (defaults to hostname)").option("-n, --name <name>", "Custom device display name").option("--tag <tag>", "Device tag (repeatable, e.g. --tag home --tag mac --tag docker)", (val, acc) => [...acc, val], []).option("--cwd <directory>", "Working directory for sessions (default: ~/punk)").action(connect);
1396
1507
  program2.command("login").description("Log in with your email and password").action(login);
1397
1508
  program2.command("logout").description("Log out and clear stored credentials").action(logout);
1398
1509
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {