Plugin distribution, contributor-facing READMEs, and site event tracking - #33
Merged
Conversation
… tracking
Prepares the repo for launch week. Three strands: a Claude Code plugin so ccsidekick is
discoverable from inside the product, a contributor funnel that is visible to someone who has not
already decided to contribute, and real event instrumentation on the landing site.
## Plugin distribution
Adds `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`, making the repo both a
plugin and its own single-plugin marketplace, installable with:
/plugin marketplace add krayong/ccsidekick
/plugin install ccsidekick@ccsidekick
The plugin is a discovery surface, not a second installer. It ships no hooks. A plugin cannot set
the main `statusLine` (bundled plugin settings support only `agent` and `subagentStatusLine`), so
every plugin user runs the npm installer anyway, and that installer already registers both classify
hooks. Shipping hooks too would register them twice; `appendEvent` has no dedupe key, so every mood,
streak and familiarity tier would read a doubled event log. There is also no binary to run, since
`packages/core/dist/` is gitignored build output.
`commands/ccsidekick-setup.md` closes the resulting gap with a `/ccsidekick-setup` slash command
that drives `npx ccsidekick setup`. It covers the whole flag surface: character, mode, roster,
theme, currency, budget, comments, tip severity, widgets and per-project scope. Two flags keep
guardrails instead of bans, because both surprise people. `--widgets` replaces the enabled set
rather than adding to it, so the command reads the current config before changing it.
`--usage-fetch=on` starts sending an OAuth token to Anthropic, so it needs an explicit yes first.
`--config-dir` stays refused: it has no path validation and redirects where `settings.json` is
written. The command also names the config that has no flag at all (`fx_refresh`, `balance_path`,
`banding`, `mood_shift`, `icons`) so a request for one ends at the TUI rather than an invented flag.
Its Bash grant is scoped to `Bash(npx -y ccsidekick:*)`, not bare `npx`, so it cannot pre-approve
fetching an arbitrary package.
Only `plugin.json` carries a version. `claude plugin validate --strict` requires the marketplace's
own name, owner and description plus each entry's name and source, and nothing else, so every other
field on the entry would be a duplicate that can drift. `scripts/sync-plugin-version.ts` copies the
published version across inside `ci:version`, by targeted string replacement rather than
parse-and-reserialize: `JSON.stringify` re-expands arrays Prettier keeps inline, which would fail
`format:check` on every release PR. `scripts/plugin-manifest.test.ts` gates version parity, the
name/source agreement, the absence of a second version, the description match, and the
single-occurrence invariant the string replacement depends on.
No CI gate runs `claude plugin validate`. It resolves `latest` over the network on a required check,
so an upstream release could block every merge with nothing in the diff, and it was measured not to
read `plugin.json` at all: deleting that file still exits 0. The offline parity test gates it
instead.
## Contributor funnel
Pack authoring was a level-three heading under Development pointing at an internal skill path, which
nobody reaching the README to decide whether to contribute would ever see. It is now a top-level
"Author your own character" section in both the repo README and the npm one, stating the figure
budget, the voice-pool floor, the lint gate, and where to start.
`packages/core/README.md` was a 3.5 KB stub, and it is the page every npm visitor lands on. It now
carries the character reel, the full eighteen-pack roster, the authoring invitation, and a
watch-for-releases line, with every link and image absolute so npm renders them.
`readme-drift.ts` previously guarded the root README alone, so the npm page's hard-coded counts and
roster could rot silently. It now checks both.
## Site event tracking
Cloudflare Web Analytics does not support custom events, so `scripts/website/worker.ts` adds a
same-origin `POST /e` sink writing one Workers Analytics Engine data point per event. The site stops
being assets-only: `wrangler.jsonc` gains `main`, an `ASSETS` binding, and
`run_worker_first: ["/e"]` so every other path keeps asset-first behaviour.
The endpoint is public and unauthenticated, so it checks Origin, rejects on Content-Length before
reading any body (Cloudflare allows 100 MB into a 128 MB isolate), and accepts only four names from
a closed set. `scripts/website/worker.test.ts` covers all of it in 19 cases. There is no preview URL
and no staging origin, so those tests are the only exercise the code gets before production.
The Origin check stops cross-site abuse and nothing else. Volume abuse needs a Cloudflare Rate
Limiting rule on `/e`, which is a dashboard object this repo cannot create or assert, and which must
exist before the first deploy.
`deploy.yml`'s cache key hashed `website/` alone, which described the deploy completely while the
site was assets-only. It now includes `wrangler.jsonc` and the Worker, without which any
Worker-only change (including the documented rollback that removes `main`) would produce an
identical tree, skip `wrangler deploy`, and report green.
## Also
`CLAUDE.md`, `website/CLAUDE.md` and `wrangler.jsonc` all described an assets-only site and are
updated, since they are how the next session learns the architecture. The README's releases link
pointed at `/subscriptions`, which 404s; the path is singular. Both READMEs claimed the pack-author
skill loads itself on opening Claude Code, which it does not.
Co-Authored-By: Penny-One (Batcave) <noreply@anthropic.com>
krayong
added a commit
that referenced
this pull request
Aug 10, 2026
The repo has been its own plugin marketplace since #33, but nothing outside the marketplace manifest said so. This puts the path on both install surfaces that people and agents actually read, and re-paces the social reel. ## README A `### From inside Claude Code` subsection under Install, carrying both slash commands: ``` /plugin marketplace add krayong/ccsidekick /plugin install ccsidekick@ccsidekick ``` The copy is explicit about what the plugin is: it ships `/ccsidekick-setup` and nothing else, and the engine still comes from npm the first time that command runs. Claiming the plugin installs the status line would send people looking for a binary that is not there. ## llms.txt The same path as a third Install bullet. That file is what an assistant reads when asked how to add this to Claude Code, so the Claude-Code-native answer belongs in it. The two commands are new `pluginAddCmd` / `pluginInstallCmd` tokens in `site-content.ts` rather than literals in the template, matching how `installCmd` already works. The repo slug stays in one place. `website/llms.txt` is generated and gitignored, so this reaches production on the next Deploy Website run. ## showcase.mp4 / showcase.gif Re-rendered at `HOLD=1.6`, which takes the reel from 0.75s to 1.35s per card and 13.8s to 24.6s overall. The GIF paces off `GIF_HOLD` and is unchanged in timing. `assets/characters.*` are untouched by design — the render ran with `OUT`/`GIF` pointed at throwaway paths — so the landing-page hero keeps its original pace and this needs no site deploy. | | before | after | |---|---|---| | `showcase.mp4` duration | 13.77s | 24.57s | | `showcase.mp4` size | 1.52 MB | 1.80 MB | | per card | 0.75s | 1.35s | | `characters.mp4` | 13.77s | unchanged | Frame 0 still reads "Spider-Man — ccsidekick", so the poster frame is intact. ## Verification `bun run format:check`, both `typecheck` configs, `bun run lint`, `bun test` (1100 pass, 0 fail), `readme-drift`, and `pack:lint` across all 18 packs. 🦇 Generated by [Penny-One](https://batman.fandom.com/wiki/Alfred_Pennyworth) at [Batcave](https://batman.fandom.com/wiki/Batcave) Co-authored-by: Penny-One (Batcave) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares the repo for launch week. Three strands, independent of each other: a Claude Code plugin so ccsidekick is discoverable from inside the product, a contributor funnel visible to someone who has not already decided to contribute, and real event instrumentation on the landing site.
Nothing here changes the render or classify hot path, and no telemetry is added to the CLI.
1. Plugin distribution
Adds
.claude-plugin/plugin.jsonand.claude-plugin/marketplace.json, so the repo is both a plugin and its own single-plugin marketplace:The plugin ships no hooks, deliberately. A plugin cannot set the main
statusLine(bundled plugin settings support onlyagentandsubagentStatusLine), so every plugin user runs the npm installer anyway, and that installer already registers both classify hooks. Shipping hooks too would register them twice.appendEventhas no dedupe key, so every mood, streak and familiarity tier would read a doubled event log. There is also no binary to run:packages/core/dist/is gitignored build output.commands/ccsidekick-setup.mdcloses the gap that leaves, with a/ccsidekick-setupcommand drivingnpx ccsidekick setupacross the whole flag surface. Two flags keep guardrails rather than bans, because both surprise people:--widgetsreplaces the enabled set rather than adding to it, so the command reads the current config before changing it.--usage-fetch=onstarts sending an OAuth token to Anthropic, so it needs an explicit yes first.--config-dirstays refused: no path validation, and it redirects wheresettings.jsonis written. The command's Bash grant is scoped toBash(npx -y ccsidekick:*)rather than barenpx, so it cannot pre-approve fetching an arbitrary package.Only
plugin.jsoncarries a version.claude plugin validate --strictneeds the marketplace's own name, owner and description plus each entry's name and source, and nothing else, so every other field on the entry would be a duplicate free to drift.scripts/sync-plugin-version.tskeeps it in step insideci:version, by targeted string replacement rather than parse-and-reserialize:JSON.stringifyre-expands arrays Prettier keeps inline, which would failformat:checkon every release PR.No CI gate runs
claude plugin validate. It resolveslatestover the network on a required check, so an upstream release could block every merge with nothing in the diff, and it was measured not to readplugin.jsonat all (deleting that file still exits 0).scripts/plugin-manifest.test.tsgates it offline instead: version parity, name/source agreement, the absence of a second version, the description match, and the single-occurrence invariant the string replacement depends on.2. Contributor funnel
Pack authoring lived under a level-three heading in Development pointing at an internal skill path. Nobody deciding whether to contribute ever scrolled that far. It is now a top-level Author your own character section in both READMEs, with the figure budget, the voice-pool floor, the lint gate, and where to start.
packages/core/README.mdwas a 3.5 KB stub, and it is the page every npm visitor lands on. It gains the reel, the eighteen-pack roster, the authoring invitation, and a watch-for-releases line, with every link and image absolute so npm renders them.readme-drift.tsguarded the root README alone, so the npm page's counts and roster could rot silently. It now checks both.3. Site event tracking
Cloudflare Web Analytics does not support custom events, so
scripts/website/worker.tsadds a same-originPOST /esink writing one Workers Analytics Engine data point per event. The site stops being assets-only:wrangler.jsoncgainsmain, anASSETSbinding, andrun_worker_first: ["/e"]so every other path keeps asset-first behaviour.The endpoint is public and unauthenticated, so it checks
Origin, rejects onContent-Lengthbefore reading any body (Cloudflare allows 100 MB into a 128 MB isolate), and accepts only four names from a closed set.scripts/website/worker.test.tscovers it in 19 cases; with no preview URL and no staging origin, those tests are the only exercise the code gets before production.deploy.yml's cache key hashedwebsite/alone, which described the deploy completely while the site was assets-only. It now includeswrangler.jsoncand the Worker. Without that, any Worker-only change — including the documented rollback that removesmain— would produce an identical tree, skipwrangler deploy, and report green.Also fixed
/subscriptions, which 404s. The path is singular.pack-authorskill loads itself when you open Claude Code. It does not.CLAUDE.md,website/CLAUDE.mdandwrangler.jsoncall still described an assets-only site. They are how the next session learns the architecture, and an agent reading them could reasonably deletemainas dead config.Verification
All green on
bf7450d:format:check/lint/typecheckbun run testreadme:driftsite:buildclaude plugin validate ./ --strictchangeset status --since=mainccsidekickwrangler@4.110.0 deploy --dry-runenv.EVENTSandenv.ASSETSThe drift guard was checked in both directions: it catches a genuinely missing pack and a stale widget count, and passes clean on the real tree.
Before this goes live
Merging is safe on its own. The event sink only becomes reachable when someone dispatches the deploy, which is
workflow_dispatch-only and does not fire on merge./e— deployed. Free plan: path/e, characteristic IP, 10 req / 10 s, Block, 10 s.wrangler deployfails. Deploys are atomic, so the live site is unaffected by that failure.deploy.yml, then verifyPOST /ewith the correctOriginreturns 204. It returns 405 today from the assets layer, so 405 after deploy meansrun_worker_firstdid not take effect._headersdoes not apply to Worker-generated responses.Note on quota: static asset requests are free and unlimited and never invoke the Worker, so only
/econsumes the Workers free tier. If that is exhausted,/ereturns 429 and the landing page keeps serving normally.🦇 Generated by Penny-One at Batcave