claude-bridge-cli 1.2.8 → 1.3.0

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/lib/bridge.js +50 -6
  2. package/package.json +1 -1
package/lib/bridge.js CHANGED
@@ -139,13 +139,13 @@ function parseSessionFile(filePath) {
139
139
  if (!preview && !looksLikeInternal(text)) preview = text.slice(0, 200);
140
140
  }
141
141
  if (obj.type === "result" && obj.result?.metadata?.title?.value) aiTitle = aiTitle || obj.result.metadata.title.value;
142
- if (obj.type === "ai-title" && obj.title) aiTitle = obj.title;
142
+ if (obj.type === "ai-title") aiTitle = obj.aiTitle || obj.title || aiTitle;
143
+ if (obj.type === "custom-title") customTitle = obj.customTitle || obj.title || customTitle;
143
144
  } catch {}
144
145
  }
145
146
  if (stat.size > CHUNK) {
146
147
  const tailBuf = Buffer.alloc(CHUNK);
147
148
  fs.readSync(fd, tailBuf, 0, CHUNK, stat.size - CHUNK);
148
- // Skip first (possibly partial) line
149
149
  const tail = tailBuf.toString("utf8");
150
150
  const tailLines = tail.split("\n");
151
151
  if (tailLines.length > 1) tailLines.shift();
@@ -157,8 +157,8 @@ function parseSessionFile(filePath) {
157
157
  const text = typeof obj.message.content === "string" ? obj.message.content : JSON.stringify(obj.message.content);
158
158
  if (!looksLikeInternal(text)) lastPrompt = text.slice(0, 200);
159
159
  }
160
- if (obj.type === "custom-title" && obj.title) customTitle = obj.title;
161
- if (obj.type === "ai-title" && obj.title) aiTitle = obj.title;
160
+ if (obj.type === "ai-title") aiTitle = obj.aiTitle || obj.title || aiTitle;
161
+ if (obj.type === "custom-title") customTitle = obj.customTitle || obj.title || customTitle;
162
162
  } catch {}
163
163
  }
164
164
  }
@@ -281,6 +281,39 @@ function searchSessions(query, limit = 30) {
281
281
 
282
282
  // ── Session messages ──
283
283
 
284
+ const INTERNAL_USER_PATTERNS = [
285
+ "<system-reminder>", "<command-name>", "<command-message>",
286
+ "<local-command-stdout>", "<command-stderr>", "<local-command-stderr>",
287
+ "Caveat: The messages below were generated by",
288
+ ];
289
+
290
+ function extractUserText(content) {
291
+ // Only extract type:"text" parts. tool_result records don't have a text
292
+ // part so they return empty string and get filtered out.
293
+ if (typeof content === "string") return content;
294
+ if (Array.isArray(content)) {
295
+ for (const part of content) {
296
+ if (part && typeof part === "object" && part.type === "text") {
297
+ return part.text || "";
298
+ }
299
+ }
300
+ }
301
+ return "";
302
+ }
303
+
304
+ function shouldSkipUserText(text) {
305
+ if (!text) return true;
306
+ const t = text.replace(/^\s+/, "");
307
+ // Skip internal command-stub patterns
308
+ if (INTERNAL_USER_PATTERNS.some(p => t.startsWith(p))) return true;
309
+ // Skip @file references — the bridge writes prompts to temp files,
310
+ // claude logs the literal @path string in the JSONL even though it
311
+ // reads file contents internally.
312
+ if (/^@[A-Z]:[\\\/]/.test(t) && /\.txt\s*$/.test(t)) return true;
313
+ if (/^@\/.*\.txt\s*$/.test(t)) return true;
314
+ return false;
315
+ }
316
+
284
317
  function getSessionMessages(sessionId) {
285
318
  const base = projectsDir();
286
319
  let dirs;
@@ -292,8 +325,12 @@ function getSessionMessages(sessionId) {
292
325
  for (const line of fs.readFileSync(filePath, "utf8").split("\n").filter(Boolean)) {
293
326
  try {
294
327
  const obj = JSON.parse(line);
328
+ // Skip transcript-only and meta records (replays, recaps)
329
+ if (obj.isMeta === true) continue;
330
+ if (obj.isVisibleInTranscriptOnly === true) continue;
295
331
  if (obj.type === "user" && obj.message?.content) {
296
- let text = typeof obj.message.content === "string" ? obj.message.content : JSON.stringify(obj.message.content);
332
+ const text = extractUserText(obj.message.content);
333
+ if (shouldSkipUserText(text)) continue;
297
334
  const split = splitUserTextAndImages(text);
298
335
  messages.push({
299
336
  role: "user",
@@ -304,7 +341,14 @@ function getSessionMessages(sessionId) {
304
341
  } else if (obj.type === "assistant" && obj.message?.content) {
305
342
  const parts = Array.isArray(obj.message.content) ? obj.message.content : [obj.message.content];
306
343
  const text = parts.map(p => typeof p === "string" ? p : p.text || "").join("");
307
- if (text) messages.push({ role: "assistant", text, timestamp: obj.timestamp });
344
+ if (!text) continue;
345
+ // Merge consecutive assistant turns into one (tool_use cycles)
346
+ if (messages.length && messages[messages.length - 1].role === "assistant") {
347
+ messages[messages.length - 1].text = (messages[messages.length - 1].text + "\n" + text).trim();
348
+ messages[messages.length - 1].timestamp = obj.timestamp || messages[messages.length - 1].timestamp;
349
+ } else {
350
+ messages.push({ role: "assistant", text, timestamp: obj.timestamp });
351
+ }
308
352
  } else if (obj.type === "result" && obj.result?.assistantMessage) {
309
353
  const text = typeof obj.result.assistantMessage === "string" ? obj.result.assistantMessage : "";
310
354
  if (text) messages.push({ role: "assistant", text, timestamp: obj.timestamp });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-bridge-cli",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
5
  "main": "lib/bridge.js",
6
6
  "bin": {