@punkcode/cli 0.1.4 → 0.1.6
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 +165 -65
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -92,6 +92,7 @@ function runClaude(options, callbacks) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
(async () => {
|
|
95
|
+
let sentCompactSummary = false;
|
|
95
96
|
try {
|
|
96
97
|
for await (const message of q) {
|
|
97
98
|
switch (message.type) {
|
|
@@ -118,7 +119,19 @@ function runClaude(options, callbacks) {
|
|
|
118
119
|
}
|
|
119
120
|
case "user": {
|
|
120
121
|
const userContent = message.message?.content;
|
|
121
|
-
if (
|
|
122
|
+
if (typeof userContent === "string") {
|
|
123
|
+
const match = userContent.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
124
|
+
if (match) {
|
|
125
|
+
if (sentCompactSummary && match[1].trim() === "Compacted") {
|
|
126
|
+
sentCompactSummary = false;
|
|
127
|
+
} else {
|
|
128
|
+
callbacks.onSlashCommandOutput?.(match[1]);
|
|
129
|
+
}
|
|
130
|
+
} else if (userContent.startsWith("This session is being continued")) {
|
|
131
|
+
sentCompactSummary = true;
|
|
132
|
+
callbacks.onSlashCommandOutput?.(userContent);
|
|
133
|
+
}
|
|
134
|
+
} else if (Array.isArray(userContent)) {
|
|
122
135
|
for (const block of userContent) {
|
|
123
136
|
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
|
|
124
137
|
const tr = block;
|
|
@@ -132,10 +145,35 @@ function runClaude(options, callbacks) {
|
|
|
132
145
|
}
|
|
133
146
|
break;
|
|
134
147
|
}
|
|
135
|
-
case "
|
|
136
|
-
|
|
148
|
+
case "system": {
|
|
149
|
+
const sys = message;
|
|
150
|
+
if (sys.subtype === "init" && callbacks.onSessionCreated) {
|
|
151
|
+
callbacks.onSessionCreated({
|
|
152
|
+
sessionId: sys.session_id ?? "",
|
|
153
|
+
tools: sys.tools ?? [],
|
|
154
|
+
slashCommands: (sys.slash_commands ?? []).map((cmd) => ({ name: cmd, description: "" })),
|
|
155
|
+
skills: sys.skills ?? [],
|
|
156
|
+
mcpServers: sys.mcp_servers ?? [],
|
|
157
|
+
model: sys.model ?? "",
|
|
158
|
+
cwd: sys.cwd ?? "",
|
|
159
|
+
claudeCodeVersion: sys.claude_code_version ?? "",
|
|
160
|
+
permissionMode: sys.permissionMode ?? "default"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
137
163
|
break;
|
|
138
164
|
}
|
|
165
|
+
case "tool_use_summary": {
|
|
166
|
+
const summary = message.summary;
|
|
167
|
+
if (summary) {
|
|
168
|
+
callbacks.onSlashCommandOutput?.(summary);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "result": {
|
|
173
|
+
const resultText = message.subtype === "success" ? message.result : void 0;
|
|
174
|
+
callbacks.onResult(message.session_id, resultText);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
139
177
|
}
|
|
140
178
|
}
|
|
141
179
|
} catch (err) {
|
|
@@ -180,11 +218,14 @@ function getOrCreateDeviceId() {
|
|
|
180
218
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
181
219
|
return id;
|
|
182
220
|
}
|
|
183
|
-
function collectDeviceInfo(deviceId) {
|
|
221
|
+
function collectDeviceInfo(deviceId, customName) {
|
|
222
|
+
if (customName) {
|
|
223
|
+
saveConfigField("deviceName", customName);
|
|
224
|
+
}
|
|
184
225
|
const cpus = os.cpus();
|
|
185
226
|
return {
|
|
186
227
|
deviceId,
|
|
187
|
-
name: getDeviceName(),
|
|
228
|
+
name: customName || getDeviceName(),
|
|
188
229
|
platform: process.platform,
|
|
189
230
|
arch: process.arch,
|
|
190
231
|
username: os.userInfo().username,
|
|
@@ -198,6 +239,11 @@ function collectDeviceInfo(deviceId) {
|
|
|
198
239
|
};
|
|
199
240
|
}
|
|
200
241
|
function getDeviceName() {
|
|
242
|
+
try {
|
|
243
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
244
|
+
if (config.deviceName) return config.deviceName;
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
201
247
|
if (process.platform === "darwin") {
|
|
202
248
|
try {
|
|
203
249
|
const { stdout } = execaSync("scutil", ["--get", "ComputerName"], { timeout: 3e3 });
|
|
@@ -208,6 +254,16 @@ function getDeviceName() {
|
|
|
208
254
|
}
|
|
209
255
|
return os.hostname();
|
|
210
256
|
}
|
|
257
|
+
function saveConfigField(key, value) {
|
|
258
|
+
fs.mkdirSync(PUNK_DIR, { recursive: true });
|
|
259
|
+
let config = {};
|
|
260
|
+
try {
|
|
261
|
+
config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
config[key] = value;
|
|
265
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
266
|
+
}
|
|
211
267
|
function parseBattery() {
|
|
212
268
|
try {
|
|
213
269
|
if (process.platform === "darwin") {
|
|
@@ -282,6 +338,9 @@ import { readdir, readFile, stat, open } from "fs/promises";
|
|
|
282
338
|
import { join } from "path";
|
|
283
339
|
import { homedir } from "os";
|
|
284
340
|
var CLAUDE_DIR = join(homedir(), ".claude", "projects");
|
|
341
|
+
function pathToProjectDir(dir) {
|
|
342
|
+
return dir.replace(/\//g, "-");
|
|
343
|
+
}
|
|
285
344
|
async function loadSession(sessionId) {
|
|
286
345
|
const sessionFile = `${sessionId}.jsonl`;
|
|
287
346
|
let projectDirs;
|
|
@@ -304,10 +363,14 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
304
363
|
function isValidSessionUUID(name) {
|
|
305
364
|
return UUID_RE.test(name);
|
|
306
365
|
}
|
|
307
|
-
async function listSessions() {
|
|
366
|
+
async function listSessions(workingDirectory) {
|
|
308
367
|
let projectDirs;
|
|
309
368
|
try {
|
|
310
|
-
|
|
369
|
+
if (workingDirectory) {
|
|
370
|
+
projectDirs = [pathToProjectDir(workingDirectory)];
|
|
371
|
+
} else {
|
|
372
|
+
projectDirs = await readdir(CLAUDE_DIR);
|
|
373
|
+
}
|
|
311
374
|
} catch {
|
|
312
375
|
return [];
|
|
313
376
|
}
|
|
@@ -335,16 +398,12 @@ async function listSessions() {
|
|
|
335
398
|
candidates.map(async (c) => {
|
|
336
399
|
try {
|
|
337
400
|
const fileStat = await stat(c.filePath);
|
|
338
|
-
const
|
|
339
|
-
extractTitle(c.filePath),
|
|
340
|
-
countTurns(c.filePath)
|
|
341
|
-
]);
|
|
401
|
+
const titleInfo = await extractTitle(c.filePath);
|
|
342
402
|
return {
|
|
343
403
|
sessionId: c.sessionId,
|
|
344
404
|
project: c.project,
|
|
345
405
|
title: titleInfo.title,
|
|
346
406
|
lastModified: fileStat.mtimeMs,
|
|
347
|
-
turnCount,
|
|
348
407
|
...titleInfo.summary && { summary: titleInfo.summary }
|
|
349
408
|
};
|
|
350
409
|
} catch {
|
|
@@ -411,7 +470,9 @@ async function extractFirstUserMessage(fh) {
|
|
|
411
470
|
try {
|
|
412
471
|
const entry = JSON.parse(line);
|
|
413
472
|
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
414
|
-
if (entry.uuid
|
|
473
|
+
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
474
|
+
metaUuids.add(entry.uuid);
|
|
475
|
+
}
|
|
415
476
|
continue;
|
|
416
477
|
}
|
|
417
478
|
if (entry.type === "user" && entry.message?.role === "user") {
|
|
@@ -421,9 +482,9 @@ async function extractFirstUserMessage(fh) {
|
|
|
421
482
|
}
|
|
422
483
|
if (Array.isArray(content)) {
|
|
423
484
|
const textBlock = content.find(
|
|
424
|
-
(b) => b.type === "text" && b.text && !b.text.startsWith("[Request interrupted")
|
|
485
|
+
(b) => b.type === "text" && "text" in b && b.text && !b.text.startsWith("[Request interrupted")
|
|
425
486
|
);
|
|
426
|
-
if (textBlock
|
|
487
|
+
if (textBlock && "text" in textBlock && typeof textBlock.text === "string") {
|
|
427
488
|
return textBlock.text.slice(0, 100);
|
|
428
489
|
}
|
|
429
490
|
}
|
|
@@ -433,41 +494,9 @@ async function extractFirstUserMessage(fh) {
|
|
|
433
494
|
}
|
|
434
495
|
return "Untitled session";
|
|
435
496
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let count = 0;
|
|
440
|
-
const metaUuids = /* @__PURE__ */ new Set();
|
|
441
|
-
for (const line of lines) {
|
|
442
|
-
if (!line.trim()) continue;
|
|
443
|
-
try {
|
|
444
|
-
const entry = JSON.parse(line);
|
|
445
|
-
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
446
|
-
if (entry.uuid) metaUuids.add(entry.uuid);
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
if (entry.type !== "user" || !entry.message || entry.message.role !== "user") continue;
|
|
450
|
-
const msgContent = entry.message.content;
|
|
451
|
-
if (typeof msgContent === "string") {
|
|
452
|
-
const trimmed = msgContent.trim();
|
|
453
|
-
if (trimmed && !trimmed.startsWith("<") && !trimmed.startsWith("[") && !trimmed.startsWith("/")) {
|
|
454
|
-
count++;
|
|
455
|
-
}
|
|
456
|
-
} else if (Array.isArray(msgContent)) {
|
|
457
|
-
const textBlock = msgContent.find(
|
|
458
|
-
(b) => b.type === "text" && b.text
|
|
459
|
-
);
|
|
460
|
-
if (textBlock?.text) {
|
|
461
|
-
const text = textBlock.text.trim();
|
|
462
|
-
if (text && !text.startsWith("<") && !text.startsWith("[") && !text.startsWith("/")) {
|
|
463
|
-
count++;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
} catch {
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return count;
|
|
497
|
+
var ANSI_RE = /\u001b\[\d*m/g;
|
|
498
|
+
function stripAnsi(text) {
|
|
499
|
+
return text.replace(ANSI_RE, "");
|
|
471
500
|
}
|
|
472
501
|
function parseSessionFile(content) {
|
|
473
502
|
const messages = [];
|
|
@@ -477,7 +506,51 @@ function parseSessionFile(content) {
|
|
|
477
506
|
try {
|
|
478
507
|
const entry = JSON.parse(line);
|
|
479
508
|
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
480
|
-
if (entry.uuid
|
|
509
|
+
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
510
|
+
metaUuids.add(entry.uuid);
|
|
511
|
+
}
|
|
512
|
+
if (entry.message?.role === "user" && typeof entry.message.content === "string") {
|
|
513
|
+
const content2 = entry.message.content;
|
|
514
|
+
const cmdMatch = content2.match(/<command-name>\/(.+?)<\/command-name>/);
|
|
515
|
+
if (cmdMatch) {
|
|
516
|
+
messages.push({
|
|
517
|
+
role: "user",
|
|
518
|
+
content: [{ type: "text", text: `/${cmdMatch[1]}` }],
|
|
519
|
+
timestamp: entry.timestamp,
|
|
520
|
+
isMeta: entry.isMeta,
|
|
521
|
+
uuid: entry.uuid,
|
|
522
|
+
parentUuid: entry.parentUuid,
|
|
523
|
+
type: entry.type
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const outMatch = content2.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
527
|
+
if (outMatch && outMatch[1].trim()) {
|
|
528
|
+
const text = stripAnsi(outMatch[1].trim());
|
|
529
|
+
if (text) {
|
|
530
|
+
messages.push({
|
|
531
|
+
role: "assistant",
|
|
532
|
+
content: [{ type: "text", text }],
|
|
533
|
+
timestamp: entry.timestamp,
|
|
534
|
+
isMeta: entry.isMeta,
|
|
535
|
+
uuid: entry.uuid,
|
|
536
|
+
parentUuid: entry.parentUuid,
|
|
537
|
+
type: entry.type
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
545
|
+
messages.push({
|
|
546
|
+
role: "assistant",
|
|
547
|
+
content: [],
|
|
548
|
+
timestamp: entry.timestamp,
|
|
549
|
+
type: "system",
|
|
550
|
+
subtype: entry.subtype,
|
|
551
|
+
uuid: entry.uuid,
|
|
552
|
+
parentUuid: entry.parentUuid
|
|
553
|
+
});
|
|
481
554
|
continue;
|
|
482
555
|
}
|
|
483
556
|
if ((entry.type === "user" || entry.type === "assistant") && entry.message) {
|
|
@@ -485,7 +558,10 @@ function parseSessionFile(content) {
|
|
|
485
558
|
messages.push({
|
|
486
559
|
role: entry.message.role,
|
|
487
560
|
content: typeof msgContent === "string" ? [{ type: "text", text: msgContent }] : msgContent,
|
|
488
|
-
timestamp: entry.timestamp
|
|
561
|
+
timestamp: entry.timestamp,
|
|
562
|
+
uuid: entry.uuid,
|
|
563
|
+
parentUuid: entry.parentUuid,
|
|
564
|
+
type: entry.type
|
|
489
565
|
});
|
|
490
566
|
}
|
|
491
567
|
} catch {
|
|
@@ -497,12 +573,25 @@ function parseSessionFile(content) {
|
|
|
497
573
|
const blocks = msg.content;
|
|
498
574
|
if (!Array.isArray(blocks)) continue;
|
|
499
575
|
for (const block of blocks) {
|
|
500
|
-
if (block.type !== "tool_result"
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
576
|
+
if (block.type !== "tool_result") continue;
|
|
577
|
+
const content2 = block.content;
|
|
578
|
+
let dataUri;
|
|
579
|
+
if (Array.isArray(content2)) {
|
|
580
|
+
const imgBlock = content2.find(
|
|
581
|
+
(b) => b?.type === "image" && b?.source?.type === "base64" && b?.source?.data
|
|
582
|
+
);
|
|
583
|
+
if (imgBlock) {
|
|
584
|
+
dataUri = `data:${imgBlock.source.media_type};base64,${imgBlock.source.data}`;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
let resultText;
|
|
588
|
+
if (typeof content2 === "string") {
|
|
589
|
+
resultText = content2 || void 0;
|
|
590
|
+
} else if (Array.isArray(content2)) {
|
|
591
|
+
const texts = content2.filter((b) => b?.type === "text" && typeof b?.text === "string").map((b) => b.text);
|
|
592
|
+
resultText = texts.join("\n") || void 0;
|
|
593
|
+
}
|
|
594
|
+
if (!dataUri && !resultText) continue;
|
|
506
595
|
for (let j = i - 1; j >= 0; j--) {
|
|
507
596
|
if (messages[j].role !== "assistant") continue;
|
|
508
597
|
const aBlocks = messages[j].content;
|
|
@@ -510,7 +599,10 @@ function parseSessionFile(content) {
|
|
|
510
599
|
const toolUse = aBlocks.find(
|
|
511
600
|
(b) => b.type === "tool_use" && b.id === block.tool_use_id
|
|
512
601
|
);
|
|
513
|
-
if (toolUse)
|
|
602
|
+
if (toolUse) {
|
|
603
|
+
if (dataUri) toolUse.imageUri = dataUri;
|
|
604
|
+
if (resultText) toolUse.result = resultText;
|
|
605
|
+
}
|
|
514
606
|
break;
|
|
515
607
|
}
|
|
516
608
|
}
|
|
@@ -778,7 +870,7 @@ async function connect(server, options) {
|
|
|
778
870
|
const activeSessions = /* @__PURE__ */ new Map();
|
|
779
871
|
socket.on("connect", () => {
|
|
780
872
|
logger.info("Connected");
|
|
781
|
-
const deviceInfo = collectDeviceInfo(deviceId);
|
|
873
|
+
const deviceInfo = collectDeviceInfo(deviceId, options.name);
|
|
782
874
|
socket.emit("register", deviceInfo, (response) => {
|
|
783
875
|
if (response.success) {
|
|
784
876
|
logger.info({ deviceId }, "Registered");
|
|
@@ -826,7 +918,7 @@ async function connect(server, options) {
|
|
|
826
918
|
});
|
|
827
919
|
socket.on("reconnect", (attemptNumber) => {
|
|
828
920
|
logger.info({ attemptNumber }, "Reconnected");
|
|
829
|
-
socket.emit("register", collectDeviceInfo(deviceId));
|
|
921
|
+
socket.emit("register", collectDeviceInfo(deviceId, options.name));
|
|
830
922
|
});
|
|
831
923
|
socket.on("connect_error", (err) => {
|
|
832
924
|
logger.error({ err }, "Connection error");
|
|
@@ -870,9 +962,16 @@ function handlePrompt(socket, msg, activeSessions) {
|
|
|
870
962
|
const handle = runClaude(
|
|
871
963
|
{ prompt: prompt2, sessionId, cwd, images, options },
|
|
872
964
|
{
|
|
965
|
+
onSessionCreated: (info) => {
|
|
966
|
+
send(socket, "response", { type: "session_created", data: info, requestId: id });
|
|
967
|
+
log2.info({ sessionId: info.sessionId }, "New session created");
|
|
968
|
+
},
|
|
873
969
|
onText: (text) => {
|
|
874
970
|
send(socket, "response", { type: "text", text, requestId: id });
|
|
875
971
|
},
|
|
972
|
+
onSlashCommandOutput: (output) => {
|
|
973
|
+
send(socket, "response", { type: "command_output", output, requestId: id });
|
|
974
|
+
},
|
|
876
975
|
onThinking: (thinking) => {
|
|
877
976
|
send(socket, "response", { type: "thinking", thinking, requestId: id });
|
|
878
977
|
},
|
|
@@ -882,8 +981,8 @@ function handlePrompt(socket, msg, activeSessions) {
|
|
|
882
981
|
onToolResult: (toolUseId, content, isError) => {
|
|
883
982
|
send(socket, "response", { type: "tool_result", tool_use_id: toolUseId, content, is_error: isError, requestId: id });
|
|
884
983
|
},
|
|
885
|
-
onResult: (sid) => {
|
|
886
|
-
send(socket, "response", { type: "result", session_id: sid, requestId: id });
|
|
984
|
+
onResult: (sid, result) => {
|
|
985
|
+
send(socket, "response", { type: "result", session_id: sid, ...result && { result }, requestId: id });
|
|
887
986
|
activeSessions.delete(id);
|
|
888
987
|
log2.info("Session done");
|
|
889
988
|
},
|
|
@@ -917,8 +1016,9 @@ function handleCancel(id, activeSessions) {
|
|
|
917
1016
|
}
|
|
918
1017
|
async function handleListSessions(socket, msg) {
|
|
919
1018
|
const { id } = msg;
|
|
1019
|
+
const workingDirectory = msg.workingDirectory ?? process.cwd();
|
|
920
1020
|
logger.info("Listing sessions...");
|
|
921
|
-
const sessions = await listSessions();
|
|
1021
|
+
const sessions = await listSessions(workingDirectory);
|
|
922
1022
|
send(socket, "response", { type: "sessions_list", sessions, requestId: id });
|
|
923
1023
|
logger.info({ count: sessions.length }, "Listed sessions");
|
|
924
1024
|
}
|
|
@@ -1022,7 +1122,7 @@ function logout() {
|
|
|
1022
1122
|
|
|
1023
1123
|
// src/commands/index.ts
|
|
1024
1124
|
function registerCommands(program2) {
|
|
1025
|
-
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)").action(connect);
|
|
1125
|
+
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").action(connect);
|
|
1026
1126
|
program2.command("login").description("Log in with your email and password").action(login);
|
|
1027
1127
|
program2.command("logout").description("Log out and clear stored credentials").action(logout);
|
|
1028
1128
|
}
|