@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.
- package/dist/cli.js +127 -16
- 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
|
|
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:
|
|
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:
|
|
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 ??
|
|
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
|
|
1309
|
-
const { id,
|
|
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
|
}
|