Skip to content

Plugin distribution, contributor-facing READMEs, and site event tracking - #33

Merged
krayong merged 1 commit into
mainfrom
feat/launch-week-1
Aug 10, 2026
Merged

Plugin distribution, contributor-facing READMEs, and site event tracking#33
krayong merged 1 commit into
mainfrom
feat/launch-week-1

Conversation

@krayong

@krayong krayong commented Aug 10, 2026

Copy link
Copy Markdown
Owner

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.json and .claude-plugin/marketplace.json, so the repo is both a plugin and its own single-plugin marketplace:

/plugin marketplace add krayong/ccsidekick
/plugin install ccsidekick@ccsidekick

The plugin ships no hooks, deliberately. 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: packages/core/dist/ is gitignored build output.

commands/ccsidekick-setup.md closes the gap that leaves, with a /ccsidekick-setup command driving npx ccsidekick setup across the whole flag surface. Two flags keep guardrails rather than 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: no path validation, and it redirects where settings.json is written. The command's Bash grant is scoped to Bash(npx -y ccsidekick:*) rather than bare npx, so it cannot pre-approve fetching an arbitrary package.

Only plugin.json carries a version. claude plugin validate --strict needs 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.ts keeps it in step 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.

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). scripts/plugin-manifest.test.ts gates 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.md was 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.ts guarded 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.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 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 hashed website/ alone, which described the deploy completely while the site was assets-only. It now includes wrangler.jsonc and the Worker. Without that, any Worker-only change — including the documented rollback that removes main — would produce an identical tree, skip wrangler deploy, and report green.

Also fixed

  • The README's releases CTA pointed at /subscriptions, which 404s. The path is singular.
  • Both READMEs claimed the pack-author skill loads itself when you open Claude Code. It does not.
  • CLAUDE.md, website/CLAUDE.md and wrangler.jsonc all still described an assets-only site. They are how the next session learns the architecture, and an agent reading them could reasonably delete main as dead config.

Verification

All green on bf7450d:

Check Result
format:check / lint / typecheck exit 0
bun run test 1100 pass, 0 fail
readme:drift 33 widgets, 75 themes (floor 75+), 18 packs
site:build ok
claude plugin validate ./ --strict ✔ Validation passed
changeset status --since=main patch bump for ccsidekick
wrangler@4.110.0 deploy --dry-run bundles; resolves env.EVENTS and env.ASSETS

The 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.

  • Cloudflare Rate Limiting rule on /e — deployed. Free plan: path /e, characteristic IP, 10 req / 10 s, Block, 10 s.
  • Confirm Analytics Engine is enabled on the account. If it is not, wrangler deploy fails. Deploys are atomic, so the live site is unaffected by that failure.
  • Dispatch deploy.yml, then verify POST /e with the correct Origin returns 204. It returns 405 today from the assets layer, so 405 after deploy means run_worker_first did not take effect.
  • Diff the security headers before and after: _headers does not apply to Worker-generated responses.

Note on quota: static asset requests are free and unlimited and never invoke the Worker, so only /e consumes the Workers free tier. If that is exhausted, /e returns 429 and the landing page keeps serving normally.

🦇 Generated by Penny-One at Batcave

… 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>
@github-actions github-actions Bot added core The engine (packages/core) website The landing page and its build scripts ci/cd Repo scripts and GitHub workflows agents Agent config (.claude, CLAUDE.md, AGENTS.md) labels Aug 10, 2026
@krayong
krayong merged commit 13a974f into main Aug 10, 2026
7 checks passed
@krayong
krayong deleted the feat/launch-week-1 branch August 10, 2026 09:00
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent config (.claude, CLAUDE.md, AGENTS.md) ci/cd Repo scripts and GitHub workflows core The engine (packages/core) website The landing page and its build scripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant