576 lines
48 KiB
HTML
576 lines
48 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>claude usage</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
/* anthropic / claude-code palette */
|
||
--ivory: #FAF9F5;
|
||
--term-bg: #1a1918;
|
||
--term-fg: #d1cfc5;
|
||
--titlebar: #252321;
|
||
--outline: rgba(255,255,255,0.08);
|
||
--hover: rgba(255,255,255,0.035);
|
||
--clay: #D97757;
|
||
--dim: rgb(136,136,136);
|
||
--subtle: rgb(80,80,80);
|
||
--green: rgb(78,186,101);
|
||
--red: rgb(255,107,128);
|
||
--blue: rgb(177,185,249);
|
||
--yellow: rgb(255,193,7);
|
||
--mono: 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Monaco, monospace;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html { background: var(--ivory); }
|
||
body {
|
||
margin: 0; padding: 48px 24px 80px;
|
||
font: 13px/1.55 var(--mono);
|
||
font-variant-numeric: tabular-nums;
|
||
color: var(--term-fg);
|
||
}
|
||
|
||
/* ——— terminal window chrome ——— */
|
||
.term {
|
||
max-width: 1180px; margin: 0 auto;
|
||
background: var(--term-bg);
|
||
border-radius: 8px;
|
||
outline: 1px solid var(--outline);
|
||
box-shadow: 0 20px 60px rgba(20,20,19,0.22);
|
||
}
|
||
.titlebar {
|
||
background: var(--titlebar);
|
||
border-radius: 8px 8px 0 0;
|
||
border-bottom: 1px solid var(--outline);
|
||
padding: 11px 14px;
|
||
display: flex; align-items: center; gap: 7px;
|
||
}
|
||
.titlebar .dot { width: 11px; height: 11px; border-radius: 50%; background: #3a3836; }
|
||
.titlebar .path { margin-left: 14px; color: var(--dim); font-size: 11px; }
|
||
.term-body { padding: 22px 30px 30px; }
|
||
|
||
/* ——— command + hero ——— */
|
||
.cmd { color: var(--dim); margin-bottom: 6px; }
|
||
.cmd .prompt { color: var(--clay); }
|
||
.cmd .flag { color: var(--blue); }
|
||
#meta-line { color: var(--subtle); font-size: 11px; }
|
||
|
||
#hero { margin: 14px 0 6px; }
|
||
#hero-total { font-size: 56px; font-weight: 700; line-height: 1; }
|
||
#hero-total .unit { color: var(--clay); }
|
||
#hero-total .label { font-size: 18px; font-weight: 400; color: var(--dim); margin-left: 8px; }
|
||
#hero-split { color: var(--dim); margin-top: 8px; }
|
||
#hero-split b { color: var(--term-fg); font-weight: 500; }
|
||
#hero-split .ok { color: var(--green); }
|
||
#hero-split .warn { color: var(--yellow); }
|
||
|
||
/* ——— sections ——— */
|
||
section { margin-top: 26px; }
|
||
.hr { color: var(--subtle); overflow: hidden; white-space: nowrap;
|
||
user-select: none; margin-bottom: 8px; }
|
||
.hr::after { content: '────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────'; }
|
||
h2 { all: unset; display: block; color: var(--clay); font-weight: 500; }
|
||
h2::before { content: '▸ '; }
|
||
h2 .hint { color: var(--subtle); font-size: 11px; font-weight: 400; margin-left: 10px; }
|
||
.section-body { margin-top: 10px; }
|
||
|
||
/* ——— overall stat grid ——— */
|
||
#overall-grid { display: grid; gap: 4px 28px;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
|
||
.stat { padding: 4px 0; }
|
||
.stat .label { font-size: 11px; color: var(--dim); }
|
||
.stat .val { font-size: 20px; font-weight: 500; }
|
||
.stat .detail { font-size: 11px; color: var(--subtle); }
|
||
|
||
/* ——— takeaways ——— */
|
||
.take { display: grid; grid-template-columns: 9ch 1fr; gap: 18px;
|
||
padding: 6px 0; align-items: baseline; }
|
||
.take .fig { text-align: right; font-weight: 700; font-size: 15px; }
|
||
.take .txt { color: var(--dim); }
|
||
.take .txt b { color: var(--term-fg); font-weight: 500; }
|
||
.take.bad .fig { color: var(--red); }
|
||
.take.good .fig { color: var(--green); }
|
||
.take.info .fig { color: var(--blue); }
|
||
|
||
/* ——— callouts (recommendations) ——— */
|
||
.callout { padding: 6px 0 6px 14px; border-left: 2px solid var(--subtle);
|
||
color: var(--dim); margin: 6px 0; }
|
||
.callout b, .callout code { color: var(--term-fg); }
|
||
|
||
/* ——— day pills + session gantt ——— */
|
||
.days { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
|
||
.dpill { flex: 1; min-width: 84px; max-width: 140px; background: none;
|
||
border: 1px solid var(--subtle); border-radius: 4px;
|
||
padding: 9px 6px; font: inherit; color: var(--dim);
|
||
cursor: pointer; text-align: center; }
|
||
.dpill:hover { border-color: var(--dim); background: var(--hover); }
|
||
.dpill .dow { font-size: 10px; color: var(--subtle); display: block; }
|
||
.dpill .date { font-size: 11px; color: var(--term-fg); font-weight: 500;
|
||
display: block; margin: 2px 0 4px; }
|
||
.dpill .pct { font-size: 16px; font-weight: 700; color: var(--term-fg); display: block; }
|
||
.dpill .ns { font-size: 10px; color: var(--subtle); display: block; margin-top: 2px; }
|
||
.dpill.heaviest .pct { color: var(--clay); }
|
||
.dpill.sel { border-color: var(--clay); background: rgba(217,119,87,0.10); }
|
||
.gantt-hd { display: flex; justify-content: space-between; align-items: baseline;
|
||
margin-bottom: 6px; }
|
||
.gantt-hd .day { color: var(--term-fg); font-weight: 500; }
|
||
.gantt-hd .stats { font-size: 11px; color: var(--dim); }
|
||
.gantt-hd .stats b { color: var(--clay); }
|
||
.gantt { position: relative; border-top: 1px solid var(--outline);
|
||
border-bottom: 1px solid var(--outline); min-height: 32px; }
|
||
.lane { position: relative; height: 16px;
|
||
border-bottom: 1px dashed rgba(255,255,255,0.04); }
|
||
.seg { position: absolute; top: 2px; height: 12px; border-radius: 2px;
|
||
opacity: .85; cursor: crosshair; }
|
||
.seg:hover { opacity: 1; outline: 1px solid var(--term-fg); z-index: 2; }
|
||
.gantt-rule { position: absolute; top: 0; bottom: 0; width: 0;
|
||
border-left: 1px dashed var(--subtle); opacity: .4;
|
||
pointer-events: none; }
|
||
.gantt-axis { display: flex; justify-content: space-between;
|
||
font-size: 10px; color: var(--subtle); padding: 4px 0; }
|
||
.gantt-leg { font-size: 10px; color: var(--subtle); margin-top: 8px;
|
||
display: flex; gap: 14px; flex-wrap: wrap; }
|
||
.gantt-leg .sw { display: inline-block; width: 14px; height: 10px;
|
||
border-radius: 2px; vertical-align: middle; margin-right: 4px; }
|
||
|
||
/* ——— block-char bars ——— */
|
||
.bar { display: grid; grid-template-columns: 26ch 1fr 8ch; gap: 14px;
|
||
padding: 2px 0; align-items: center; }
|
||
.bar .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.bar .blocks { color: var(--clay); white-space: pre; overflow: hidden; }
|
||
.bar .blocks .empty { color: var(--subtle); }
|
||
.bar .pct { text-align: right; color: var(--dim); }
|
||
.bar:hover .name { color: var(--clay); }
|
||
|
||
/* ——— drill-down lists (top prompts, cache breaks) ——— */
|
||
.drill details { border-top: 1px solid var(--outline); }
|
||
.drill details:last-of-type { border-bottom: 1px solid var(--outline); }
|
||
.drill summary { list-style: none; cursor: pointer;
|
||
display: grid; grid-template-columns: 8ch 1fr; gap: 16px;
|
||
padding: 9px 4px; align-items: baseline; }
|
||
.drill summary::-webkit-details-marker { display: none; }
|
||
.drill summary:hover { background: var(--hover); }
|
||
.drill .amt { font-weight: 700; text-align: right; color: var(--clay); }
|
||
.drill .desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.drill .meta { grid-column: 2; font-size: 11px; color: var(--subtle); }
|
||
.drill details[open] summary .desc { white-space: normal; }
|
||
.drill .body { padding: 4px 4px 14px calc(8ch + 20px); font-size: 12px; color: var(--dim); }
|
||
|
||
/* ——— transcript context (±2 user msgs) — light inset for legibility ——— */
|
||
.ctx { margin: 8px 0 12px; padding: 10px 14px;
|
||
background: #F0EEE6; color: #1a1918;
|
||
border-radius: 6px; font-size: 12px; }
|
||
.ctx-msg { padding: 4px 0; white-space: pre-wrap; }
|
||
.ctx-msg .who { color: #87867F; font-size: 11px; }
|
||
.ctx-msg .ts { color: #87867F; font-size: 10px; margin-left: 8px; }
|
||
.ctx-msg.here { margin: 2px -14px; padding: 6px 11px 6px 14px;
|
||
border-left: 3px solid var(--clay);
|
||
background: rgba(217,119,87,0.10); }
|
||
.ctx-msg.here .who { color: var(--clay); font-weight: 500; }
|
||
.ctx-gap { color: #87867F; font-size: 11px; padding: 3px 0 3px 7ch; }
|
||
.ctx-gap::before { content: '⟨ '; }
|
||
.ctx-gap::after { content: ' ⟩'; }
|
||
.ctx-break { margin: 2px -14px; padding: 8px 11px 8px 14px;
|
||
border-left: 3px solid #BD5E6D;
|
||
background: rgba(189,94,109,0.12); color: #A63244; }
|
||
.ctx-break b { color: #1a1918; margin-right: 6px; }
|
||
|
||
.more-btn { display: block; width: 100%; margin-top: 10px; padding: 9px;
|
||
background: none; border: 1px dashed var(--subtle); cursor: pointer;
|
||
font: 500 11px/1 var(--mono); letter-spacing: 0.06em;
|
||
color: var(--dim); }
|
||
.more-btn:hover { border-color: var(--dim); color: var(--term-fg); }
|
||
|
||
/* ——— tables ——— */
|
||
.scroll { max-height: 440px; overflow: auto;
|
||
border-top: 1px solid var(--outline); border-bottom: 1px solid var(--outline); }
|
||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||
th, td { text-align: left; padding: 6px 10px; }
|
||
td { border-top: 1px solid rgba(255,255,255,0.04); }
|
||
th { position: sticky; top: 0; background: var(--term-bg); z-index: 1;
|
||
font-weight: 500; font-size: 11px; color: var(--subtle);
|
||
cursor: pointer; user-select: none;
|
||
border-bottom: 1px solid var(--outline); }
|
||
th:hover { color: var(--dim); }
|
||
th.sorted { color: var(--clay); }
|
||
th.sorted::after { content: ' ↓'; }
|
||
th.sorted.asc::after { content: ' ↑'; }
|
||
td.num, th.num { text-align: right; }
|
||
tbody tr:hover td { background: var(--hover); }
|
||
|
||
footer { margin-top: 28px; color: var(--subtle); font-size: 11px;
|
||
display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
||
|
||
code { color: var(--blue); }
|
||
a { color: var(--clay); }
|
||
::selection { background: var(--clay); color: var(--term-bg); }
|
||
|
||
@media (max-width: 760px) {
|
||
body { padding: 20px 12px 48px; }
|
||
.term-body { padding: 16px 16px 24px; }
|
||
#hero-total { font-size: 40px; }
|
||
.bar { grid-template-columns: 14ch 1fr 7ch; gap: 8px; }
|
||
.drill summary { grid-template-columns: 6ch 1fr; }
|
||
.drill .body { padding-left: 12px; }
|
||
.take { grid-template-columns: 7ch 1fr; gap: 12px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="term">
|
||
<div class="titlebar">
|
||
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
||
<span class="path" id="title-path">~/.claude — session-report</span>
|
||
</div>
|
||
<div class="term-body">
|
||
|
||
<div class="cmd"><span class="prompt">></span> claude usage <span id="cmd-flags"></span></div>
|
||
<div id="meta-line">loading…</div>
|
||
|
||
<div id="hero">
|
||
<div id="hero-total">—</div>
|
||
<div id="hero-split"></div>
|
||
</div>
|
||
|
||
<!-- ====================================================================
|
||
FINDINGS — agent fills this. 3–5 one-line takeaways. Use .bad for
|
||
waste/anomalies, .good for healthy signals, .info for neutral.
|
||
==================================================================== -->
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>findings</h2>
|
||
<div class="section-body" id="takeaways">
|
||
<!-- AGENT: anomalies -->
|
||
<div class="take bad"><div class="fig">26.7%</div><div class="txt"><b>nuxt-wiki "위키 작성" 프롬프트</b>가 전체 토큰의 26.7%를 단일 턴에서 소비했습니다 (1.97M / 7.39M) — API 호출 19회, 서브에이전트 3회 포함</div></div>
|
||
<div class="take bad"><div class="fig">24.4%</div><div class="txt"><b>gameservice-fe-agent "단계별 스킬 만들기" 프롬프트</b>가 전체 토큰의 24.4%를 단일 턴에서 소비했습니다 (1.80M / 7.39M) — API 호출 19회, 서브에이전트 없음</div></div>
|
||
<div class="take bad"><div class="fig">60.4%</div><div class="txt"><b>Explore 서브에이전트</b> 캐시 히트율이 60.4%에 불과합니다 — 목표치 85% 대비 크게 낮아, 서브에이전트 호출마다 콜드 캐시로 비용이 증가했습니다</div></div>
|
||
<div class="take good"><div class="fig">89.2%</div><div class="txt"><b>전체 캐시 히트율</b>이 89.2%로 양호합니다 — 목표치 85%를 상회하며, 100k 초과 캐시 브레이크가 0건입니다</div></div>
|
||
<div class="take info"><div class="fig">6.6%</div><div class="txt"><b>session-report:session-report 스킬</b>이 2회 실행으로 전체 토큰의 6.6% (486k / 7.39M)를 소비했습니다 — 대부분 컨텍스트 재로딩 캐시 읽기</div></div>
|
||
<!-- /AGENT -->
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ====================================================================
|
||
Everything below renders automatically from #report-data.
|
||
==================================================================== -->
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>summary</h2>
|
||
<div class="section-body" id="overall-grid"></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>tokens by project<span class="hint">share of total</span></h2>
|
||
<div class="section-body" id="project-bars"></div>
|
||
</section>
|
||
|
||
<section id="timeline-section">
|
||
<div class="hr"></div>
|
||
<h2>session timeline by day<span class="hint">click a day · ←/→ to navigate</span></h2>
|
||
<div class="section-body">
|
||
<div class="days" id="day-pills"></div>
|
||
<div class="gantt-hd">
|
||
<span class="day" id="g-day">—</span>
|
||
<span class="stats" id="g-stats"></span>
|
||
</div>
|
||
<div class="gantt-axis"><span>00:00</span><span>06:00</span><span>12:00</span><span>18:00</span><span>24:00</span></div>
|
||
<div class="gantt" id="gantt"></div>
|
||
<div class="gantt-leg" id="gantt-leg"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>most expensive prompts<span class="hint">click to expand context</span></h2>
|
||
<div class="section-body drill" id="top-prompts"></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>cache breaks<span class="hint">>100k uncached · click for context</span></h2>
|
||
<div class="section-body drill" id="cache-breaks"></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>projects</h2>
|
||
<div class="section-body scroll"><table id="tbl-projects"></table></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>subagent types</h2>
|
||
<div class="section-body scroll"><table id="tbl-subagents"></table></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>skills & slash commands</h2>
|
||
<div class="section-body scroll"><table id="tbl-skills"></table></div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="hr"></div>
|
||
<h2>recommendations</h2>
|
||
<div class="section-body">
|
||
<!-- AGENT: optimizations -->
|
||
<div class="callout">"nuxt 위키 작성" (26.7%)과 "단계별 스킬 만들기" (24.4%) 두 프롬프트가 주간 전체 토큰의 절반 이상을 소비했습니다. 대규모 위키/스킬 생성 작업은 범위를 나눠 여러 프롬프트로 분할하면 턴당 컨텍스트 누적을 줄일 수 있습니다.</div>
|
||
<div class="callout"><b>Explore 서브에이전트</b>의 캐시 히트율이 60.4%로 목표치(85%)를 크게 밑돕니다. 서브에이전트는 부모 세션의 캐시를 상속받지 않으므로, 범위를 좁게 유지하거나 세션 초반에 호출하는 것이 유리합니다. 간단한 검색은 서브에이전트 대신 메인 스레드에서 직접 처리하는 방법도 고려하세요.</div>
|
||
<div class="callout">nuxt-wiki 프로젝트가 단일 세션으로 82.7시간(실제 활성 0.9시간)을 유지했습니다. 세션이 길어지면 오래된 컨텍스트가 계속 캐시에 쌓입니다. 수 시간에 걸친 작업 세션에서는 <code>/compact</code>를 주기적으로 실행해 불필요한 컨텍스트를 정리하면 캐시 효율을 높일 수 있습니다.</div>
|
||
<!-- /AGENT -->
|
||
</div>
|
||
</section>
|
||
|
||
<footer>
|
||
<span id="foot-gen"></span>
|
||
<span id="foot-stats"></span>
|
||
</footer>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========================================================================
|
||
DATA — agent replaces the {} below with the full --json output.
|
||
======================================================================== -->
|
||
<script id="report-data" type="application/json">{"root":"/Users/gil/.claude/projects","generated_at":"2026-05-16T01:40:29.518Z","overall":{"sessions":8,"api_calls":124,"input_tokens":{"uncached":13270,"cache_create":762618,"cache_read":6405597,"total":7181485,"pct_cached":89.2},"output_tokens":208707,"human_messages":38,"hours":{"wall_clock":83.6,"active":1.5},"cache_breaks_over_100k":0,"subagent":{"calls":4,"total_tokens":269182,"avg_tokens_per_call":67296},"skill_invocations":{"compact":1,"login":1,"model":1,"clear":1,"plugin":5,"session-report:session-report":2,"exit":1},"span":{"from":"2026-05-12T14:14:40.027Z","to":"2026-05-16T01:40:24.095Z"}},"cache_breaks":[],"by_project":{"-Users-gil--openclaw-workspace-nuxt-wiki":{"sessions":1,"api_calls":63,"input_tokens":{"uncached":13184,"cache_create":330480,"cache_read":3550576,"total":3894240,"pct_cached":91.2},"output_tokens":110900,"human_messages":13,"hours":{"wall_clock":82.7,"active":0.9},"cache_breaks_over_100k":0,"subagent":{"calls":4,"total_tokens":269182,"avg_tokens_per_call":67296},"skill_invocations":{"compact":1,"login":1,"model":1},"span":{"from":"2026-05-12T15:04:06.239Z","to":"2026-05-16T01:39:00.703Z"}},"-Users-gil-Downloads-gameservice-fe-agent-2":{"sessions":7,"api_calls":61,"input_tokens":{"uncached":86,"cache_create":432138,"cache_read":2855021,"total":3287245,"pct_cached":86.9},"output_tokens":97807,"human_messages":25,"hours":{"wall_clock":0.9,"active":0.6},"cache_breaks_over_100k":0,"subagent":{"calls":0,"total_tokens":0,"avg_tokens_per_call":0},"skill_invocations":{"clear":1,"plugin":5,"session-report:session-report":2,"exit":1},"span":{"from":"2026-05-12T14:14:40.027Z","to":"2026-05-16T01:40:24.095Z"}}},"by_subagent_type":{"Explore":{"sessions":1,"api_calls":9,"input_tokens":{"uncached":38,"cache_create":90391,"cache_read":138094,"total":228523,"pct_cached":60.4},"output_tokens":40659,"human_messages":0,"hours":{"wall_clock":0.1,"active":0.1},"cache_breaks_over_100k":0,"subagent":{"calls":4,"total_tokens":269182,"avg_tokens_per_call":67296},"skill_invocations":{},"span":{"from":"2026-05-12T15:08:45.560Z","to":"2026-05-12T15:27:53.262Z"}}},"by_skill":{"session-report:session-report":{"sessions":0,"api_calls":15,"input_tokens":{"uncached":17,"cache_create":55795,"cache_read":425175,"total":480987,"pct_cached":88.4},"output_tokens":4940,"human_messages":0,"hours":{"wall_clock":0,"active":0},"cache_breaks_over_100k":0,"subagent":{"calls":0,"total_tokens":0,"avg_tokens_per_call":0},"skill_invocations":{},"span":null}},"top_prompts":[{"ts":"2026-05-12T15:24:51.660Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"'/Users/gil/.openclaw/workspace/nuxt-wiki/reference' nuxt정보 위키도 작성해줘","api_calls":19,"subagent_calls":3,"total_tokens":1971429,"input":{"uncached":9422,"cache_create":144138,"cache_read":1755812},"output":62057,"context":[{"text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","ts":"2026-05-12T15:13:54.781Z","calls":7,"here":false},{"text":"정리해줘","ts":"2026-05-12T15:18:28.590Z","calls":8,"here":false},{"text":"'/Users/gil/.openclaw/workspace/nuxt-wiki/reference' nuxt정보 위키도 작성해줘","ts":"2026-05-12T15:24:51.660Z","calls":19,"here":true},{"text":"/compact","ts":"2026-05-16T01:30:11.999Z","calls":0,"here":false},{"text":"nuxt 관련 위키를 토대로 개발 지침 생성해줘","ts":"2026-05-16T01:30:43.054Z","calls":1,"here":false}]},{"ts":"2026-05-12T14:46:32.501Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"183bbac6-1874-4c04-a33a-3009e4e124aa","text":"단계별 스킬에대한 스킬만들어주고 단계별 Skill 테이블에 명령 프롬프트 예시도 작성해줘","api_calls":19,"subagent_calls":0,"total_tokens":1803022,"input":{"uncached":29,"cache_create":279242,"cache_read":1482416},"output":41335,"context":[{"text":"b로 바꿔줘","ts":"2026-05-12T14:43:44.144Z","calls":9,"here":false},{"text":"단계별 Skill 설명 추가해줘","ts":"2026-05-12T14:45:42.573Z","calls":0,"here":false},{"text":"단계별 스킬에대한 스킬만들어주고 단계별 Skill 테이블에 명령 프롬프트 예시도 작성해줘","ts":"2026-05-12T14:46:32.501Z","calls":19,"here":true}]},{"ts":"2026-05-12T15:18:28.590Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"정리해줘","api_calls":8,"subagent_calls":0,"total_tokens":737235,"input":{"uncached":1437,"cache_create":25183,"cache_read":698784},"output":11831,"context":[{"text":"현재 raw바깥에 폴더들은 raw로 넣으면돼?","ts":"2026-05-12T15:08:28.986Z","calls":14,"here":false},{"text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","ts":"2026-05-12T15:13:54.781Z","calls":7,"here":false},{"text":"정리해줘","ts":"2026-05-12T15:18:28.590Z","calls":8,"here":true},{"text":"'/Users/gil/.openclaw/workspace/nuxt-wiki/reference' nuxt정보 위키도 작성해줘","ts":"2026-05-12T15:24:51.660Z","calls":19,"here":false},{"text":"/compact","ts":"2026-05-16T01:30:11.999Z","calls":0,"here":false}]},{"ts":"2026-05-12T15:08:28.986Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"현재 raw바깥에 폴더들은 raw로 넣으면돼?","api_calls":14,"subagent_calls":1,"total_tokens":492766,"input":{"uncached":29,"cache_create":77960,"cache_read":404225},"output":10552,"context":[{"text":"나만의 Nuxt 개발 지식/노하우 위키를 만들어줘. 아래 Karpathy의 LLM Wiki 아이디어를 참고해서 구현해: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f 이 프로젝트는 내가 Nuxt 개발을 하면서 쌓이는 지식을 축적하는 나만의 나무위키야. 자료를 넣으면 네가 읽고 정리해서 위키로 만들어주고, 나는 필요할 때 질문하면 돼. 나는 Nuxt(V…","ts":"2026-05-12T15:04:06.239Z","calls":9,"here":false},{"text":"현재 raw바깥에 폴더들은 raw로 넣으면돼?","ts":"2026-05-12T15:08:28.986Z","calls":14,"here":true},{"text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","ts":"2026-05-12T15:13:54.781Z","calls":7,"here":false},{"text":"정리해줘","ts":"2026-05-12T15:18:28.590Z","calls":8,"here":false}]},{"ts":"2026-05-12T14:43:44.144Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"183bbac6-1874-4c04-a33a-3009e4e124aa","text":"b로 바꿔줘","api_calls":9,"subagent_calls":0,"total_tokens":470269,"input":{"uncached":11,"cache_create":7123,"cache_read":458633},"output":4502,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","ts":"2026-05-12T14:38:49.822Z","calls":11,"here":false},{"text":"개발 nuxt-component nuxt-docs nuxt-api-state unit-test-generator docs-generator 개발단계 prefix 추천해줘","ts":"2026-05-12T14:42:53.731Z","calls":1,"here":false},{"text":"b로 바꿔줘","ts":"2026-05-12T14:43:44.144Z","calls":9,"here":true},{"text":"단계별 Skill 설명 추가해줘","ts":"2026-05-12T14:45:42.573Z","calls":0,"here":false},{"text":"단계별 스킬에대한 스킬만들어주고 단계별 Skill 테이블에 명령 프롬프트 예시도 작성해줘","ts":"2026-05-12T14:46:32.501Z","calls":19,"here":false}]},{"ts":"2026-05-12T14:47:49.031Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"b36d34e5-069a-45f0-8e61-0d1c4e768768","text":"/session-report:session-report","api_calls":14,"subagent_calls":0,"total_tokens":461307,"input":{"uncached":15,"cache_create":42235,"cache_read":414344},"output":4713,"context":[{"text":"/session-report:session-report","ts":"2026-05-12T14:47:49.031Z","calls":14,"here":true}]},{"ts":"2026-05-12T15:13:54.781Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","api_calls":7,"subagent_calls":0,"total_tokens":409813,"input":{"uncached":2125,"cache_create":30582,"cache_read":362830},"output":14276,"context":[{"text":"나만의 Nuxt 개발 지식/노하우 위키를 만들어줘. 아래 Karpathy의 LLM Wiki 아이디어를 참고해서 구현해: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f 이 프로젝트는 내가 Nuxt 개발을 하면서 쌓이는 지식을 축적하는 나만의 나무위키야. 자료를 넣으면 네가 읽고 정리해서 위키로 만들어주고, 나는 필요할 때 질문하면 돼. 나는 Nuxt(V…","ts":"2026-05-12T15:04:06.239Z","calls":9,"here":false},{"text":"현재 raw바깥에 폴더들은 raw로 넣으면돼?","ts":"2026-05-12T15:08:28.986Z","calls":14,"here":false},{"text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","ts":"2026-05-12T15:13:54.781Z","calls":7,"here":true},{"text":"정리해줘","ts":"2026-05-12T15:18:28.590Z","calls":8,"here":false},{"text":"'/Users/gil/.openclaw/workspace/nuxt-wiki/reference' nuxt정보 위키도 작성해줘","ts":"2026-05-12T15:24:51.660Z","calls":19,"here":false}]},{"ts":"2026-05-12T14:38:49.822Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"183bbac6-1874-4c04-a33a-3009e4e124aa","text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","api_calls":11,"subagent_calls":0,"total_tokens":407317,"input":{"uncached":13,"cache_create":35109,"cache_read":359684},"output":12511,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","ts":"2026-05-12T14:38:49.822Z","calls":11,"here":true},{"text":"개발 nuxt-component nuxt-docs nuxt-api-state unit-test-generator docs-generator 개발단계 prefix 추천해줘","ts":"2026-05-12T14:42:53.731Z","calls":1,"here":false},{"text":"b로 바꿔줘","ts":"2026-05-12T14:43:44.144Z","calls":9,"here":false}]},{"ts":"2026-05-12T15:04:06.239Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"나만의 Nuxt 개발 지식/노하우 위키를 만들어줘. 아래 Karpathy의 LLM Wiki 아이디어를 참고해서 구현해: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f 이 프로젝트는 내가 Nuxt 개발을 하면서 쌓이는 지식을 축적하는 나만의 나무위키야. 자료를 넣으면 네가 읽고 정리해서 위키로 만들어주고, 나는 필요할 때 질문하면 돼. 나는 Nuxt(V…","api_calls":9,"subagent_calls":0,"total_tokens":201301,"input":{"uncached":164,"cache_create":13556,"cache_read":182091},"output":5490,"context":[{"text":"나만의 Nuxt 개발 지식/노하우 위키를 만들어줘. 아래 Karpathy의 LLM Wiki 아이디어를 참고해서 구현해: https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f 이 프로젝트는 내가 Nuxt 개발을 하면서 쌓이는 지식을 축적하는 나만의 나무위키야. 자료를 넣으면 네가 읽고 정리해서 위키로 만들어주고, 나는 필요할 때 질문하면 돼. 나는 Nuxt(V…","ts":"2026-05-12T15:04:06.239Z","calls":9,"here":true},{"text":"현재 raw바깥에 폴더들은 raw로 넣으면돼?","ts":"2026-05-12T15:08:28.986Z","calls":14,"here":false},{"text":"웹상에서 클로코드 업무 자동화 관련 자료 찾아서 raw폴더에 넣어줘","ts":"2026-05-12T15:13:54.781Z","calls":7,"here":false}]},{"ts":"2026-05-16T01:31:28.037Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"nuxt 관련 위키를 토대로 개발 지침 생성해줘","api_calls":5,"subagent_calls":0,"total_tokens":192596,"input":{"uncached":7,"cache_create":39061,"cache_read":146834},"output":6694,"context":[{"text":"/model","ts":"2026-05-16T01:31:25.368Z","calls":0,"here":false},{"text":"Set model to u001b[1mSonnet 4.6 (default)u001b[22m","ts":"2026-05-16T01:31:25.368Z","calls":0,"here":false},{"text":"nuxt 관련 위키를 토대로 개발 지침 생성해줘","ts":"2026-05-16T01:31:28.037Z","calls":5,"here":true}]},{"ts":"2026-05-12T14:14:40.027Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"f378c1ec-3e63-4b5b-bd6f-bd4b617e8ae5","text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/fe-ai-workflow.html' fe-ai-workflow.html fe-ai-reference-flow 2.html fe-ai-rules.html WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md fe-ai-reference-flow.html 단계별 스킬을 …","api_calls":2,"subagent_calls":0,"total_tokens":88813,"input":{"uncached":4,"cache_create":20537,"cache_read":36133},"output":32139,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/fe-ai-workflow.html' fe-ai-workflow.html fe-ai-reference-flow 2.html fe-ai-rules.html WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md fe-ai-reference-flow.html 단계별 스킬을 …","ts":"2026-05-12T14:14:40.027Z","calls":2,"here":true}]},{"ts":"2026-05-12T14:42:53.731Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"183bbac6-1874-4c04-a33a-3009e4e124aa","text":"개발 nuxt-component nuxt-docs nuxt-api-state unit-test-generator docs-generator 개발단계 prefix 추천해줘","api_calls":1,"subagent_calls":0,"total_tokens":48421,"input":{"uncached":3,"cache_create":520,"cache_read":45947},"output":1951,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","ts":"2026-05-12T14:38:49.822Z","calls":11,"here":false},{"text":"개발 nuxt-component nuxt-docs nuxt-api-state unit-test-generator docs-generator 개발단계 prefix 추천해줘","ts":"2026-05-12T14:42:53.731Z","calls":1,"here":true},{"text":"b로 바꿔줘","ts":"2026-05-12T14:43:44.144Z","calls":9,"here":false},{"text":"단계별 Skill 설명 추가해줘","ts":"2026-05-12T14:45:42.573Z","calls":0,"here":false}]},{"ts":"2026-05-12T14:28:26.067Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"ccbfe545-12d5-4c3e-9feb-b3467feb8620","text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","api_calls":1,"subagent_calls":0,"total_tokens":33131,"input":{"uncached":3,"cache_create":8926,"cache_read":23946},"output":256,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/fe-ai-workflow.html' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰 -","ts":"2026-05-12T14:28:04.868Z","calls":1,"here":false},{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","ts":"2026-05-12T14:28:26.067Z","calls":1,"here":true},{"text":"--continue","ts":"2026-05-12T14:36:13.298Z","calls":0,"here":false}]},{"ts":"2026-05-16T01:40:17.904Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"8c56053b-8d23-4314-926d-5895c6c27fd9","text":"/session-report:session-report","api_calls":1,"subagent_calls":0,"total_tokens":24620,"input":{"uncached":2,"cache_create":13560,"cache_read":10831},"output":227,"context":[{"text":"/plugin","ts":"2026-05-16T01:40:11.638Z","calls":0,"here":false},{"text":"Failed to reconnect to plugin:serena:serena.","ts":"2026-05-16T01:40:11.638Z","calls":0,"here":false},{"text":"/session-report:session-report","ts":"2026-05-16T01:40:17.904Z","calls":1,"here":true}]},{"ts":"2026-05-12T14:28:04.868Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"ccbfe545-12d5-4c3e-9feb-b3467feb8620","text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/fe-ai-workflow.html' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰 -","api_calls":1,"subagent_calls":0,"total_tokens":24122,"input":{"uncached":3,"cache_create":11782,"cache_read":12164},"output":173,"context":[{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/fe-ai-workflow.html' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰 -","ts":"2026-05-12T14:28:04.868Z","calls":1,"here":true},{"text":"'/Users/gil/Downloads/gameservice-fe-agent 2/docs/WDG00.04.02.06.07.01 AI 활용 - CBO-플랫폼서비스개발담당.md' 단계별 스킬을 더 추가해줘 아래 말한 단계 외에도 필요한 단계 추천 - 단위 테스트 - 코드 리뷰","ts":"2026-05-12T14:28:26.067Z","calls":1,"here":false},{"text":"--continue","ts":"2026-05-12T14:36:13.298Z","calls":0,"here":false}]},{"ts":"2026-05-12T15:26:41.699Z","project":"-Users-gil-Downloads-gameservice-fe-agent-2","session":"724530e1-31af-40a0-b272-2535f9935ce4","text":"http://gilnas.synology.me:3000/gil/game-fe-agent.git 여기로 깃 연결","api_calls":2,"subagent_calls":0,"total_tokens":24030,"input":{"uncached":3,"cache_create":13104,"cache_read":10923},"output":0,"context":[{"text":"/clear","ts":"2026-05-12T15:26:32.796Z","calls":0,"here":false},{"text":"http://gilnas.synology.me:3000/gil/game-fe-agent.git 여기로 깃 연결","ts":"2026-05-12T15:26:41.699Z","calls":2,"here":true}]},{"ts":"2026-05-16T01:30:43.054Z","project":"-Users-gil--openclaw-workspace-nuxt-wiki","session":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","text":"nuxt 관련 위키를 토대로 개발 지침 생성해줘","api_calls":1,"subagent_calls":0,"total_tokens":0,"input":{"uncached":0,"cache_create":0,"cache_read":0},"output":0,"context":[{"text":"'/Users/gil/.openclaw/workspace/nuxt-wiki/reference' nuxt정보 위키도 작성해줘","ts":"2026-05-12T15:24:51.660Z","calls":19,"here":false},{"text":"/compact","ts":"2026-05-16T01:30:11.999Z","calls":0,"here":false},{"text":"nuxt 관련 위키를 토대로 개발 지침 생성해줘","ts":"2026-05-16T01:30:43.054Z","calls":1,"here":true},{"text":"/login","ts":"2026-05-16T01:31:07.978Z","calls":0,"here":false},{"text":"Login successful","ts":"2026-05-16T01:31:07.978Z","calls":0,"here":false}]}],"by_day":[{"date":"2026-05-12","dow":"Tue","tokens":3336402,"sessions":[{"id":"f378c1ec-3e63-4b5b-bd6f-bd4b617e8ae5","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":88813,"start_min":1395,"end_min":1408},{"id":"ccbfe545-12d5-4c3e-9feb-b3467feb8620","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":57253,"start_min":1408,"end_min":1416},{"id":"183bbac6-1874-4c04-a33a-3009e4e124aa","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":2729029,"start_min":1419,"end_min":1440},{"id":"b36d34e5-069a-45f0-8e61-0d1c4e768768","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":461307,"start_min":1428,"end_min":1434}],"peak":2,"peak_at_min":1400},{"date":"2026-05-13","dow":"Wed","tokens":4029170,"sessions":[{"id":"a98eda68-dae8-4ee7-8727-e4dbbfccc411","project":"-Users-gil--openclaw-workspace-nuxt-wiki","tokens":4005140,"start_min":4,"end_min":4959},{"id":"724530e1-31af-40a0-b272-2535f9935ce4","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":24030,"start_min":27,"end_min":30}],"peak":2,"peak_at_min":20},{"date":"2026-05-16","dow":"Sat","tokens":24620,"sessions":[{"id":"8c56053b-8d23-4314-926d-5895c6c27fd9","project":"-Users-gil-Downloads-gameservice-fe-agent-2","tokens":24620,"start_min":640,"end_min":640}],"peak":0,"peak_at_min":0}]}</script>
|
||
|
||
<script>
|
||
(function() {
|
||
const DATA = JSON.parse(document.getElementById('report-data').textContent || '{}');
|
||
const $ = id => document.getElementById(id);
|
||
const fmt = n => n>=1e9 ? (n/1e9).toFixed(2)+'B' : n>=1e6 ? (n/1e6).toFixed(2)+'M'
|
||
: n>=1e3 ? (n/1e3).toFixed(1)+'k' : String(n);
|
||
const pct = (a,b) => b>0 ? ((100*a/b).toFixed(1)+'%') : '—';
|
||
const esc = s => String(s).replace(/[&<>"]/g, c =>
|
||
({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
||
const short = p => String(p||'').replace(/^-Users-[^-]+-/,'').replace(/^code-/,'');
|
||
const MON = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||
const niceDate = iso => { const d = new Date(iso); return isNaN(d) ? '' :
|
||
`${MON[d.getMonth()]} ${d.getDate()} · ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; };
|
||
|
||
if (!DATA.overall) { $('meta-line').textContent = 'no data — embed JSON in #report-data.'; return; }
|
||
|
||
// header + hero
|
||
const o = DATA.overall, it = o.input_tokens;
|
||
const GRAND = it.total + o.output_tokens;
|
||
const share = n => GRAND>0 ? (100*n/GRAND).toFixed(1)+'%' : '—';
|
||
const span = o.span;
|
||
$('cmd-flags').innerHTML = DATA.since
|
||
? `<span class="flag">--since</span> ${esc(DATA.since)}` : '';
|
||
$('meta-line').textContent =
|
||
(span ? `${span.from.slice(0,10)} → ${span.to.slice(0,10)}` : '') +
|
||
` · ${DATA.root || ''}`;
|
||
$('foot-gen').textContent = `generated ${DATA.generated_at?.slice(0,16).replace('T',' ') || ''}`;
|
||
$('foot-stats').textContent =
|
||
`${o.sessions} sessions · ${o.api_calls} api calls · ${o.human_messages} prompts`;
|
||
|
||
const num = fmt(GRAND), m = num.match(/^([\d.]+)([A-Za-z]*)$/);
|
||
$('hero-total').innerHTML =
|
||
`${m?m[1]:num}<span class="unit">${m?m[2]:''}</span><span class="label">tokens</span>`;
|
||
const cacheCls = it.pct_cached>=85 ? 'ok' : 'warn';
|
||
$('hero-split').innerHTML =
|
||
`<b>${fmt(it.total)}</b> input <span class="${cacheCls}">(${it.pct_cached}% cache-read)</span> · `+
|
||
`<b>${fmt(o.output_tokens)}</b> output · all figures below are % of this total`;
|
||
|
||
// overall stat grid
|
||
$('overall-grid').innerHTML = [
|
||
['sessions', o.sessions],
|
||
['api calls', o.api_calls],
|
||
['human msgs', o.human_messages],
|
||
['active hours', o.hours.active, `${o.hours.wall_clock} wall-clock`],
|
||
['cache breaks', o.cache_breaks_over_100k, '>100k uncached'],
|
||
['subagent calls', o.subagent.calls, `avg ${fmt(o.subagent.avg_tokens_per_call)}`],
|
||
].map(([l,v,d]) =>
|
||
`<div class="stat"><div class="label">${l}</div>`+
|
||
`<div class="val">${typeof v==='number'&&v>=1e4?fmt(v):v}</div>`+
|
||
(d?`<div class="detail">${d}</div>`:'')+`</div>`).join('');
|
||
|
||
// session timeline by day
|
||
(function() {
|
||
const days = (DATA.by_day||[]).slice(-14);
|
||
if (!days.length) { $('timeline-section').style.display='none'; return; }
|
||
const PCOL = ['rgb(177,185,249)','rgb(78,186,101)','#D97757','rgb(255,193,7)',
|
||
'rgb(255,107,128)','#9b8cff','#6ec1d6','#c792ea'];
|
||
const dayTotal = days.reduce((a,d)=>a+d.tokens,0) || 1;
|
||
const tokMax = Math.max(...days.map(d=>d.tokens));
|
||
const projects = [...new Set(days.flatMap(d=>d.sessions.map(s=>s.project)))];
|
||
const colorOf = p => PCOL[projects.indexOf(p)%PCOL.length];
|
||
const hhmm = m => (m>=1440?`+${Math.floor(m/1440)}d `:'') +
|
||
`${String(Math.floor(m/60)%24).padStart(2,'0')}:${String(m%60).padStart(2,'0')}`;
|
||
const md = iso => { const [,mo,da]=iso.split('-'); return `${MON[+mo-1]} ${+da}`; };
|
||
let sel = days.findIndex(d=>d.tokens===tokMax);
|
||
|
||
function pills() {
|
||
$('day-pills').innerHTML = days.map((d,i)=>
|
||
`<button class="dpill${d.tokens===tokMax?' heaviest':''}${i===sel?' sel':''}" data-i="${i}">`+
|
||
`<span class="dow">${esc(d.dow)}</span>`+
|
||
`<span class="date">${esc(md(d.date))}</span>`+
|
||
`<span class="pct">${(100*d.tokens/dayTotal).toFixed(1)}%</span>`+
|
||
`<span class="ns">${d.sessions.length} sess</span></button>`
|
||
).join('');
|
||
$('day-pills').querySelectorAll('.dpill').forEach(el=>
|
||
el.onclick=()=>{sel=+el.dataset.i;pills();gantt();});
|
||
}
|
||
function gantt() {
|
||
const d = days[sel], DAY = 1440;
|
||
$('g-day').textContent = `${d.dow} ${md(d.date)}`;
|
||
$('g-stats').innerHTML = `${d.sessions.length} sessions · ${fmt(d.tokens)} tokens`+
|
||
` · peak <b>${d.peak}</b> concurrent at <b>${hhmm(d.peak_at_min)}</b>`;
|
||
const lanes = [];
|
||
for (const s of d.sessions) {
|
||
let placed = false;
|
||
for (const L of lanes) if (L[L.length-1].end_min <= s.start_min) { L.push(s); placed=true; break; }
|
||
if (!placed) lanes.push([s]);
|
||
}
|
||
let h = '';
|
||
for (let t=0;t<=24;t+=6) h += `<div class="gantt-rule" style="left:${100*t/24}%"></div>`;
|
||
h += lanes.map(L=>`<div class="lane">${L.map(s=>{
|
||
const end = Math.min(s.end_min, DAY);
|
||
const w = Math.max(0.15, 100*(end-s.start_min)/DAY);
|
||
const tip = `folder: ${short(s.project)}\n`+
|
||
`${hhmm(s.start_min)}–${hhmm(s.end_min)} · ${fmt(s.tokens)} tokens\n`+
|
||
`session ${s.id}`;
|
||
return `<span class="seg" style="left:${100*s.start_min/DAY}%;width:${w}%;`+
|
||
`background:${colorOf(s.project)}" title="${esc(tip)}"></span>`;
|
||
}).join('')}</div>`).join('');
|
||
$('gantt').innerHTML = h || '<div class="callout">no sessions</div>';
|
||
}
|
||
document.addEventListener('keydown',e=>{
|
||
if (e.key==='ArrowRight'&&sel<days.length-1){sel++;pills();gantt();e.preventDefault();}
|
||
if (e.key==='ArrowLeft'&&sel>0){sel--;pills();gantt();e.preventDefault();}
|
||
});
|
||
$('gantt-leg').innerHTML = projects.slice(0,12).map(p=>
|
||
`<span><span class="sw" style="background:${colorOf(p)}"></span>${esc(short(p))}</span>`).join('');
|
||
pills(); gantt();
|
||
})();
|
||
|
||
// block-char project bars
|
||
(function() {
|
||
const W = 48;
|
||
const rows = Object.entries(DATA.by_project||{})
|
||
.map(([k,v]) => [k, v.input_tokens.total + v.output_tokens])
|
||
.sort((a,b)=>b[1]-a[1]).slice(0,15);
|
||
const max = rows[0]?.[1] || 1;
|
||
$('project-bars').innerHTML = rows.map(([k,v]) => {
|
||
const n = Math.max(1, Math.round(W*v/max));
|
||
return `<div class="bar"><span class="name" title="${esc(k)} — ${fmt(v)}">${esc(short(k))}</span>`+
|
||
`<span class="blocks">${'█'.repeat(n)}<span class="empty">${'░'.repeat(W-n)}</span></span>`+
|
||
`<span class="pct">${share(v)}</span></div>`;
|
||
}).join('');
|
||
})();
|
||
|
||
// ±2 user-message transcript context
|
||
function renderContext(ctx, mark) {
|
||
if (!ctx || !ctx.length) return '<div class="ctx-gap">no transcript context available</div>';
|
||
let h = '<div class="ctx">';
|
||
ctx.forEach((m, i) => {
|
||
const who = m.here ? '> user' : ' user';
|
||
h += `<div class="ctx-msg${m.here?' here':''}">`+
|
||
`<span class="who">${who}</span> ${esc(m.text||'(non-text)')}`+
|
||
`<span class="ts">${niceDate(m.ts)}</span></div>`;
|
||
if (m.here && mark) h += mark;
|
||
if (i < ctx.length-1 || m.here)
|
||
h += `<div class="ctx-gap">${m.calls} api call${m.calls===1?'':'s'}</div>`;
|
||
});
|
||
return h + '</div>';
|
||
}
|
||
|
||
// expandable drill-down list with "show N more" toggle
|
||
function drillList(hostId, items, rowFn, empty) {
|
||
const SHOW = 5;
|
||
const host = $(hostId);
|
||
if (!items.length) { host.innerHTML = `<div class="callout">${empty}</div>`; return; }
|
||
const head = items.slice(0,SHOW).map(rowFn).join('');
|
||
const rest = items.slice(SHOW).map(rowFn).join('');
|
||
host.innerHTML = head + (rest
|
||
? `<div hidden>${rest}</div><button class="more-btn">show ${items.length-SHOW} more</button>`
|
||
: '');
|
||
const btn = host.querySelector('.more-btn');
|
||
if (btn) btn.onclick = () => {
|
||
const r = btn.previousElementSibling; r.hidden = !r.hidden;
|
||
btn.textContent = r.hidden ? `show ${items.length-SHOW} more` : 'show less';
|
||
};
|
||
}
|
||
|
||
drillList('top-prompts', (DATA.top_prompts||[]).slice(0,100), p => {
|
||
const inTot = p.input.uncached+p.input.cache_create+p.input.cache_read;
|
||
return `<details><summary>`+
|
||
`<span class="amt">${share(p.total_tokens)}</span>`+
|
||
`<span class="desc">${esc(p.text)}</span>`+
|
||
`<span class="meta">${niceDate(p.ts)} · ${esc(short(p.project))} · ${p.api_calls} calls`+
|
||
(p.subagent_calls?` · ${p.subagent_calls} subagents`:'')+
|
||
` · ${pct(p.input.cache_read,inTot)} cached</span>`+
|
||
`</summary><div class="body">`+
|
||
renderContext(p.context)+
|
||
`<div>session <code>${esc(p.session)}</code></div>`+
|
||
`<div>in: uncached ${fmt(p.input.uncached)} · cache-create ${fmt(p.input.cache_create)} · `+
|
||
`cache-read ${fmt(p.input.cache_read)} · out ${fmt(p.output)}</div>`+
|
||
`</div></details>`;
|
||
}, 'No prompts in range.');
|
||
|
||
drillList('cache-breaks', (DATA.cache_breaks||[]).slice(0,100), b =>
|
||
`<details><summary>`+
|
||
`<span class="amt">${fmt(b.uncached)}</span>`+
|
||
`<span class="desc">${esc(short(b.project))} · `+
|
||
`${b.kind==='subagent'?esc(b.agentType||'subagent'):'main'}</span>`+
|
||
`<span class="meta">${niceDate(b.ts)} · ${pct(b.uncached,b.total)} of ${fmt(b.total)} uncached</span>`+
|
||
`</summary><div class="body">`+
|
||
renderContext(b.context,
|
||
`<div class="ctx-break"><b>${fmt(b.uncached)}</b> uncached `+
|
||
`(${pct(b.uncached,b.total)} of ${fmt(b.total)}) — cache break here</div>`)+
|
||
`<div>session <code>${esc(b.session)}</code></div>`+
|
||
`</div></details>`,
|
||
'No cache breaks over threshold.');
|
||
|
||
// sortable table
|
||
function table(el, cols, rows) {
|
||
let sortIdx = cols.findIndex(c=>c.sort), asc = false;
|
||
function render() {
|
||
const sorted = rows.slice().sort((a,b)=>{
|
||
const va=a[sortIdx], vb=b[sortIdx];
|
||
return (asc?1:-1)*(typeof va==='number' ? va-vb : String(va).localeCompare(String(vb)));
|
||
});
|
||
el.innerHTML = `<thead><tr>${cols.map((c,i)=>
|
||
`<th class="${c.num?'num':''} ${i===sortIdx?'sorted'+(asc?' asc':''):''}" data-i="${i}">${c.h}</th>`
|
||
).join('')}</tr></thead><tbody>${sorted.map(r=>
|
||
`<tr>${r.map((v,i)=>`<td class="${cols[i].num?'num':''}">${
|
||
cols[i].fmt?cols[i].fmt(v):esc(v)}</td>`).join('')}</tr>`
|
||
).join('')}</tbody>`;
|
||
el.querySelectorAll('th').forEach(th=>th.onclick=()=>{
|
||
const i=+th.dataset.i; if(i===sortIdx)asc=!asc; else{sortIdx=i;asc=false;} render();
|
||
});
|
||
}
|
||
render();
|
||
}
|
||
|
||
function statRows(obj) {
|
||
return Object.entries(obj||{}).map(([k,v])=>[
|
||
short(k), GRAND>0 ? 100*(v.input_tokens.total+v.output_tokens)/GRAND : 0,
|
||
v.sessions, v.api_calls, v.human_messages,
|
||
v.input_tokens.total, v.input_tokens.pct_cached, v.output_tokens,
|
||
v.hours.active, v.cache_breaks_over_100k,
|
||
v.subagent.calls, v.subagent.avg_tokens_per_call,
|
||
]);
|
||
}
|
||
const statCols = [
|
||
{h:'name'},{h:'% total',num:1,sort:1,fmt:v=>v.toFixed(1)+'%'},
|
||
{h:'sess',num:1},{h:'calls',num:1},{h:'msgs',num:1},
|
||
{h:'input',num:1,fmt:fmt},{h:'%cached',num:1,fmt:v=>v+'%'},
|
||
{h:'output',num:1,fmt:fmt},{h:'active h',num:1},{h:'breaks',num:1},
|
||
{h:'subagents',num:1},{h:'avg sub tok',num:1,fmt:fmt},
|
||
];
|
||
table($('tbl-projects'), statCols, statRows(DATA.by_project));
|
||
table($('tbl-subagents'), statCols, statRows(DATA.by_subagent_type));
|
||
table($('tbl-skills'), statCols, statRows(DATA.by_skill));
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|