@punkcode/cli 0.1.5 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +142 -60
  2. 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 (Array.isArray(userContent)) {
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 "result": {
136
- callbacks.onResult(message.session_id);
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
+ }
163
+ break;
164
+ }
165
+ case "tool_use_summary": {
166
+ const summary = message.summary;
167
+ if (summary) {
168
+ callbacks.onSlashCommandOutput?.(summary);
169
+ }
137
170
  break;
138
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) {
@@ -300,6 +338,9 @@ import { readdir, readFile, stat, open } from "fs/promises";
300
338
  import { join } from "path";
301
339
  import { homedir } from "os";
302
340
  var CLAUDE_DIR = join(homedir(), ".claude", "projects");
341
+ function pathToProjectDir(dir) {
342
+ return dir.replace(/\//g, "-");
343
+ }
303
344
  async function loadSession(sessionId) {
304
345
  const sessionFile = `${sessionId}.jsonl`;
305
346
  let projectDirs;
@@ -322,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;
322
363
  function isValidSessionUUID(name) {
323
364
  return UUID_RE.test(name);
324
365
  }
325
- async function listSessions() {
366
+ async function listSessions(workingDirectory) {
326
367
  let projectDirs;
327
368
  try {
328
- projectDirs = await readdir(CLAUDE_DIR);
369
+ if (workingDirectory) {
370
+ projectDirs = [pathToProjectDir(workingDirectory)];
371
+ } else {
372
+ projectDirs = await readdir(CLAUDE_DIR);
373
+ }
329
374
  } catch {
330
375
  return [];
331
376
  }
@@ -353,16 +398,12 @@ async function listSessions() {
353
398
  candidates.map(async (c) => {
354
399
  try {
355
400
  const fileStat = await stat(c.filePath);
356
- const [titleInfo, turnCount] = await Promise.all([
357
- extractTitle(c.filePath),
358
- countTurns(c.filePath)
359
- ]);
401
+ const titleInfo = await extractTitle(c.filePath);
360
402
  return {
361
403
  sessionId: c.sessionId,
362
404
  project: c.project,
363
405
  title: titleInfo.title,
364
406
  lastModified: fileStat.mtimeMs,
365
- turnCount,
366
407
  ...titleInfo.summary && { summary: titleInfo.summary }
367
408
  };
368
409
  } catch {
@@ -429,7 +470,9 @@ async function extractFirstUserMessage(fh) {
429
470
  try {
430
471
  const entry = JSON.parse(line);
431
472
  if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
432
- if (entry.uuid) metaUuids.add(entry.uuid);
473
+ if (entry.uuid && entry.message?.role !== "assistant") {
474
+ metaUuids.add(entry.uuid);
475
+ }
433
476
  continue;
434
477
  }
435
478
  if (entry.type === "user" && entry.message?.role === "user") {
@@ -439,9 +482,9 @@ async function extractFirstUserMessage(fh) {
439
482
  }
440
483
  if (Array.isArray(content)) {
441
484
  const textBlock = content.find(
442
- (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")
443
486
  );
444
- if (textBlock?.text) {
487
+ if (textBlock && "text" in textBlock && typeof textBlock.text === "string") {
445
488
  return textBlock.text.slice(0, 100);
446
489
  }
447
490
  }
@@ -451,41 +494,9 @@ async function extractFirstUserMessage(fh) {
451
494
  }
452
495
  return "Untitled session";
453
496
  }
454
- async function countTurns(filePath) {
455
- const content = await readFile(filePath, "utf-8");
456
- const lines = content.split("\n");
457
- let count = 0;
458
- const metaUuids = /* @__PURE__ */ new Set();
459
- for (const line of lines) {
460
- if (!line.trim()) continue;
461
- try {
462
- const entry = JSON.parse(line);
463
- if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
464
- if (entry.uuid) metaUuids.add(entry.uuid);
465
- continue;
466
- }
467
- if (entry.type !== "user" || !entry.message || entry.message.role !== "user") continue;
468
- const msgContent = entry.message.content;
469
- if (typeof msgContent === "string") {
470
- const trimmed = msgContent.trim();
471
- if (trimmed && !trimmed.startsWith("<") && !trimmed.startsWith("[") && !trimmed.startsWith("/")) {
472
- count++;
473
- }
474
- } else if (Array.isArray(msgContent)) {
475
- const textBlock = msgContent.find(
476
- (b) => b.type === "text" && b.text
477
- );
478
- if (textBlock?.text) {
479
- const text = textBlock.text.trim();
480
- if (text && !text.startsWith("<") && !text.startsWith("[") && !text.startsWith("/")) {
481
- count++;
482
- }
483
- }
484
- }
485
- } catch {
486
- }
487
- }
488
- return count;
497
+ var ANSI_RE = /\u001b\[\d*m/g;
498
+ function stripAnsi(text) {
499
+ return text.replace(ANSI_RE, "");
489
500
  }
490
501
  function parseSessionFile(content) {
491
502
  const messages = [];
@@ -495,7 +506,51 @@ function parseSessionFile(content) {
495
506
  try {
496
507
  const entry = JSON.parse(line);
497
508
  if (entry.isMeta || entry.parentUuid && metaUuids.has(entry.parentUuid)) {
498
- if (entry.uuid) metaUuids.add(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
+ });
499
554
  continue;
500
555
  }
501
556
  if ((entry.type === "user" || entry.type === "assistant") && entry.message) {
@@ -503,7 +558,10 @@ function parseSessionFile(content) {
503
558
  messages.push({
504
559
  role: entry.message.role,
505
560
  content: typeof msgContent === "string" ? [{ type: "text", text: msgContent }] : msgContent,
506
- timestamp: entry.timestamp
561
+ timestamp: entry.timestamp,
562
+ uuid: entry.uuid,
563
+ parentUuid: entry.parentUuid,
564
+ type: entry.type
507
565
  });
508
566
  }
509
567
  } catch {
@@ -515,12 +573,25 @@ function parseSessionFile(content) {
515
573
  const blocks = msg.content;
516
574
  if (!Array.isArray(blocks)) continue;
517
575
  for (const block of blocks) {
518
- if (block.type !== "tool_result" || !Array.isArray(block.content)) continue;
519
- const imgBlock = block.content.find(
520
- (b) => b?.type === "image" && b?.source?.type === "base64" && b?.source?.data
521
- );
522
- if (!imgBlock) continue;
523
- const dataUri = `data:${imgBlock.source.media_type};base64,${imgBlock.source.data}`;
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;
524
595
  for (let j = i - 1; j >= 0; j--) {
525
596
  if (messages[j].role !== "assistant") continue;
526
597
  const aBlocks = messages[j].content;
@@ -528,7 +599,10 @@ function parseSessionFile(content) {
528
599
  const toolUse = aBlocks.find(
529
600
  (b) => b.type === "tool_use" && b.id === block.tool_use_id
530
601
  );
531
- if (toolUse) toolUse.imageUri = dataUri;
602
+ if (toolUse) {
603
+ if (dataUri) toolUse.imageUri = dataUri;
604
+ if (resultText) toolUse.result = resultText;
605
+ }
532
606
  break;
533
607
  }
534
608
  }
@@ -888,9 +962,16 @@ function handlePrompt(socket, msg, activeSessions) {
888
962
  const handle = runClaude(
889
963
  { prompt: prompt2, sessionId, cwd, images, options },
890
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
+ },
891
969
  onText: (text) => {
892
970
  send(socket, "response", { type: "text", text, requestId: id });
893
971
  },
972
+ onSlashCommandOutput: (output) => {
973
+ send(socket, "response", { type: "command_output", output, requestId: id });
974
+ },
894
975
  onThinking: (thinking) => {
895
976
  send(socket, "response", { type: "thinking", thinking, requestId: id });
896
977
  },
@@ -900,8 +981,8 @@ function handlePrompt(socket, msg, activeSessions) {
900
981
  onToolResult: (toolUseId, content, isError) => {
901
982
  send(socket, "response", { type: "tool_result", tool_use_id: toolUseId, content, is_error: isError, requestId: id });
902
983
  },
903
- onResult: (sid) => {
904
- 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 });
905
986
  activeSessions.delete(id);
906
987
  log2.info("Session done");
907
988
  },
@@ -935,8 +1016,9 @@ function handleCancel(id, activeSessions) {
935
1016
  }
936
1017
  async function handleListSessions(socket, msg) {
937
1018
  const { id } = msg;
1019
+ const workingDirectory = msg.workingDirectory ?? process.cwd();
938
1020
  logger.info("Listing sessions...");
939
- const sessions = await listSessions();
1021
+ const sessions = await listSessions(workingDirectory);
940
1022
  send(socket, "response", { type: "sessions_list", sessions, requestId: id });
941
1023
  logger.info({ count: sessions.length }, "Listed sessions");
942
1024
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {