mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-04-16 20:54:56 +00:00
Compare commits
17 Commits
add-box-pl
...
add-datave
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b666914e6 | ||
|
|
0de7a91403 | ||
|
|
3ffb4b4ca8 | ||
|
|
656b617198 | ||
|
|
7ed523140f | ||
|
|
9fc974ef8b | ||
|
|
9a6b30ebb4 | ||
|
|
d4e6f609d8 | ||
|
|
95f807ee6c | ||
|
|
23a9a10ff7 | ||
|
|
5c6c90c1bd | ||
|
|
76f1e09f07 | ||
|
|
d19dab67e8 | ||
|
|
58578a456a | ||
|
|
1057d02c53 | ||
|
|
9dc3809e74 | ||
|
|
6e43e87fc8 |
@@ -7,6 +7,16 @@
|
||||
"email": "support@anthropic.com"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "adlc",
|
||||
"description": "Agentforce Agent Development Life Cycle — author, discover, scaffold, deploy, test, and optimize .agent files",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/SalesforceAIResearch/agentforce-adlc.git"
|
||||
},
|
||||
"homepage": "https://github.com/SalesforceAIResearch/agentforce-adlc"
|
||||
},
|
||||
{
|
||||
"name": "adspirer-ads-agent",
|
||||
"description": "Cross-platform ad management for Google Ads, Meta Ads, TikTok Ads, and LinkedIn Ads. 91 tools for keyword research, campaign creation, performance analysis, and budget optimization.",
|
||||
@@ -73,6 +83,17 @@
|
||||
},
|
||||
"homepage": "https://github.com/awslabs/agent-plugins"
|
||||
},
|
||||
{
|
||||
"name": "amplitude",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/amplitude/mcp-marketplace.git",
|
||||
"sha": "be54ccb66b10593721dd3a31e47b2db20ea02d2f"
|
||||
},
|
||||
"description": "Use Amplitude as an expert analyst — instrument Amplitude, discover product opportunities, analyze charts, create dashboards, manage experiments, and understand users and accounts.",
|
||||
"category": "monitoring",
|
||||
"homepage": "https://github.com/amplitude/mcp-marketplace"
|
||||
},
|
||||
{
|
||||
"name": "asana",
|
||||
"description": "Asana project management integration. Create and manage tasks, search projects, update assignments, track progress, and integrate your development workflow with Asana's work management platform.",
|
||||
@@ -147,6 +168,39 @@
|
||||
},
|
||||
"homepage": "https://github.com/awslabs/agent-plugins"
|
||||
},
|
||||
{
|
||||
"name": "azure-cosmos-db-assistant",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/AzureCosmosDB/cosmosdb-claude-code-plugin.git",
|
||||
"sha": "56e6da0cae93cdee8bcfa5e624ecdd9a0a483181"
|
||||
},
|
||||
"description": "Expert assistant for Azure Cosmos DB — data modeling, query optimization, performance tuning, and best practices.",
|
||||
"category": "database",
|
||||
"homepage": "https://github.com/AzureCosmosDB/cosmosdb-claude-code-plugin"
|
||||
},
|
||||
{
|
||||
"name": "base44",
|
||||
"description": "Build and deploy Base44 full-stack apps with CLI project management and JavaScript/TypeScript SDK development skills",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/base44/skills.git",
|
||||
"sha": "c7039b37eca0e2916a565a7395040c00055bcf8b"
|
||||
},
|
||||
"homepage": "https://docs.base44.com"
|
||||
},
|
||||
{
|
||||
"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...",
|
||||
@@ -242,6 +296,17 @@
|
||||
"category": "productivity",
|
||||
"homepage": "https://github.com/anthropics/claude-plugins-official/tree/main/plugins/claude-md-management"
|
||||
},
|
||||
{
|
||||
"name": "cloudflare",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/cloudflare/skills.git",
|
||||
"sha": "5ec03da67e230df52b698255c8e5979dc9b124b6"
|
||||
},
|
||||
"description": "Skills for the Cloudflare developer platform: Workers, Durable Objects, Agents SDK, MCP servers, Wrangler CLI, and web performance.",
|
||||
"category": "deployment",
|
||||
"homepage": "https://github.com/cloudflare/skills"
|
||||
},
|
||||
{
|
||||
"name": "cloudinary",
|
||||
"description": "Use Cloudinary directly in Claude. Manage assets, apply transformations, optimize media, and more through natural conversation.",
|
||||
@@ -356,6 +421,18 @@
|
||||
},
|
||||
"homepage": "https://github.com/astronomer/agents"
|
||||
},
|
||||
{
|
||||
"name": "dataverse",
|
||||
"description": "Agent skills for building on, analyzing, and managing Microsoft Dataverse — with Dataverse MCP, PAC CLI, and Python SDK.",
|
||||
"category": "database",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "https://github.com/microsoft/Dataverse-skills.git",
|
||||
"path": ".github/plugins/dataverse",
|
||||
"ref": "main"
|
||||
},
|
||||
"homepage": "https://github.com/microsoft/Dataverse-skills"
|
||||
},
|
||||
{
|
||||
"name": "deploy-on-aws",
|
||||
"description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment.",
|
||||
@@ -1002,6 +1079,18 @@
|
||||
},
|
||||
"homepage": "https://www.accoil.com/product-tracking"
|
||||
},
|
||||
{
|
||||
"name": "pydantic-ai",
|
||||
"description": "Write accurate Pydantic AI code from the start. Up-to-date patterns, decision trees, and common gotchas for agents, tools, structured output, streaming, and multi-agent apps.",
|
||||
"category": "development",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "pydantic/skills",
|
||||
"path": "plugins/ai",
|
||||
"ref": "main"
|
||||
},
|
||||
"homepage": "https://github.com/pydantic/skills/tree/main/plugins/ai"
|
||||
},
|
||||
{
|
||||
"name": "pyright-lsp",
|
||||
"description": "Python language server (Pyright) for type checking and code intelligence",
|
||||
@@ -1137,7 +1226,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sanity-plugin",
|
||||
"name": "sanity",
|
||||
"description": "Sanity content platform integration with MCP server, agent skills, and slash commands. Query and author content, build and optimize GROQ queries, design schemas, and set up Visual Editing.",
|
||||
"category": "development",
|
||||
"author": {
|
||||
@@ -1240,8 +1329,7 @@
|
||||
"category": "security",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/SonarSource/sonarqube-agent-plugins.git",
|
||||
"sha": "0cae644cee9318e6245b62ca779abdc60e6daa49"
|
||||
"url": "https://github.com/SonarSource/sonarqube-agent-plugins.git"
|
||||
},
|
||||
"homepage": "https://github.com/SonarSource/sonarqube-agent-plugins"
|
||||
},
|
||||
@@ -1266,6 +1354,17 @@
|
||||
},
|
||||
"homepage": "https://sourcegraph.com"
|
||||
},
|
||||
{
|
||||
"name": "spotify-ads-api",
|
||||
"description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.",
|
||||
"category": "productivity",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "https://github.com/spotify/ads-claude-plugin.git",
|
||||
"sha": "a4bce9912db071d47dfb410086a48004e0539efa"
|
||||
},
|
||||
"homepage": "https://github.com/spotify/ads-claude-plugin"
|
||||
},
|
||||
{
|
||||
"name": "stagehand",
|
||||
"description": "Browser automation skill for Claude Code using Stagehand. Automate web interactions, extract data, and navigate websites using natural language.",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "slack",
|
||||
"description": "Slack workspace integration. Search messages, access channels, read threads, and stay connected with your team's communications while coding. Find relevant discussions and context quickly.",
|
||||
"author": {
|
||||
"name": "Slack"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"slack": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.slack.com/mcp",
|
||||
"oauth": {
|
||||
"clientId": "1601185624273.8899143856786",
|
||||
"callbackPort": 3118
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?)'
|
||||
|
||||
202
plugins/session-report/LICENSE
Normal file
202
plugins/session-report/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -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