@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.
Files changed (2) hide show
  1. package/dist/cli.js +160 -59
  2. 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 (Array.isArray(userContent)) {
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 "result": {
136
- callbacks.onResult(message.session_id);
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
- projectDirs = await readdir(CLAUDE_DIR);
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 [titleInfo, turnCount] = await Promise.all([
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) metaUuids.add(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?.text) {
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
- async function countTurns(filePath) {
455
- const content = await readFile(filePath, "utf-8");
456
- const lines = content.split("\n");
457
- let count = 0;
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) metaUuids.add(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.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++;
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
- } 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++;
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" || !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}`;
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) toolUse.imageUri = dataUri;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {