@punkcode/cli 0.1.5 → 0.1.7
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 +160 -59
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,7 @@ function runClaude(options, callbacks) {
|
|
|
49
49
|
...opts.allowedTools && { allowedTools: opts.allowedTools },
|
|
50
50
|
...opts.disallowedTools && { disallowedTools: opts.disallowedTools },
|
|
51
51
|
...opts.maxTurns && { maxTurns: opts.maxTurns },
|
|
52
|
+
systemPrompt: opts.systemPrompt ?? { type: "preset", preset: "claude_code" },
|
|
52
53
|
...options.cwd && { cwd: options.cwd },
|
|
53
54
|
...options.sessionId && { resume: options.sessionId },
|
|
54
55
|
maxThinkingTokens: 1e4,
|
|
@@ -92,6 +93,7 @@ function runClaude(options, callbacks) {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
(async () => {
|
|
96
|
+
let sentCompactSummary = false;
|
|
95
97
|
try {
|
|
96
98
|
for await (const message of q) {
|
|
97
99
|
switch (message.type) {
|
|
@@ -118,7 +120,19 @@ function runClaude(options, callbacks) {
|
|
|
118
120
|
}
|
|
119
121
|
case "user": {
|
|
120
122
|
const userContent = message.message?.content;
|
|
121
|
-
if (
|
|
123
|
+
if (typeof userContent === "string") {
|
|
124
|
+
const match = userContent.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
125
|
+
if (match) {
|
|
126
|
+
if (sentCompactSummary && match[1].trim() === "Compacted") {
|
|
127
|
+
sentCompactSummary = false;
|
|
128
|
+
} else {
|
|
129
|
+
callbacks.onSlashCommandOutput?.(match[1]);
|
|
130
|
+
}
|
|
131
|
+
} else if (userContent.startsWith("This session is being continued")) {
|
|
132
|
+
sentCompactSummary = true;
|
|
133
|
+
callbacks.onSlashCommandOutput?.(userContent);
|
|
134
|
+
}
|
|
135
|
+
} else if (Array.isArray(userContent)) {
|
|
122
136
|
for (const block of userContent) {
|
|
123
137
|
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
|
|
124
138
|
const tr = block;
|
|
@@ -132,10 +146,35 @@ function runClaude(options, callbacks) {
|
|
|
132
146
|
}
|
|
133
147
|
break;
|
|
134
148
|
}
|
|
135
|
-
case "
|
|
136
|
-
|
|
149
|
+
case "system": {
|
|
150
|
+
const sys = message;
|
|
151
|
+
if (sys.subtype === "init" && callbacks.onSessionCreated) {
|
|
152
|
+
callbacks.onSessionCreated({
|
|
153
|
+
sessionId: sys.session_id ?? "",
|
|
154
|
+
tools: sys.tools ?? [],
|
|
155
|
+
slashCommands: (sys.slash_commands ?? []).map((cmd) => ({ name: cmd, description: "" })),
|
|
156
|
+
skills: sys.skills ?? [],
|
|
157
|
+
mcpServers: sys.mcp_servers ?? [],
|
|
158
|
+
model: sys.model ?? "",
|
|
159
|
+
cwd: sys.cwd ?? "",
|
|
160
|
+
claudeCodeVersion: sys.claude_code_version ?? "",
|
|
161
|
+
permissionMode: sys.permissionMode ?? "default"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "tool_use_summary": {
|
|
167
|
+
const summary = message.summary;
|
|
168
|
+
if (summary) {
|
|
169
|
+
callbacks.onSlashCommandOutput?.(summary);
|
|
170
|
+
}
|
|
137
171
|
break;
|
|
138
172
|
}
|
|
173
|
+
case "result": {
|
|
174
|
+
const resultText = message.subtype === "success" ? message.result : void 0;
|
|
175
|
+
callbacks.onResult(message.session_id, resultText);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
139
178
|
}
|
|
140
179
|
}
|
|
141
180
|
} catch (err) {
|
|
@@ -300,6 +339,9 @@ import { readdir, readFile, stat, open } from "fs/promises";
|
|
|
300
339
|
import { join } from "path";
|
|
301
340
|
import { homedir } from "os";
|
|
302
341
|
var CLAUDE_DIR = join(homedir(), ".claude", "projects");
|
|
342
|
+
function pathToProjectDir(dir) {
|
|
343
|
+
return dir.replace(/\//g, "-");
|
|
344
|
+
}
|
|
303
345
|
async function loadSession(sessionId) {
|
|
304
346
|
const sessionFile = `${sessionId}.jsonl`;
|
|
305
347
|
let projectDirs;
|
|
@@ -322,10 +364,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;
|
|
|
322
364
|
function isValidSessionUUID(name) {
|
|
323
365
|
return UUID_RE.test(name);
|
|
324
366
|
}
|
|
325
|
-
async function listSessions() {
|
|
367
|
+
async function listSessions(workingDirectory) {
|
|
326
368
|
let projectDirs;
|
|
327
369
|
try {
|
|
328
|
-
|
|
370
|
+
if (workingDirectory) {
|
|
371
|
+
projectDirs = [pathToProjectDir(workingDirectory)];
|
|
372
|
+
} else {
|
|
373
|
+
projectDirs = await readdir(CLAUDE_DIR);
|
|
374
|
+
}
|
|
329
375
|
} catch {
|
|
330
376
|
return [];
|
|
331
377
|
}
|
|
@@ -353,16 +399,12 @@ async function listSessions() {
|
|
|
353
399
|
candidates.map(async (c) => {
|
|
354
400
|
try {
|
|
355
401
|
const fileStat = await stat(c.filePath);
|
|
356
|
-
const
|
|
357
|
-
extractTitle(c.filePath),
|
|
358
|
-
countTurns(c.filePath)
|
|
359
|
-
]);
|
|
402
|
+
const titleInfo = await extractTitle(c.filePath);
|
|
360
403
|
return {
|
|
361
404
|
sessionId: c.sessionId,
|
|
362
405
|
project: c.project,
|
|
363
406
|
title: titleInfo.title,
|
|
364
407
|
lastModified: fileStat.mtimeMs,
|
|
365
|
-
turnCount,
|
|
366
408
|
...titleInfo.summary && { summary: titleInfo.summary }
|
|
367
409
|
};
|
|
368
410
|
} catch {
|
|
@@ -429,7 +471,9 @@ async function extractFirstUserMessage(fh) {
|
|
|
429
471
|
try {
|
|
430
472
|
const entry = JSON.parse(line);
|
|
431
473
|
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
432
|
-
if (entry.uuid
|
|
474
|
+
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
475
|
+
metaUuids.add(entry.uuid);
|
|
476
|
+
}
|
|
433
477
|
continue;
|
|
434
478
|
}
|
|
435
479
|
if (entry.type === "user" && entry.message?.role === "user") {
|
|
@@ -439,9 +483,9 @@ async function extractFirstUserMessage(fh) {
|
|
|
439
483
|
}
|
|
440
484
|
if (Array.isArray(content)) {
|
|
441
485
|
const textBlock = content.find(
|
|
442
|
-
(b) => b.type === "text" && b.text && !b.text.startsWith("[Request interrupted")
|
|
486
|
+
(b) => b.type === "text" && "text" in b && b.text && !b.text.startsWith("[Request interrupted")
|
|
443
487
|
);
|
|
444
|
-
if (textBlock
|
|
488
|
+
if (textBlock && "text" in textBlock && typeof textBlock.text === "string") {
|
|
445
489
|
return textBlock.text.slice(0, 100);
|
|
446
490
|
}
|
|
447
491
|
}
|
|
@@ -451,51 +495,81 @@ async function extractFirstUserMessage(fh) {
|
|
|
451
495
|
}
|
|
452
496
|
return "Untitled session";
|
|
453
497
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
498
|
+
var ANSI_RE = /\u001b\[\d*m/g;
|
|
499
|
+
function stripAnsi(text) {
|
|
500
|
+
return text.replace(ANSI_RE, "");
|
|
501
|
+
}
|
|
502
|
+
function parseSessionFile(content) {
|
|
503
|
+
const messages = [];
|
|
504
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
458
505
|
const metaUuids = /* @__PURE__ */ new Set();
|
|
459
506
|
for (const line of lines) {
|
|
460
|
-
if (!line.trim()) continue;
|
|
461
507
|
try {
|
|
462
508
|
const entry = JSON.parse(line);
|
|
463
509
|
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
464
|
-
if (entry.uuid
|
|
510
|
+
if (entry.uuid && entry.message?.role !== "assistant") {
|
|
511
|
+
metaUuids.add(entry.uuid);
|
|
512
|
+
}
|
|
513
|
+
if (entry.message?.role === "user" && typeof entry.message.content === "string") {
|
|
514
|
+
const content2 = entry.message.content;
|
|
515
|
+
const cmdMatch = content2.match(/<command-name>\/(.+?)<\/command-name>/);
|
|
516
|
+
if (cmdMatch) {
|
|
517
|
+
messages.push({
|
|
518
|
+
role: "user",
|
|
519
|
+
content: [{ type: "text", text: `/${cmdMatch[1]}` }],
|
|
520
|
+
timestamp: entry.timestamp,
|
|
521
|
+
isMeta: entry.isMeta,
|
|
522
|
+
uuid: entry.uuid,
|
|
523
|
+
parentUuid: entry.parentUuid,
|
|
524
|
+
type: entry.type
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
const outMatch = content2.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
528
|
+
if (outMatch && outMatch[1].trim()) {
|
|
529
|
+
const text = stripAnsi(outMatch[1].trim());
|
|
530
|
+
if (text) {
|
|
531
|
+
messages.push({
|
|
532
|
+
role: "assistant",
|
|
533
|
+
content: [{ type: "text", text }],
|
|
534
|
+
timestamp: entry.timestamp,
|
|
535
|
+
isMeta: entry.isMeta,
|
|
536
|
+
uuid: entry.uuid,
|
|
537
|
+
parentUuid: entry.parentUuid,
|
|
538
|
+
type: entry.type
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (entry.type === "system" && entry.subtype === "compact_boundary") {
|
|
546
|
+
messages.push({
|
|
547
|
+
role: "system",
|
|
548
|
+
content: [],
|
|
549
|
+
timestamp: entry.timestamp,
|
|
550
|
+
type: "system",
|
|
551
|
+
subtype: entry.subtype,
|
|
552
|
+
uuid: entry.uuid,
|
|
553
|
+
parentUuid: entry.parentUuid
|
|
554
|
+
});
|
|
465
555
|
continue;
|
|
466
556
|
}
|
|
467
|
-
if (entry.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
557
|
+
if (entry.isCompactSummary && entry.message) {
|
|
558
|
+
const summaryContent = entry.message.content;
|
|
559
|
+
let summaryText = "";
|
|
560
|
+
if (typeof summaryContent === "string") {
|
|
561
|
+
summaryText = summaryContent;
|
|
562
|
+
} else if (Array.isArray(summaryContent)) {
|
|
563
|
+
summaryText = summaryContent.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
473
564
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (text && !text.startsWith("<") && !text.startsWith("[") && !text.startsWith("/")) {
|
|
481
|
-
count++;
|
|
565
|
+
if (summaryText) {
|
|
566
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
567
|
+
if (messages[i].subtype === "compact_boundary") {
|
|
568
|
+
messages[i].content = [{ type: "text", text: summaryText }];
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
482
571
|
}
|
|
483
572
|
}
|
|
484
|
-
}
|
|
485
|
-
} catch {
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return count;
|
|
489
|
-
}
|
|
490
|
-
function parseSessionFile(content) {
|
|
491
|
-
const messages = [];
|
|
492
|
-
const lines = content.split("\n").filter((line) => line.trim());
|
|
493
|
-
const metaUuids = /* @__PURE__ */ new Set();
|
|
494
|
-
for (const line of lines) {
|
|
495
|
-
try {
|
|
496
|
-
const entry = JSON.parse(line);
|
|
497
|
-
if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
|
|
498
|
-
if (entry.uuid) metaUuids.add(entry.uuid);
|
|
499
573
|
continue;
|
|
500
574
|
}
|
|
501
575
|
if ((entry.type === "user" || entry.type === "assistant") && entry.message) {
|
|
@@ -503,7 +577,10 @@ function parseSessionFile(content) {
|
|
|
503
577
|
messages.push({
|
|
504
578
|
role: entry.message.role,
|
|
505
579
|
content: typeof msgContent === "string" ? [{ type: "text", text: msgContent }] : msgContent,
|
|
506
|
-
timestamp: entry.timestamp
|
|
580
|
+
timestamp: entry.timestamp,
|
|
581
|
+
uuid: entry.uuid,
|
|
582
|
+
parentUuid: entry.parentUuid,
|
|
583
|
+
type: entry.type
|
|
507
584
|
});
|
|
508
585
|
}
|
|
509
586
|
} catch {
|
|
@@ -515,12 +592,25 @@ function parseSessionFile(content) {
|
|
|
515
592
|
const blocks = msg.content;
|
|
516
593
|
if (!Array.isArray(blocks)) continue;
|
|
517
594
|
for (const block of blocks) {
|
|
518
|
-
if (block.type !== "tool_result"
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
|
|
595
|
+
if (block.type !== "tool_result") continue;
|
|
596
|
+
const content2 = block.content;
|
|
597
|
+
let dataUri;
|
|
598
|
+
if (Array.isArray(content2)) {
|
|
599
|
+
const imgBlock = content2.find(
|
|
600
|
+
(b) => b?.type === "image" && b?.source?.type === "base64" && b?.source?.data
|
|
601
|
+
);
|
|
602
|
+
if (imgBlock) {
|
|
603
|
+
dataUri = `data:${imgBlock.source.media_type};base64,${imgBlock.source.data}`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
let resultText;
|
|
607
|
+
if (typeof content2 === "string") {
|
|
608
|
+
resultText = content2 || void 0;
|
|
609
|
+
} else if (Array.isArray(content2)) {
|
|
610
|
+
const texts = content2.filter((b) => b?.type === "text" && typeof b?.text === "string").map((b) => b.text);
|
|
611
|
+
resultText = texts.join("\n") || void 0;
|
|
612
|
+
}
|
|
613
|
+
if (!dataUri && !resultText) continue;
|
|
524
614
|
for (let j = i - 1; j >= 0; j--) {
|
|
525
615
|
if (messages[j].role !== "assistant") continue;
|
|
526
616
|
const aBlocks = messages[j].content;
|
|
@@ -528,7 +618,10 @@ function parseSessionFile(content) {
|
|
|
528
618
|
const toolUse = aBlocks.find(
|
|
529
619
|
(b) => b.type === "tool_use" && b.id === block.tool_use_id
|
|
530
620
|
);
|
|
531
|
-
if (toolUse)
|
|
621
|
+
if (toolUse) {
|
|
622
|
+
if (dataUri) toolUse.imageUri = dataUri;
|
|
623
|
+
if (resultText) toolUse.result = resultText;
|
|
624
|
+
}
|
|
532
625
|
break;
|
|
533
626
|
}
|
|
534
627
|
}
|
|
@@ -888,9 +981,16 @@ function handlePrompt(socket, msg, activeSessions) {
|
|
|
888
981
|
const handle = runClaude(
|
|
889
982
|
{ prompt: prompt2, sessionId, cwd, images, options },
|
|
890
983
|
{
|
|
984
|
+
onSessionCreated: (info) => {
|
|
985
|
+
send(socket, "response", { type: "session_created", data: info, requestId: id });
|
|
986
|
+
log2.info({ sessionId: info.sessionId }, "New session created");
|
|
987
|
+
},
|
|
891
988
|
onText: (text) => {
|
|
892
989
|
send(socket, "response", { type: "text", text, requestId: id });
|
|
893
990
|
},
|
|
991
|
+
onSlashCommandOutput: (output) => {
|
|
992
|
+
send(socket, "response", { type: "command_output", output, requestId: id });
|
|
993
|
+
},
|
|
894
994
|
onThinking: (thinking) => {
|
|
895
995
|
send(socket, "response", { type: "thinking", thinking, requestId: id });
|
|
896
996
|
},
|
|
@@ -900,8 +1000,8 @@ function handlePrompt(socket, msg, activeSessions) {
|
|
|
900
1000
|
onToolResult: (toolUseId, content, isError) => {
|
|
901
1001
|
send(socket, "response", { type: "tool_result", tool_use_id: toolUseId, content, is_error: isError, requestId: id });
|
|
902
1002
|
},
|
|
903
|
-
onResult: (sid) => {
|
|
904
|
-
send(socket, "response", { type: "result", session_id: sid, requestId: id });
|
|
1003
|
+
onResult: (sid, result) => {
|
|
1004
|
+
send(socket, "response", { type: "result", session_id: sid, ...result && { result }, requestId: id });
|
|
905
1005
|
activeSessions.delete(id);
|
|
906
1006
|
log2.info("Session done");
|
|
907
1007
|
},
|
|
@@ -935,8 +1035,9 @@ function handleCancel(id, activeSessions) {
|
|
|
935
1035
|
}
|
|
936
1036
|
async function handleListSessions(socket, msg) {
|
|
937
1037
|
const { id } = msg;
|
|
1038
|
+
const workingDirectory = msg.workingDirectory ?? process.cwd();
|
|
938
1039
|
logger.info("Listing sessions...");
|
|
939
|
-
const sessions = await listSessions();
|
|
1040
|
+
const sessions = await listSessions(workingDirectory);
|
|
940
1041
|
send(socket, "response", { type: "sessions_list", sessions, requestId: id });
|
|
941
1042
|
logger.info({ count: sessions.length }, "Listed sessions");
|
|
942
1043
|
}
|