mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-04-19 06:54:16 +00:00
Compare commits
8 Commits
tobin/add-
...
claude/daz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50ac9c11c6 | ||
|
|
face11b559 | ||
|
|
1057d02c53 | ||
|
|
9dc3809e74 | ||
|
|
6e43e87fc8 | ||
|
|
b32879bf76 | ||
|
|
98c01d3fbf | ||
|
|
ce0166dde2 |
@@ -147,6 +147,17 @@
|
||||
},
|
||||
"homepage": "https://github.com/awslabs/agent-plugins"
|
||||
},
|
||||
{
|
||||
"name": "box",
|
||||
"description": "Work with your Box content directly from Claude Code — search files, organize folders, collaborate with your team, and use Box AI to answer questions, summarize documents, and extract data without leaving your workflow.",
|
||||
"category": "productivity",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/box/box-for-ai.git",
|
||||
"sha": "6f4ec3549f3e869b115628403555b1c9220b2b34"
|
||||
},
|
||||
"homepage": "https://github.com/box/box-for-ai"
|
||||
},
|
||||
{
|
||||
"name": "brightdata-plugin",
|
||||
"description": "Web scraping, Google search, structured data extraction, and MCP server integration powered by Bright Data. Includes 7 skills: scrape any webpage as markdown (with bot detection/CAPTCHA bypass), search Google with structured JSON results, extract data from 40+ websites (Amazon, LinkedIn, Instagram, TikTok, YouTube, and more), orchestrate Bright Data's 60+ MCP tools, built-in best practices for Web Unlocker, SERP API, Web Scraper API, and Browser API, Python SDK best practices for the brightda...",
|
||||
@@ -157,6 +168,17 @@
|
||||
},
|
||||
"homepage": "https://docs.brightdata.com"
|
||||
},
|
||||
{
|
||||
"name": "cds-mcp",
|
||||
"description": "AI-assisted development of SAP Cloud Application Programming Model (CAP) projects. Search CDS models and CAP documentation.",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/cap-js/mcp-server.git",
|
||||
"sha": "4d59d7070a52761a9b8028cbe710c8d7477cbc92"
|
||||
},
|
||||
"homepage": "https://cap.cloud.sap/"
|
||||
},
|
||||
{
|
||||
"name": "chrome-devtools-mcp",
|
||||
"description": "Control and inspect a live Chrome browser from your coding agent. Record performance traces, analyze network requests, check console messages with source-mapped stack traces, and automate browser actions with Puppeteer.",
|
||||
@@ -384,6 +406,18 @@
|
||||
"category": "learning",
|
||||
"homepage": "https://github.com/anthropics/claude-plugins-public/tree/main/plugins/explanatory-output-style"
|
||||
},
|
||||
{
|
||||
"name": "expo",
|
||||
"description": "Official Expo skills for building, deploying, upgrading, and debugging React Native apps with Expo. Covers UI development with Expo Router, SwiftUI and Jetpack Compose components, Tailwind CSS setup, API routes, data fetching, CI/CD workflows, App Store and Play Store deployment, SDK upgrades, DOM components, and dev client distribution.",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "expo/skills",
|
||||
"path": "plugins/expo",
|
||||
"ref": "main"
|
||||
},
|
||||
"homepage": "https://github.com/expo/skills/blob/main/plugins/expo/README.md"
|
||||
},
|
||||
{
|
||||
"name": "fakechat",
|
||||
"description": "Localhost web chat for testing the channel notification flow. No tokens, no access control, no third-party service.",
|
||||
@@ -1453,6 +1487,16 @@
|
||||
"sha": "b93007e9a726c6ee93c57a949e732744ef5acbfd"
|
||||
},
|
||||
"homepage": "https://github.com/zapier/zapier-mcp/tree/main/plugins/zapier"
|
||||
},
|
||||
{
|
||||
"name": "zoom-plugin",
|
||||
"description": "Claude plugin for planning, building, and debugging Zoom integrations across REST APIs, SDKs, webhooks, bots, and MCP workflows.",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/zoom/zoom-plugin.git"
|
||||
},
|
||||
"homepage": "https://developers.zoom.us/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "telegram",
|
||||
"description": "Telegram channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"keywords": [
|
||||
"telegram",
|
||||
"messaging",
|
||||
|
||||
@@ -51,6 +51,22 @@ if (!TOKEN) {
|
||||
process.exit(1)
|
||||
}
|
||||
const INBOX_DIR = join(STATE_DIR, 'inbox')
|
||||
const PID_FILE = join(STATE_DIR, 'bot.pid')
|
||||
|
||||
// Telegram allows exactly one getUpdates consumer per token. If a previous
|
||||
// session crashed (SIGKILL, terminal closed) its server.ts grandchild can
|
||||
// survive as an orphan and hold the slot forever, so every new session sees
|
||||
// 409 Conflict. Kill any stale holder before we start polling.
|
||||
mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 })
|
||||
try {
|
||||
const stale = parseInt(readFileSync(PID_FILE, 'utf8'), 10)
|
||||
if (stale > 1 && stale !== process.pid) {
|
||||
process.kill(stale, 0)
|
||||
process.stderr.write(`telegram channel: replacing stale poller pid=${stale}\n`)
|
||||
process.kill(stale, 'SIGTERM')
|
||||
}
|
||||
} catch {}
|
||||
writeFileSync(PID_FILE, String(process.pid))
|
||||
|
||||
// Last-resort safety net — without these the process dies silently on any
|
||||
// unhandled promise rejection. With them it logs and keeps serving tools.
|
||||
@@ -621,6 +637,9 @@ function shutdown(): void {
|
||||
if (shuttingDown) return
|
||||
shuttingDown = true
|
||||
process.stderr.write('telegram channel: shutting down\n')
|
||||
try {
|
||||
if (parseInt(readFileSync(PID_FILE, 'utf8'), 10) === process.pid) rmSync(PID_FILE)
|
||||
} catch {}
|
||||
// bot.stop() signals the poll loop to end; the current getUpdates request
|
||||
// may take up to its long-poll timeout to return. Force-exit after 2s.
|
||||
setTimeout(() => process.exit(0), 2000)
|
||||
@@ -630,6 +649,19 @@ process.stdin.on('end', shutdown)
|
||||
process.stdin.on('close', shutdown)
|
||||
process.on('SIGTERM', shutdown)
|
||||
process.on('SIGINT', shutdown)
|
||||
process.on('SIGHUP', shutdown)
|
||||
|
||||
// Orphan watchdog: stdin events above don't reliably fire when the parent
|
||||
// chain (`bun run` wrapper → shell → us) is severed by a crash. Poll for
|
||||
// reparenting (POSIX) or a dead stdin pipe and self-terminate.
|
||||
const bootPpid = process.ppid
|
||||
setInterval(() => {
|
||||
const orphaned =
|
||||
(process.platform !== 'win32' && process.ppid !== bootPpid) ||
|
||||
process.stdin.destroyed ||
|
||||
process.stdin.readableEnded
|
||||
if (orphaned) shutdown()
|
||||
}, 5000).unref()
|
||||
|
||||
// Commands are DM-only. Responding in groups would: (1) leak pairing codes via
|
||||
// /status to other group members, (2) confirm bot presence in non-allowlisted
|
||||
@@ -975,7 +1007,15 @@ void (async () => {
|
||||
})
|
||||
return // bot.stop() was called — clean exit from the loop
|
||||
} catch (err) {
|
||||
if (shuttingDown) return
|
||||
if (err instanceof GrammyError && err.error_code === 409) {
|
||||
if (attempt >= 8) {
|
||||
process.stderr.write(
|
||||
`telegram channel: 409 Conflict persists after ${attempt} attempts — ` +
|
||||
`another poller is holding the bot token (stray 'bun server.ts' process or a second session). Exiting.\n`,
|
||||
)
|
||||
return
|
||||
}
|
||||
const delay = Math.min(1000 * attempt, 15000)
|
||||
const detail = attempt === 1
|
||||
? ' — another instance is polling (zombie session, or a second Claude Code running?)'
|
||||
|
||||
@@ -166,6 +166,7 @@ const toolUseIdToPrompt = new Map() // tool_use id -> promptKey (Agent spawned d
|
||||
const agentIdToPrompt = new Map() // agentId -> promptKey
|
||||
const prompts = new Map() // promptKey -> { text, ts, project, sessionId, ...usage }
|
||||
const sessionTurns = new Map() // sessionId -> [promptKey, ...] in transcript order
|
||||
const sessionSpans = new Map() // sessionId -> {project, firstTs, lastTs, tokens}
|
||||
|
||||
function promptRecord(key, init) {
|
||||
let r = prompts.get(key)
|
||||
@@ -333,11 +334,29 @@ async function processFile(p, info, buckets) {
|
||||
}
|
||||
}
|
||||
|
||||
// session span (for by_day timeline) — subagent files roll into parent sessionId
|
||||
let span = sessionSpans.get(info.sessionId)
|
||||
if (!span) {
|
||||
span = { project: info.project, firstTs: null, lastTs: null, tokens: 0 }
|
||||
sessionSpans.set(info.sessionId, span)
|
||||
}
|
||||
if (firstTs !== null) {
|
||||
if (span.firstTs === null || firstTs < span.firstTs) span.firstTs = firstTs
|
||||
if (span.lastTs === null || lastTs > span.lastTs) span.lastTs = lastTs
|
||||
}
|
||||
|
||||
// commit API calls
|
||||
for (const [key, { usage, ts, skill, prompt }] of fileApiCalls) {
|
||||
if (key && seenRequestIds.has(key)) continue
|
||||
seenRequestIds.add(key)
|
||||
|
||||
const tot =
|
||||
(usage.input_tokens || 0) +
|
||||
(usage.cache_creation_input_tokens || 0) +
|
||||
(usage.cache_read_input_tokens || 0) +
|
||||
(usage.output_tokens || 0)
|
||||
span.tokens += tot
|
||||
|
||||
const targets = [overall, project]
|
||||
if (subagent) targets.push(subagent)
|
||||
if (skill && skillStats) {
|
||||
@@ -359,11 +378,6 @@ async function processFile(p, info, buckets) {
|
||||
|
||||
// subagent token accounting on parent buckets
|
||||
if (info.kind === 'subagent') {
|
||||
const tot =
|
||||
(usage.input_tokens || 0) +
|
||||
(usage.cache_creation_input_tokens || 0) +
|
||||
(usage.cache_read_input_tokens || 0) +
|
||||
(usage.output_tokens || 0)
|
||||
overall.subagentTokens += tot
|
||||
project.subagentTokens += tot
|
||||
if (subagent) subagent.subagentTokens += tot
|
||||
@@ -656,10 +670,55 @@ function printJson({ overall, perProject, perSubagent, perSkill }) {
|
||||
[...perSkill].map(([k, v]) => [k, summarize(v)]),
|
||||
),
|
||||
top_prompts: topPrompts(100),
|
||||
by_day: buildByDay(),
|
||||
}
|
||||
process.stdout.write(JSON.stringify(out, null, 2) + '\n')
|
||||
}
|
||||
|
||||
// Group sessions into local-date buckets for the timeline view. A session is
|
||||
// placed on the day its first message landed; tokens for that session (incl.
|
||||
// subagents) count toward that day even if it ran past midnight.
|
||||
function buildByDay() {
|
||||
const DOW = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
const days = new Map() // yyyy-mm-dd -> {date, dow, tokens, sessions:[]}
|
||||
for (const [id, s] of sessionSpans) {
|
||||
if (s.firstTs === null || s.tokens === 0) continue
|
||||
const d0 = new Date(s.firstTs)
|
||||
const key = `${d0.getFullYear()}-${String(d0.getMonth() + 1).padStart(2, '0')}-${String(d0.getDate()).padStart(2, '0')}`
|
||||
let day = days.get(key)
|
||||
if (!day) {
|
||||
day = { date: key, dow: DOW[d0.getDay()], tokens: 0, sessions: [] }
|
||||
days.set(key, day)
|
||||
}
|
||||
const base = new Date(
|
||||
d0.getFullYear(),
|
||||
d0.getMonth(),
|
||||
d0.getDate(),
|
||||
).getTime()
|
||||
day.tokens += s.tokens
|
||||
day.sessions.push({
|
||||
id,
|
||||
project: s.project,
|
||||
tokens: s.tokens,
|
||||
start_min: Math.max(0, Math.round((s.firstTs - base) / 60000)),
|
||||
end_min: Math.max(1, Math.round((s.lastTs - base) / 60000)),
|
||||
})
|
||||
}
|
||||
for (const d of days.values()) {
|
||||
// peak concurrency via 10-min buckets, capped at 24h for display
|
||||
const b = new Array(144).fill(0)
|
||||
for (const s of d.sessions) {
|
||||
const lo = Math.min(143, Math.floor(s.start_min / 10))
|
||||
const hi = Math.min(144, Math.ceil(Math.min(s.end_min, 1440) / 10))
|
||||
for (let i = lo; i < hi; i++) b[i]++
|
||||
}
|
||||
d.peak = Math.max(0, ...b)
|
||||
d.peak_at_min = d.peak > 0 ? b.indexOf(d.peak) * 10 : 0
|
||||
d.sessions.sort((a, b) => a.start_min - b.start_min)
|
||||
}
|
||||
return [...days.values()].sort((a, b) => a.date.localeCompare(b.date))
|
||||
}
|
||||
|
||||
function promptTotal(r) {
|
||||
return (
|
||||
r.inputUncached + r.inputCacheCreate + r.inputCacheRead + r.outputTokens
|
||||
|
||||
@@ -102,6 +102,42 @@
|
||||
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; }
|
||||
@@ -231,6 +267,21 @@
|
||||
<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>
|
||||
@@ -335,6 +386,65 @@
|
||||
`<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;
|
||||
@@ -366,57 +476,52 @@
|
||||
return h + '</div>';
|
||||
}
|
||||
|
||||
// top prompts — share of grand total
|
||||
(function() {
|
||||
const ps = (DATA.top_prompts||[]).slice(0,100);
|
||||
// expandable drill-down list with "show N more" toggle
|
||||
function drillList(hostId, items, rowFn, empty) {
|
||||
const SHOW = 5;
|
||||
const row = 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>`;
|
||||
};
|
||||
const head = ps.slice(0,SHOW).map(row).join('');
|
||||
const rest = ps.slice(SHOW).map(row).join('');
|
||||
$('top-prompts').innerHTML = ps.length
|
||||
? head + (rest
|
||||
? `<div id="tp-rest" hidden>${rest}</div>`+
|
||||
`<button id="tp-more" class="more-btn">show ${ps.length-SHOW} more</button>`
|
||||
: '')
|
||||
: '<div class="callout">No prompts in range.</div>';
|
||||
const btn = $('tp-more');
|
||||
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 = $('tp-rest'); r.hidden = !r.hidden;
|
||||
btn.textContent = r.hidden ? `show ${ps.length-SHOW} more` : 'show less';
|
||||
const r = btn.previousElementSibling; r.hidden = !r.hidden;
|
||||
btn.textContent = r.hidden ? `show ${items.length-SHOW} more` : 'show less';
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// cache breaks
|
||||
(function() {
|
||||
const bs = (DATA.cache_breaks||[]).slice(0,100);
|
||||
$('cache-breaks').innerHTML = bs.map(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>`+
|
||||
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(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>`
|
||||
).join('') || '<div class="callout">No cache breaks over threshold.</div>';
|
||||
})();
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user