claude-teammate 0.1.166 → 0.1.167

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-teammate",
3
- "version": "0.1.166",
3
+ "version": "0.1.167",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1326,6 +1326,28 @@ a:hover { text-decoration:underline; }
1326
1326
  font-size:.65rem;
1327
1327
  }
1328
1328
 
1329
+ /* Log rich formatting */
1330
+ .tool-name { color:#86EFAC; font-weight:600; }
1331
+ .tool-key { color:#FCD34D; }
1332
+ .tool-arg { color:#9CA3AF; }
1333
+ .so-key { color:#60A5FA; font-weight:500; }
1334
+ .so-val { color:#E5E0D8; }
1335
+ .so-item { color:#D1D5DB; }
1336
+ .so-empty { color:#6B7280; font-style:italic; }
1337
+ .log-expand-btn {
1338
+ background:none; border:1px solid #374151; color:#9CA3AF;
1339
+ font-size:.6rem; padding:1px 5px; cursor:pointer; border-radius:3px;
1340
+ margin-left:6px; vertical-align:middle;
1341
+ }
1342
+ .log-expand-btn:hover { border-color:#6B7280; color:#E5E0D8; }
1343
+ .log-code {
1344
+ background:#0D0C0B; border:1px solid #1F2937; color:#9CA3AF;
1345
+ font-size:.63rem; padding:6px 8px; margin-top:4px; border-radius:4px;
1346
+ white-space:pre-wrap; word-break:break-all;
1347
+ max-height:180px; overflow-y:auto;
1348
+ }
1349
+ .log-code.hidden { display:none; }
1350
+
1329
1351
  /* Drawer info sections */
1330
1352
  .drawer-section { margin-bottom:20px; }
1331
1353
 
@@ -2638,13 +2660,166 @@ setInterval(() => {
2638
2660
  /* ── Detail Drawer ── */
2639
2661
  function classifyLogEntry(text) {
2640
2662
  if (text.startsWith("[thinking]")) return "thinking";
2663
+ if (text.startsWith("[tool: StructuredOutput]")) return "meta";
2641
2664
  if (text.startsWith("[tool:")) return "tool";
2642
2665
  if (text.startsWith("[session]")) return "session";
2643
2666
  if (text.startsWith("[done]")) return "done";
2644
- if (text.startsWith("[tool: StructuredOutput]")) return "meta";
2645
2667
  return "response";
2646
2668
  }
2647
2669
 
2670
+ function _eid() { return "e" + Math.random().toString(36).slice(2,9); }
2671
+
2672
+ function truncLog(s, n) {
2673
+ s = String(s).replace(/\n/g, " ");
2674
+ return s.length > n ? s.slice(0, n) + "…" : s;
2675
+ }
2676
+
2677
+ function toggleExpand(id) {
2678
+ const el = document.getElementById(id);
2679
+ if (!el) return;
2680
+ el.classList.toggle("hidden");
2681
+ const btn = document.querySelector(`[data-expand="${id}"]`);
2682
+ if (btn) btn.textContent = el.classList.contains("hidden") ? "▶ show" : "▼ hide";
2683
+ }
2684
+
2685
+ function fmtExpandable(label, fullText, maxInline) {
2686
+ if (fullText.length <= maxInline) return esc(fullText);
2687
+ const id = _eid();
2688
+ const preview = esc(fullText.slice(0, maxInline)) + "…";
2689
+ return `${preview} <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ show</button><pre class="log-code hidden" id="${id}">${esc(fullText.slice(0, 3000))}${fullText.length > 3000 ? "\n…" : ""}</pre>`;
2690
+ }
2691
+
2692
+ function fmtToolCall(toolName, rawArg) {
2693
+ /* StructuredOutput → pretty key-value */
2694
+ if (toolName === "StructuredOutput") {
2695
+ let parsed = null;
2696
+ try { parsed = JSON.parse(rawArg); } catch {}
2697
+ if (!parsed) return `<span class="tool-name">output</span> ${esc(rawArg.slice(0, 200))}`;
2698
+ const lines = [];
2699
+ for (const [k, v] of Object.entries(parsed)) {
2700
+ if (Array.isArray(v)) {
2701
+ if (v.length === 0) lines.push(`<span class="so-key">${esc(k)}:</span> <span class="so-empty">(empty)</span>`);
2702
+ else {
2703
+ lines.push(`<span class="so-key">${esc(k)}:</span>`);
2704
+ v.forEach(item => lines.push(` <span class="so-item">• ${esc(truncLog(String(item), 120))}</span>`));
2705
+ }
2706
+ } else if (v !== null && v !== undefined) {
2707
+ const s = String(v);
2708
+ lines.push(`<span class="so-key">${esc(k)}:</span> <span class="so-val">${esc(truncLog(s, 150))}</span>`);
2709
+ }
2710
+ }
2711
+ return lines.join("\n");
2712
+ }
2713
+
2714
+ /* Try parse JSON args */
2715
+ let parsed = null;
2716
+ try { parsed = JSON.parse(rawArg); } catch {}
2717
+
2718
+ const nameHtml = `<span class="tool-name">${esc(toolName.replace(/^mcp__[^_]+__/, ""))}</span>`;
2719
+
2720
+ if (!parsed) {
2721
+ return `${nameHtml} <span class="tool-arg">${esc(truncLog(rawArg, 150))}</span>`;
2722
+ }
2723
+
2724
+ /* Pick the most meaningful single field to show inline */
2725
+ const KEY_MAP = {
2726
+ Bash: "command", Read: "file_path", Write: "file_path",
2727
+ Edit: "file_path", Glob: "pattern", Grep: "pattern",
2728
+ WebFetch: "url", WebSearch: "query",
2729
+ TodoWrite: null,
2730
+ };
2731
+ const tn = toolName.replace(/^mcp__[^_]+__/, "");
2732
+ let keyVal = null;
2733
+ if (tn in KEY_MAP && KEY_MAP[tn]) {
2734
+ keyVal = parsed[KEY_MAP[tn]] != null ? String(parsed[KEY_MAP[tn]]) : null;
2735
+ } else if (tn.includes("jira_get_issue")) keyVal = parsed.issue_key || null;
2736
+ else if (tn.includes("jira_add_comment")) keyVal = parsed.issue_key ? `${parsed.issue_key}: ${truncLog(parsed.body || "", 60)}` : null;
2737
+ else if (tn.includes("jira_transition")) keyVal = parsed.issue_key ? `${parsed.issue_key}` : null;
2738
+ else if (tn.includes("jira_search") || tn.includes("confluence_search")) keyVal = parsed.jql || parsed.query || null;
2739
+ else if (tn.includes("jira_update") || tn.includes("jira_create")) keyVal = parsed.issue_key || parsed.summary || null;
2740
+ else if (tn.includes("confluence_get_page") || tn.includes("confluence_update")) keyVal = parsed.page_id ? `page ${parsed.page_id}` : null;
2741
+ else if (tn === "TodoWrite") keyVal = `${Array.isArray(parsed.todos) ? parsed.todos.length : "?"} items`;
2742
+ else if (tn.includes("browser_navigate")) keyVal = parsed.url || null;
2743
+ else if (tn.includes("browser_")) keyVal = parsed.selector || parsed.text || null;
2744
+ else if (tn.includes("gsheets")) keyVal = parsed.spreadsheet_id || parsed.sheet_name || null;
2745
+
2746
+ const fullJson = JSON.stringify(parsed, null, 2);
2747
+ const id = _eid();
2748
+ const keyHtml = keyVal ? ` <span class="tool-key">${esc(truncLog(keyVal, 100))}</span>` : "";
2749
+ const expandHtml = fullJson.length > 120
2750
+ ? ` <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ args</button><pre class="log-code hidden" id="${id}">${esc(fullJson.slice(0, 3000))}${fullJson.length > 3000 ? "\n…" : ""}</pre>`
2751
+ : ` <span class="tool-arg">${esc(fullJson)}</span>`;
2752
+
2753
+ return `${nameHtml}${keyHtml}${keyVal ? "" : expandHtml}${keyVal ? expandHtml : ""}`;
2754
+ }
2755
+
2756
+ function fmtAgentMessage(text) {
2757
+ /* Strip base64 blobs first */
2758
+ const b64re = /"base64":"([A-Za-z0-9+/]{80,}={0,2})"/g;
2759
+ let b64total = 0;
2760
+ const stripped = text.replace(b64re, (_, b) => {
2761
+ b64total += Math.round(b.length * 0.75);
2762
+ return `"base64":"[…${Math.round(b.length * 0.75 / 1024)}kb…]"`;
2763
+ });
2764
+
2765
+ if (b64total > 0) {
2766
+ return `<span class="tool-key">📎 Binary/image data (~${Math.round(b64total/1024)}kb encoded)</span>`;
2767
+ }
2768
+
2769
+ /* Large JSON blob → collapse */
2770
+ if (text.length > 250) {
2771
+ let summary = "Agent message";
2772
+ try {
2773
+ const p = JSON.parse(text);
2774
+ const role = p.message?.role || p.type || "";
2775
+ const content = p.message?.content;
2776
+ if (Array.isArray(content)) {
2777
+ const types = [...new Set(content.map(c => c.type || "?"))].join(", ");
2778
+ const first = content.find(c => typeof c.content === "string" && c.content.length > 0);
2779
+ summary = role ? `${role} [${types}]` : `[${types}]`;
2780
+ if (first) summary += `: ${truncLog(first.content, 80)}`;
2781
+ } else if (role) {
2782
+ summary = role;
2783
+ }
2784
+ } catch {}
2785
+ const id = _eid();
2786
+ return `<span class="tool-arg">${esc(summary)}</span> <button class="log-expand-btn" data-expand="${id}" onclick="toggleExpand('${id}')">▶ raw</button><pre class="log-code hidden" id="${id}">${esc(text.slice(0, 3000))}${text.length > 3000 ? "\n…" : ""}</pre>`;
2787
+ }
2788
+
2789
+ return esc(text);
2790
+ }
2791
+
2792
+ function formatLogContent(text) {
2793
+ /* [done] success duration=Xms cost=$Y */
2794
+ const doneM = text.match(/^\[done\]\s+(\w+)\s+duration=(\d+)ms(?:\s+cost=\$?([\d.]+))?/);
2795
+ if (doneM) {
2796
+ const secs = (parseInt(doneM[2]) / 1000).toFixed(1);
2797
+ const cost = doneM[3] ? ` · $${doneM[3]}` : "";
2798
+ return `${esc(doneM[1])} — ${secs}s${esc(cost)}`;
2799
+ }
2800
+
2801
+ /* [session] model=... */
2802
+ if (text.startsWith("[session]")) {
2803
+ return `<span style="opacity:.35">${esc(text.replace(/^\[session\]\s*/, ""))}</span>`;
2804
+ }
2805
+
2806
+ /* [tool: NAME] args */
2807
+ const toolM = text.match(/^\[tool: ([^\]]+)\]\s*([\s\S]*)/);
2808
+ if (toolM) return fmtToolCall(toolM[1], toolM[2].trim());
2809
+
2810
+ /* [thinking] text */
2811
+ if (text.startsWith("[thinking]")) {
2812
+ const body = text.replace(/^\[thinking\]\s*/, "");
2813
+ return fmtExpandable("thinking", body, 200);
2814
+ }
2815
+
2816
+ /* Agent message JSON */
2817
+ if (text.startsWith('{"type":')) return fmtAgentMessage(text);
2818
+
2819
+ /* Plain text — collapse if very long */
2820
+ return fmtExpandable("text", text, 300);
2821
+ }
2822
+
2648
2823
  function setLogFilter(filter) {
2649
2824
  currentLogFilter = filter;
2650
2825
  document.querySelectorAll(".log-filter-btn[data-filter]").forEach(btn => {
@@ -2813,14 +2988,12 @@ function renderIssueLogLine(line) {
2813
2988
  const phase = phaseMatch ? phaseMatch[1] : "";
2814
2989
  const text = line.replace(/^\[[^\]]+\] (?:\[[^\]]+\] )?/, "");
2815
2990
  const type = classifyLogEntry(text);
2816
- const displayText = text
2817
- .replace(/^\[thinking\] /, "")
2818
- .replace(/^\[tool: ([^\]]+)\] /, (_, name) => name === "StructuredOutput" ? "[output] " : `${name}: `);
2991
+ const content = formatLogContent(text);
2819
2992
  return `<div class="log-entry type-${type}" data-type="${type}">
2820
2993
  <div class="log-entry-ts">${esc(ts)}</div>
2821
2994
  <div class="log-entry-phase phase-${esc(phase)}">${esc(phase)}</div>
2822
2995
  <div class="log-entry-sep">|</div>
2823
- <div class="log-entry-content">${esc(displayText)}</div>
2996
+ <div class="log-entry-content">${content}</div>
2824
2997
  </div>`;
2825
2998
  }
2826
2999