@raquezha/notrace 0.0.6 → 0.1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +24 -1
- package/bin/notrace-compare.mjs +153 -0
- package/bin/notrace-review.mjs +123 -0
- package/dist/notrace/adapters.js +4 -1
- package/dist/notrace/index.js +84 -16
- package/dist/notrace/renderer.js +219 -24
- package/dist/notrace/types.d.ts +1 -0
- package/extensions/notrace/adapters.ts +4 -1
- package/extensions/notrace/index.ts +75 -18
- package/extensions/notrace/renderer.ts +218 -24
- package/extensions/notrace/types.ts +1 -0
- package/package.json +7 -1
- package/templates/dashboard.sample.html +96 -14
- package/templates/session.sample.html +122 -12
- package/templates/sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html +122 -12
- package/templates/sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html +122 -12
- package/templates/sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html +122 -12
|
@@ -87,7 +87,9 @@ function taskDisplay(taskish: any): string {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function resolveRepoName(data: any): string {
|
|
90
|
-
|
|
90
|
+
const name = data?.repository?.name || data?.repositoryName || data?.repoName || "Repository";
|
|
91
|
+
const branch = data?.repository?.branch;
|
|
92
|
+
return branch ? `${name} @ ${branch}` : name;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
function formatUsd(value: number | undefined): string {
|
|
@@ -211,6 +213,44 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
211
213
|
overflow: visible;
|
|
212
214
|
}
|
|
213
215
|
.subtitle { margin: 10px 0 0; color: var(--muted); }
|
|
216
|
+
.session-subtitle {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: 10px;
|
|
220
|
+
flex-wrap: wrap;
|
|
221
|
+
}
|
|
222
|
+
.session-id-chip {
|
|
223
|
+
display: inline-flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
gap: 8px;
|
|
226
|
+
max-width: 100%;
|
|
227
|
+
padding: 6px 8px 6px 10px;
|
|
228
|
+
border: 1px solid var(--border);
|
|
229
|
+
border-radius: 999px;
|
|
230
|
+
background: rgba(0,0,0,0.18);
|
|
231
|
+
color: var(--text);
|
|
232
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
233
|
+
font-size: 0.78rem;
|
|
234
|
+
word-break: break-all;
|
|
235
|
+
}
|
|
236
|
+
.copy-btn {
|
|
237
|
+
display: inline-flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
justify-content: center;
|
|
240
|
+
width: 26px;
|
|
241
|
+
height: 26px;
|
|
242
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
243
|
+
border-radius: 999px;
|
|
244
|
+
background: rgba(255,255,255,0.04);
|
|
245
|
+
color: var(--muted);
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
248
|
+
}
|
|
249
|
+
.copy-btn:hover, .copy-btn.copied {
|
|
250
|
+
color: var(--text);
|
|
251
|
+
border-color: rgba(216,132,98,0.45);
|
|
252
|
+
background: var(--accent-soft);
|
|
253
|
+
}
|
|
214
254
|
.meta {
|
|
215
255
|
display: flex;
|
|
216
256
|
gap: 8px;
|
|
@@ -219,7 +259,7 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
219
259
|
align-items: center;
|
|
220
260
|
margin-top: 16px;
|
|
221
261
|
}
|
|
222
|
-
.pill, .workflow-pill, .sort-btn {
|
|
262
|
+
.pill, .workflow-pill, .sort-btn, .export-btn {
|
|
223
263
|
display: inline-flex;
|
|
224
264
|
align-items: center;
|
|
225
265
|
gap: 6px;
|
|
@@ -232,7 +272,7 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
232
272
|
font-size: 0.86rem;
|
|
233
273
|
font-weight: 600;
|
|
234
274
|
}
|
|
235
|
-
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
275
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
|
|
236
276
|
.metric-card {
|
|
237
277
|
background: var(--panel-strong);
|
|
238
278
|
border: 1px solid var(--border);
|
|
@@ -351,7 +391,42 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
351
391
|
.msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
352
392
|
.msg.user .msg-role { color: #8ec5ff; }
|
|
353
393
|
.msg.assistant .msg-role { color: var(--accent); }
|
|
354
|
-
.msg-content { padding: 14px;
|
|
394
|
+
.msg-content { padding: 14px; }
|
|
395
|
+
.chat-text {
|
|
396
|
+
white-space: pre-wrap;
|
|
397
|
+
word-break: break-word;
|
|
398
|
+
font-size: 0.95rem;
|
|
399
|
+
line-height: 1.6;
|
|
400
|
+
margin-bottom: 12px;
|
|
401
|
+
}
|
|
402
|
+
.chat-text:last-child { margin-bottom: 0; }
|
|
403
|
+
.chat-tool-use {
|
|
404
|
+
background: rgba(0,0,0,0.3);
|
|
405
|
+
border: 1px solid var(--border);
|
|
406
|
+
border-radius: 8px;
|
|
407
|
+
overflow: hidden;
|
|
408
|
+
margin-bottom: 12px;
|
|
409
|
+
}
|
|
410
|
+
.chat-tool-use:last-child { margin-bottom: 0; }
|
|
411
|
+
.chat-tool-header {
|
|
412
|
+
background: rgba(255,255,255,0.04);
|
|
413
|
+
padding: 8px 12px;
|
|
414
|
+
font-size: 0.8rem;
|
|
415
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
416
|
+
color: #8ec5ff;
|
|
417
|
+
border-bottom: 1px solid var(--border);
|
|
418
|
+
display: flex;
|
|
419
|
+
align-items: center;
|
|
420
|
+
gap: 8px;
|
|
421
|
+
}
|
|
422
|
+
.chat-tool-body {
|
|
423
|
+
padding: 12px;
|
|
424
|
+
margin: 0;
|
|
425
|
+
background: transparent;
|
|
426
|
+
border: none;
|
|
427
|
+
max-height: 400px;
|
|
428
|
+
overflow-y: auto;
|
|
429
|
+
}
|
|
355
430
|
.footer-note {
|
|
356
431
|
margin-top: 22px;
|
|
357
432
|
color: var(--muted);
|
|
@@ -396,7 +471,15 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
396
471
|
color: var(--text);
|
|
397
472
|
border-bottom-color: rgba(236,227,218,0.45);
|
|
398
473
|
}
|
|
399
|
-
|
|
474
|
+
.export-btn {
|
|
475
|
+
cursor: pointer;
|
|
476
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
477
|
+
}
|
|
478
|
+
.export-btn:hover, .export-btn.copied {
|
|
479
|
+
color: var(--text);
|
|
480
|
+
border-color: rgba(216,132,98,0.45);
|
|
481
|
+
background: var(--accent-soft);
|
|
482
|
+
}
|
|
400
483
|
.container { padding: 20px 14px 48px; }
|
|
401
484
|
.hero { padding: 20px; }
|
|
402
485
|
.hero-top { grid-template-columns: 1fr; }
|
|
@@ -411,13 +494,121 @@ function shell(title: string, body: string, script = ""): string {
|
|
|
411
494
|
</html>`;
|
|
412
495
|
}
|
|
413
496
|
|
|
497
|
+
function copyButton(value: string, label: string, className = "copy-btn"): string {
|
|
498
|
+
return `<button class="${escapeHtml(className)}" type="button" data-copy-value="${escapeHtml(value)}" aria-label="Copy ${escapeHtml(label)}" title="Copy ${escapeHtml(label)}"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function exportButton(data: any): string {
|
|
502
|
+
const payload = JSON.stringify({
|
|
503
|
+
kind: "notrace-export",
|
|
504
|
+
traceId: data.traceId,
|
|
505
|
+
repository: data.repository?.name,
|
|
506
|
+
branch: data.repository?.branch,
|
|
507
|
+
task: data.task,
|
|
508
|
+
metrics: data.activity?.totals,
|
|
509
|
+
summary: data.telemetry?.extensions?.noheadroom?.summary,
|
|
510
|
+
events: (data.events || []).filter((e: any) => e.type === "llm_completion").map((e: any) => ({
|
|
511
|
+
model: e.model,
|
|
512
|
+
input: e.inputPayload?.messages,
|
|
513
|
+
output: e.outputContent
|
|
514
|
+
}))
|
|
515
|
+
});
|
|
516
|
+
return `<button class="export-btn" type="button" data-copy-value="${escapeHtml(payload)}" title="Copy session data for LLM/Agent context"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg><span>Export</span></button>`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function copyScript(): string {
|
|
520
|
+
return `(() => {
|
|
521
|
+
document.querySelectorAll('[data-copy-value]').forEach((button) => {
|
|
522
|
+
button.addEventListener('click', async () => {
|
|
523
|
+
const value = button.getAttribute('data-copy-value') || '';
|
|
524
|
+
try {
|
|
525
|
+
if (navigator.clipboard?.writeText) {
|
|
526
|
+
await navigator.clipboard.writeText(value);
|
|
527
|
+
} else {
|
|
528
|
+
const textarea = document.createElement('textarea');
|
|
529
|
+
textarea.value = value;
|
|
530
|
+
textarea.style.position = 'fixed';
|
|
531
|
+
textarea.style.opacity = '0';
|
|
532
|
+
document.body.appendChild(textarea);
|
|
533
|
+
textarea.focus();
|
|
534
|
+
textarea.select();
|
|
535
|
+
document.execCommand('copy');
|
|
536
|
+
textarea.remove();
|
|
537
|
+
}
|
|
538
|
+
const previous = button.innerHTML;
|
|
539
|
+
button.classList.add('copied');
|
|
540
|
+
button.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>';
|
|
541
|
+
setTimeout(() => {
|
|
542
|
+
button.classList.remove('copied');
|
|
543
|
+
button.innerHTML = previous;
|
|
544
|
+
}, 1400);
|
|
545
|
+
} catch {
|
|
546
|
+
button.textContent = 'ERR';
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
})();`;
|
|
551
|
+
}
|
|
552
|
+
|
|
414
553
|
function renderJsonBlock(title: string, value: unknown): string {
|
|
415
554
|
return `<section class="block"><h4>${escapeHtml(title)}</h4><pre>${escapeHtml(typeof value === "string" ? value : JSON.stringify(value, null, 2))}</pre></section>`;
|
|
416
555
|
}
|
|
417
556
|
|
|
557
|
+
function renderToolUseHtml(name: string, input: any): string {
|
|
558
|
+
const parsedInput = typeof input === "string" ? (() => { try { return JSON.parse(input); } catch { return input; } })() : input;
|
|
559
|
+
return `<div class="chat-tool-use"><div class="chat-tool-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg> ${escapeHtml(name)}</div><pre class="chat-tool-body">${escapeHtml(typeof parsedInput === 'string' ? parsedInput : JSON.stringify(parsedInput, null, 2))}</pre></div>`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function renderToolResultHtml(id: string, content: any): string {
|
|
563
|
+
return `<div class="chat-tool-use"><div class="chat-tool-header" style="color: var(--muted);"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Tool Result: ${escapeHtml(id)}</div><pre class="chat-tool-body">${escapeHtml(typeof content === 'string' ? content : JSON.stringify(content, null, 2))}</pre></div>`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function renderUniversalMessageContent(m: any): string {
|
|
567
|
+
if (!m) return "";
|
|
568
|
+
let html = "";
|
|
569
|
+
|
|
570
|
+
// 1. Handle string content or Anthropic/Pi blocks
|
|
571
|
+
if (typeof m.content === "string" && m.content.trim()) {
|
|
572
|
+
html += `<div class="chat-text">${escapeHtml(m.content)}</div>`;
|
|
573
|
+
} else if (Array.isArray(m.content)) {
|
|
574
|
+
html += m.content.map((block: any) => {
|
|
575
|
+
if (!block) return "";
|
|
576
|
+
if (block.type === "text") return `<div class="chat-text">${escapeHtml(block.text)}</div>`;
|
|
577
|
+
if (block.type === "tool_use") return renderToolUseHtml(block.name, block.input);
|
|
578
|
+
if (block.type === "tool_result") return renderToolResultHtml(block.tool_use_id || "unknown", block.content);
|
|
579
|
+
return `<pre class="chat-tool-body">${escapeHtml(JSON.stringify(block, null, 2))}</pre>`;
|
|
580
|
+
}).join("");
|
|
581
|
+
} else if (m.content && typeof m.content === "object") {
|
|
582
|
+
html += `<pre class="chat-tool-body">${escapeHtml(JSON.stringify(m.content, null, 2))}</pre>`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// 2. Handle OpenAI/Codex tool_calls (attached to message, not in content)
|
|
586
|
+
if (Array.isArray(m.tool_calls)) {
|
|
587
|
+
html += m.tool_calls.map((tc: any) => {
|
|
588
|
+
if (tc.type === "function" && tc.function) {
|
|
589
|
+
return renderToolUseHtml(tc.function.name, tc.function.arguments);
|
|
590
|
+
}
|
|
591
|
+
return "";
|
|
592
|
+
}).join("");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 3. Handle OpenAI legacy function_call
|
|
596
|
+
if (m.function_call) {
|
|
597
|
+
html += renderToolUseHtml(m.function_call.name, m.function_call.arguments);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 4. Handle OpenAI tool result (message role is "tool")
|
|
601
|
+
if (m.role === "tool" && !html.includes("chat-tool-result")) {
|
|
602
|
+
// If it was just a string, it rendered above. Wrap it in a tool result block instead.
|
|
603
|
+
html = renderToolResultHtml(m.tool_call_id || m.name || "unknown", m.content);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return html || `<div class="empty">Empty message</div>`;
|
|
607
|
+
}
|
|
608
|
+
|
|
418
609
|
function renderMessages(messages: any[] | undefined): string {
|
|
419
610
|
if (!messages?.length) return "";
|
|
420
|
-
return `<section class="block"><h4>Input Messages</h4>${messages.map(m => `<div class="msg ${escapeHtml(m?.role || "unknown")}"><div class="msg-head"><span class="msg-role">${escapeHtml(m?.role || "unknown")}</span></div><div class="msg-content">${
|
|
611
|
+
return `<section class="block"><h4>Input Messages</h4>${messages.map(m => `<div class="msg ${escapeHtml(m?.role || "unknown")}"><div class="msg-head"><span class="msg-role">${escapeHtml(m?.role || "unknown")}</span></div><div class="msg-content">${renderUniversalMessageContent(m)}</div></div>`).join("")}</section>`;
|
|
421
612
|
}
|
|
422
613
|
|
|
423
614
|
function eventBadgeClass(ev: any): string {
|
|
@@ -440,12 +631,12 @@ function renderEventCard(ev: any): string {
|
|
|
440
631
|
if (ev.errorMessage) {
|
|
441
632
|
sections.push(renderJsonBlock("Error Message", ev.errorMessage));
|
|
442
633
|
}
|
|
443
|
-
sections.push(
|
|
634
|
+
sections.push(`<section class="block"><h4>Output</h4><div class="msg-content">${renderUniversalMessageContent({ content: ev.outputContent })}</div></section>`);
|
|
444
635
|
if (ev.usage) sections.push(renderJsonBlock("Usage", ev.usage));
|
|
445
636
|
} else if (ev.type === "tool_start") {
|
|
446
|
-
sections.push(
|
|
637
|
+
sections.push(`<section class="block"><h4>Arguments</h4><div class="msg-content"><div class="chat-tool-use"><div class="chat-tool-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg> Execution Input</div><pre class="chat-tool-body">${escapeHtml(typeof ev.args === 'string' ? ev.args : JSON.stringify(ev.args, null, 2))}</pre></div></div></section>`);
|
|
447
638
|
} else if (ev.type === "tool_end") {
|
|
448
|
-
sections.push(
|
|
639
|
+
sections.push(`<section class="block"><h4>${ev.isError ? "Error Result" : "Result"}</h4><div class="msg-content"><div class="chat-tool-use" style="${ev.isError ? 'border-color: rgba(239,127,127,0.3);' : ''}"><div class="chat-tool-header" style="${ev.isError ? 'color: var(--err);' : 'color: var(--muted);'}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Execution Output</div><pre class="chat-tool-body">${escapeHtml(typeof ev.result === 'string' ? ev.result : JSON.stringify(ev.result, null, 2))}</pre></div></div></section>`);
|
|
449
640
|
} else {
|
|
450
641
|
sections.push(renderJsonBlock("Event", ev));
|
|
451
642
|
}
|
|
@@ -522,17 +713,19 @@ export function generateDashboardHtml(sessions: any[], options: any = {}): strin
|
|
|
522
713
|
const reversed = sessions.slice().reverse();
|
|
523
714
|
const totalCost = sessions.reduce((sum, s) => sum + Number(s.activity?.totals?.totalCostUsd || 0), 0);
|
|
524
715
|
const totalTokens = sessions.reduce((sum, s) => sum + Number(s.activity?.totals?.totalTokens || 0), 0);
|
|
525
|
-
const repositoryName = resolveRepoName(options);
|
|
526
716
|
const homeHref = options?.indexHref || "index.html";
|
|
527
717
|
const body = `<div class="container">
|
|
528
718
|
<section class="hero">
|
|
529
|
-
<div class="hero-
|
|
530
|
-
<
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
719
|
+
<div class="hero-split">
|
|
720
|
+
<a class="brand-link" href="${escapeHtml(homeHref)}">${wordmarkSvg()}</a>
|
|
721
|
+
<div class="hero-right">
|
|
722
|
+
<div class="hero-session">
|
|
723
|
+
<strong style="color: var(--text); font-weight: 500;">Global Index</strong>
|
|
724
|
+
<span style="color: var(--muted);">Machine-wide session evidence.</span>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="hero-meta">
|
|
727
|
+
<span class="hero-pill">${sessions.length} sessions</span>
|
|
728
|
+
</div>
|
|
536
729
|
</div>
|
|
537
730
|
</div>
|
|
538
731
|
<div class="metrics">
|
|
@@ -543,14 +736,14 @@ export function generateDashboardHtml(sessions: any[], options: any = {}): strin
|
|
|
543
736
|
</section>
|
|
544
737
|
<section class="panel">
|
|
545
738
|
<h2 class="section-title">Session Reports</h2>
|
|
546
|
-
${reversed.length ? `<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
|
|
739
|
+
${reversed.length ? `<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th>Project</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
|
|
547
740
|
${reversed.map((s, index) => {
|
|
548
|
-
const link = s.artifacts.html.startsWith(".notrace/") ? s.artifacts.html.substring(9) : s.artifacts.html;
|
|
741
|
+
const link = s.artifacts?.html ? (s.artifacts.html.startsWith(".notrace/") ? s.artifacts.html.substring(9) : s.artifacts.html) : "#";
|
|
549
742
|
const workflow = s.task?.workflow || "generic";
|
|
550
743
|
const workflowLabel = workflowDisplayName(workflow);
|
|
551
744
|
const tokens = Number(s.activity?.totals?.totalTokens || 0);
|
|
552
745
|
const cost = Number(s.activity?.totals?.totalCostUsd || 0);
|
|
553
|
-
return `<tr data-index="${reversed.length - index}" data-workflow="${escapeHtml(workflowLabel)}" data-started="${parseDate(s.startedAt)?.getTime() || 0}" data-tokens="${tokens}" data-cost="${cost}"><td class="index-cell">${reversed.length - index}</td><td><a class="session-link" href="${escapeHtml(link)}"><strong>${escapeHtml(String(s.sessionId).slice(0, 8))}</strong><span class="session-sub">${escapeHtml(String(s.sessionId))}</span></a></td><td><span class="workflow-pill ${workflowClassName(workflow)}">${escapeHtml(workflowLabel)}</span></td><td>${formatDateCell(s.startedAt)}</td><td>${escapeHtml(taskDisplay(s))}</td><td class="num-cell">${tokens.toLocaleString()}</td><td class="num-cell">${formatUsd(cost)}</td></tr>`;
|
|
746
|
+
return `<tr data-index="${reversed.length - index}" data-workflow="${escapeHtml(workflowLabel)}" data-started="${parseDate(s.startedAt)?.getTime() || 0}" data-tokens="${tokens}" data-cost="${cost}"><td class="index-cell">${reversed.length - index}</td><td><a class="session-link" href="${escapeHtml(link)}"><strong>${escapeHtml(String(s.sessionId).slice(0, 8))}</strong><span class="session-sub">${escapeHtml(String(s.sessionId))}</span></a></td><td><span class="hero-pill">${escapeHtml(s.repositoryName || "Unknown")}</span></td><td><span class="workflow-pill ${workflowClassName(workflow)}">${escapeHtml(workflowLabel)}</span></td><td>${formatDateCell(s.startedAt)}</td><td>${escapeHtml(taskDisplay(s))}</td><td class="num-cell">${tokens.toLocaleString()}</td><td class="num-cell">${formatUsd(cost)}</td></tr>`;
|
|
554
747
|
}).join("")}
|
|
555
748
|
</tbody></table>` : `<div class="empty">No sessions yet. Run Pi with notrace enabled. New reports appear here.</div>`}
|
|
556
749
|
</section>
|
|
@@ -568,12 +761,13 @@ export function generateHtmlReport(data: any): string {
|
|
|
568
761
|
<section class="hero">
|
|
569
762
|
<div class="hero-top">
|
|
570
763
|
<div>
|
|
571
|
-
<div class="brand"><a class="brand-link" href="${escapeHtml(indexHref)}" onclick="if (window.history.length > 1) { window.history.back(); return false; }">${wordmarkSvg()}</a><p class="subtitle">Session retrospective
|
|
764
|
+
<div class="brand"><a class="brand-link" href="${escapeHtml(indexHref)}" onclick="if (window.history.length > 1) { window.history.back(); return false; }">${wordmarkSvg()}</a><p class="subtitle session-subtitle"><span>Session retrospective</span><span class="session-id-chip"><span>${escapeHtml(data.traceId)}</span>${copyButton(String(data.traceId || ""), "session ID")}</span></p></div>
|
|
572
765
|
</div>
|
|
573
766
|
<div class="meta">
|
|
574
|
-
<span class="pill">${escapeHtml(
|
|
767
|
+
<span class="pill">${escapeHtml(resolveRepoName(data))}</span>
|
|
575
768
|
<span class="pill">Started ${formatDateLong(data.session?.startedAt)}</span>
|
|
576
|
-
<span class="pill">
|
|
769
|
+
<span class="pill">Mode: ${escapeHtml(data.captureMode || "full")}</span>
|
|
770
|
+
${exportButton(data)}
|
|
577
771
|
</div>
|
|
578
772
|
</div>
|
|
579
773
|
<div class="metrics">
|
|
@@ -612,5 +806,5 @@ export function generateHtmlReport(data: any): string {
|
|
|
612
806
|
<div class="footer-meta"><a href="https://opensource.org/licenses/MIT">MIT</a></div>
|
|
613
807
|
</footer>
|
|
614
808
|
</div>`;
|
|
615
|
-
return shell(`notrace - ${data.traceId}`, body);
|
|
809
|
+
return shell(`notrace - ${data.traceId}`, body, copyScript());
|
|
616
810
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raquezha/notrace",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Zero-dependency, local-first interactive HTML Trace Viewer for the Pi Coding Agent",
|
|
5
5
|
"main": "dist/notrace/index.js",
|
|
6
6
|
"types": "dist/notrace/index.d.ts",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"render:samples": "npm run build && node ./templates/render-samples.mjs",
|
|
11
|
+
"review": "node ./bin/notrace-review.mjs",
|
|
12
|
+
"compare": "node ./bin/notrace-compare.mjs",
|
|
11
13
|
"prepare": "npm run build"
|
|
12
14
|
},
|
|
13
15
|
"peerDependencies": {
|
|
@@ -31,5 +33,9 @@
|
|
|
31
33
|
"extensions": [
|
|
32
34
|
"extensions"
|
|
33
35
|
]
|
|
36
|
+
},
|
|
37
|
+
"bin": {
|
|
38
|
+
"notrace-review": "./bin/notrace-review.mjs",
|
|
39
|
+
"notrace-compare": "./bin/notrace-compare.mjs"
|
|
34
40
|
}
|
|
35
41
|
}
|
|
@@ -68,6 +68,44 @@
|
|
|
68
68
|
overflow: visible;
|
|
69
69
|
}
|
|
70
70
|
.subtitle { margin: 10px 0 0; color: var(--muted); }
|
|
71
|
+
.session-subtitle {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 10px;
|
|
75
|
+
flex-wrap: wrap;
|
|
76
|
+
}
|
|
77
|
+
.session-id-chip {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
max-width: 100%;
|
|
82
|
+
padding: 6px 8px 6px 10px;
|
|
83
|
+
border: 1px solid var(--border);
|
|
84
|
+
border-radius: 999px;
|
|
85
|
+
background: rgba(0,0,0,0.18);
|
|
86
|
+
color: var(--text);
|
|
87
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
88
|
+
font-size: 0.78rem;
|
|
89
|
+
word-break: break-all;
|
|
90
|
+
}
|
|
91
|
+
.copy-btn {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
width: 26px;
|
|
96
|
+
height: 26px;
|
|
97
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
98
|
+
border-radius: 999px;
|
|
99
|
+
background: rgba(255,255,255,0.04);
|
|
100
|
+
color: var(--muted);
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
103
|
+
}
|
|
104
|
+
.copy-btn:hover, .copy-btn.copied {
|
|
105
|
+
color: var(--text);
|
|
106
|
+
border-color: rgba(216,132,98,0.45);
|
|
107
|
+
background: var(--accent-soft);
|
|
108
|
+
}
|
|
71
109
|
.meta {
|
|
72
110
|
display: flex;
|
|
73
111
|
gap: 8px;
|
|
@@ -76,7 +114,7 @@
|
|
|
76
114
|
align-items: center;
|
|
77
115
|
margin-top: 16px;
|
|
78
116
|
}
|
|
79
|
-
.pill, .workflow-pill, .sort-btn {
|
|
117
|
+
.pill, .workflow-pill, .sort-btn, .export-btn {
|
|
80
118
|
display: inline-flex;
|
|
81
119
|
align-items: center;
|
|
82
120
|
gap: 6px;
|
|
@@ -89,7 +127,7 @@
|
|
|
89
127
|
font-size: 0.86rem;
|
|
90
128
|
font-weight: 600;
|
|
91
129
|
}
|
|
92
|
-
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
130
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
|
|
93
131
|
.metric-card {
|
|
94
132
|
background: var(--panel-strong);
|
|
95
133
|
border: 1px solid var(--border);
|
|
@@ -208,7 +246,40 @@
|
|
|
208
246
|
.msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
209
247
|
.msg.user .msg-role { color: #8ec5ff; }
|
|
210
248
|
.msg.assistant .msg-role { color: var(--accent); }
|
|
211
|
-
.msg-content { padding: 14px;
|
|
249
|
+
.msg-content { padding: 14px; }
|
|
250
|
+
.chat-text {
|
|
251
|
+
white-space: pre-wrap;
|
|
252
|
+
word-break: break-word;
|
|
253
|
+
font-size: 0.95rem;
|
|
254
|
+
line-height: 1.6;
|
|
255
|
+
margin-bottom: 12px;
|
|
256
|
+
}
|
|
257
|
+
.chat-text:last-child { margin-bottom: 0; }
|
|
258
|
+
.chat-tool-use {
|
|
259
|
+
background: rgba(0,0,0,0.3);
|
|
260
|
+
border: 1px solid var(--border);
|
|
261
|
+
border-radius: 8px;
|
|
262
|
+
overflow: hidden;
|
|
263
|
+
margin-bottom: 12px;
|
|
264
|
+
}
|
|
265
|
+
.chat-tool-use:last-child { margin-bottom: 0; }
|
|
266
|
+
.chat-tool-header {
|
|
267
|
+
background: rgba(255,255,255,0.04);
|
|
268
|
+
padding: 8px 12px;
|
|
269
|
+
font-size: 0.8rem;
|
|
270
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
271
|
+
color: #8ec5ff;
|
|
272
|
+
border-bottom: 1px solid var(--border);
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
gap: 8px;
|
|
276
|
+
}
|
|
277
|
+
.chat-tool-body {
|
|
278
|
+
padding: 12px;
|
|
279
|
+
margin: 0;
|
|
280
|
+
background: transparent;
|
|
281
|
+
border: none;
|
|
282
|
+
}
|
|
212
283
|
.footer-note {
|
|
213
284
|
margin-top: 22px;
|
|
214
285
|
color: var(--muted);
|
|
@@ -253,7 +324,15 @@
|
|
|
253
324
|
color: var(--text);
|
|
254
325
|
border-bottom-color: rgba(236,227,218,0.45);
|
|
255
326
|
}
|
|
256
|
-
|
|
327
|
+
.export-btn {
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
330
|
+
}
|
|
331
|
+
.export-btn:hover, .export-btn.copied {
|
|
332
|
+
color: var(--text);
|
|
333
|
+
border-color: rgba(216,132,98,0.45);
|
|
334
|
+
background: var(--accent-soft);
|
|
335
|
+
}
|
|
257
336
|
.container { padding: 20px 14px 48px; }
|
|
258
337
|
.hero { padding: 20px; }
|
|
259
338
|
.hero-top { grid-template-columns: 1fr; }
|
|
@@ -266,9 +345,8 @@
|
|
|
266
345
|
</head>
|
|
267
346
|
<body><div class="container">
|
|
268
347
|
<section class="hero">
|
|
269
|
-
<div class="hero-
|
|
270
|
-
<
|
|
271
|
-
<div class="brand"><a class="brand-link" href="index.html"><svg class="wordmark" viewBox="0 0 420 138" aria-label="notrace" role="img" xmlns="http://www.w3.org/2000/svg">
|
|
348
|
+
<div class="hero-split">
|
|
349
|
+
<a class="brand-link" href="index.html"><svg class="wordmark" viewBox="0 0 420 138" aria-label="notrace" role="img" xmlns="http://www.w3.org/2000/svg">
|
|
272
350
|
<defs>
|
|
273
351
|
<linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
274
352
|
<stop offset="0%" stop-color="#E2754A"/>
|
|
@@ -285,11 +363,15 @@
|
|
|
285
363
|
</g>
|
|
286
364
|
<text x="0" y="114" fill="#ECE3DA" style="fill:#ECE3DA" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">no</text>
|
|
287
365
|
<text x="82" y="114" fill="#d88462" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">trace</text>
|
|
288
|
-
</svg></a
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
366
|
+
</svg></a>
|
|
367
|
+
<div class="hero-right">
|
|
368
|
+
<div class="hero-session">
|
|
369
|
+
<strong style="color: var(--text); font-weight: 500;">Global Index</strong>
|
|
370
|
+
<span style="color: var(--muted);">Machine-wide session evidence.</span>
|
|
371
|
+
</div>
|
|
372
|
+
<div class="hero-meta">
|
|
373
|
+
<span class="hero-pill">3 sessions</span>
|
|
374
|
+
</div>
|
|
293
375
|
</div>
|
|
294
376
|
</div>
|
|
295
377
|
<div class="metrics">
|
|
@@ -300,8 +382,8 @@
|
|
|
300
382
|
</section>
|
|
301
383
|
<section class="panel">
|
|
302
384
|
<h2 class="section-title">Session Reports</h2>
|
|
303
|
-
<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
|
|
304
|
-
<tr data-index="3" data-workflow="Generic" data-started="1781691240000" data-tokens="5910" data-cost="0.0334"><td class="index-cell">3</td><td><a class="session-link" href="sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1002-76ee-b353-000000000003</span></a></td><td><span class="workflow-pill workflow-generic">Generic</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>18:14</span></div></td><td>General session</td><td class="num-cell">5,910</td><td class="num-cell">$0.03340</td></tr><tr data-index="2" data-workflow="Research" data-started="1781689020000" data-tokens="5754" data-cost="0.0267"><td class="index-cell">2</td><td><a class="session-link" href="sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1001-76ee-b353-000000000002</span></a></td><td><span class="workflow-pill workflow-research">Research</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:37</span></div></td><td>Branch topic-02</td><td class="num-cell">5,754</td><td class="num-cell">$0.02670</td></tr><tr data-index="1" data-workflow="RPIV" data-started="1781686800000" data-tokens="3510" data-cost="0.0204"><td class="index-cell">1</td><td><a class="session-link" href="sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1000-76ee-b353-000000000001</span></a></td><td><span class="workflow-pill workflow-rpiv">RPIV</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:00</span></div></td><td>NR-101</td><td class="num-cell">3,510</td><td class="num-cell">$0.02040</td></tr>
|
|
385
|
+
<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th>Project</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
|
|
386
|
+
<tr data-index="3" data-workflow="Generic" data-started="1781691240000" data-tokens="5910" data-cost="0.0334"><td class="index-cell">3</td><td><a class="session-link" href="sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1002-76ee-b353-000000000003</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-generic">Generic</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>18:14</span></div></td><td>General session</td><td class="num-cell">5,910</td><td class="num-cell">$0.03340</td></tr><tr data-index="2" data-workflow="Research" data-started="1781689020000" data-tokens="5754" data-cost="0.0267"><td class="index-cell">2</td><td><a class="session-link" href="sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1001-76ee-b353-000000000002</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-research">Research</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:37</span></div></td><td>Branch topic-02</td><td class="num-cell">5,754</td><td class="num-cell">$0.02670</td></tr><tr data-index="1" data-workflow="RPIV" data-started="1781686800000" data-tokens="3510" data-cost="0.0204"><td class="index-cell">1</td><td><a class="session-link" href="sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1000-76ee-b353-000000000001</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-rpiv">RPIV</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:00</span></div></td><td>NR-101</td><td class="num-cell">3,510</td><td class="num-cell">$0.02040</td></tr>
|
|
305
387
|
</tbody></table>
|
|
306
388
|
</section>
|
|
307
389
|
<footer class="footer-note minimal">notrace • raquezha 2026</footer>
|