You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TDD: Community Registry — discover and install themes, plugins, and grammars
Introduction
Claudette has a sandboxed plugin runtime and a built-in theme system, but no path for users to discover and install third-party contributions. The bundled set is small (4 env-providers, 2 SCMs, 1 grammar, ~12 themes) and grows only at Claudette's release cadence. This TDD designs the community registry — the in-app surface that browses, installs, updates, and revokes user-contributed themes, plugins, and grammars from utensils/claudette-community.
Converges:#537 — third-party plugin distribution. The discussion in #537 weighed four distribution models and the author's stated bias was "Option 4 layered on Option 3 mechanics, with a mirroring cron in the registry repo so we don't depend on upstream availability." This TDD adopts that recommendation, expands scope to themes + grammars + future kinds (not just plugins), and commits to concrete schemas and phasing.
The registry.json index file at claudette-community/registry.json, generated by CI
In-tree contribution
A contribution whose source files live inside claudette-community itself (themes, grammars, the simple plugins)
External contribution
A plugin whose source lives in the author's own repo, referenced by git_url + sha + sha256, with a tarball mirror in claudette-community for resilience
Direct install
Power-user install path: paste <git-url>@<sha>, marked Unverified in UI
Trust anchor
The cryptographic property that "the code that runs is the code that was reviewed." Today: build-time include_str! + .content_hash. Tomorrow: registry-pinned sha256 verified at install time
Current State
src/plugin_runtime/seed.rs bakes bundled plugins via include_str! and reseeds them to ~/.claudette/plugins/<name>/ on startup, with a .content_hash sentinel that distinguishes "our last write" from "user edits."
Themes are CSS blocks in src/ui/src/styles/theme.css, listed in BUILTIN_THEME_META. There is no runtime theme loading path — every theme ships in the bundle.
Settings has two existing plugin sections: Plugins (Claudette's own Lua plugins, always visible) and Claude Code Plugins (gated by pluginManagementEnabled, the upstream Claude CLI marketplace).
utensils/claudette-community is scaffolded with kind directories (themes/, plugins/{scm,env-providers,language-grammars}/, …), a registry.json stub, contribution guides, and issue/PR templates. No CI, no runtime consumer.
Future State
Users open Settings → Community, browse contributions filtered by kind, click Install, and the contribution is hash-verified, written to disk, and immediately usable.
Themes installed at runtime appear in the theme picker alongside built-ins.
Plugins installed at runtime are picked up by the existing plugin discovery — same ~/.claudette/plugins/<name>/ path, no plugin-runtime changes.
Auto-update is off; updates surface as badges, applied on user click.
Capability changes during update (a plugin requesting a new CLI, a theme adding @import) require fresh user consent — never silent.
Revoked contributions (security-flagged in revocations.json) are uninstalled at app start and shown in a "Removed" history.
A power-user Direct install form takes <git-url>@<sha>; the result is clearly marked Unverified.
Plugin authors can develop in their own repos; only a small registry-PR is needed to publish a new version.
Not in Scope
Author signing (cosign / sigstore). Trust is registry-PR review + commit-SHA + sha256. Reserve a signature field in the schema for v2.
Private/org registries. Reserve a customRegistryUrls settings field but ship only the default utensils/claudette-community URL.
Multiple versions per contribution. v1 surfaces only the latest. Schema includes version so a future v2 can pin.
Auto-update. Off. Per-contribution opt-in is v2.
Slash-commands and MCP-recipes. Schema reserves slots; surface ships disabled. The kinds get their own follow-up TDDs once the underlying systems are runtime-loadable.
Bundled plugin migration. The existing seed-from-include_str! flow stays. New community-installed contributions live alongside; no consolidation.
A content-driven registry: claudette-community is the source of truth and registry.json (CI-generated) is its index. Claudette fetches the index over HTTPS, lets the user browse, then per-contribution downloads a sha256-pinned tarball, verifies it, and writes to ~/.claudette/{plugins,themes}/. Existing plugin discovery picks up installed plugins with no runtime changes. A new theme-loader subsystem injects user-installed CSS at app startup. The whole surface sits behind a communityRegistryEnabled feature flag.
The design preserves #537's invariants: hash-pinning end-to-end, capability re-consent on update, opt-in auto-update, signed revocation list, namespace policy enforced at PR time.
source.type: "in-tree" covers themes, grammars, and any plugin authored inside claudette-community. The mirror cron is unnecessary for these — they're already in the repo.
source.type: "external" covers plugins authored in their own repos. The mirror cron in claudette-community keeps a tarball copy at mirrors/<sha>.tar.gz, so upstream deletion does not break installation.
sha256 is computed against a deterministic tarball (sorted entries, fixed mtimes). The generator script is responsible for determinism. Verifying at install time means tampering with main between registry generation and download is detected.
version is semver, contributor-declared. Used only for display in v1.
author ties to a verified GitHub user/org; the validator script checks the GitHub Profile API at PR time to prevent typosquatting against unrelated users.
Distribution / fetching
Registry:https://raw.githubusercontent.com/utensils/claudette-community/main/registry.json — fetched with If-None-Match ETag, cached at ~/.claudette/registry-cache/registry.json with a 1h TTL. Stale-but-readable on network failure.
In-tree contribution: download https://codeload.github.com/utensils/claudette-community/tar.gz/<source.sha>, extract only files under <source.path>/, verify sha256 matches.
External plugin: download https://raw.githubusercontent.com/utensils/claudette-community/<registry-source-sha>/<source.mirror_path>, verify sha256. The mirror is the canonical fetch path — we never fetch directly from the upstream repo at install time. (The mirror cron does that; if upstream is gone, the mirror is what we have.)
Crucially: no git binary required. Reasoned alternative to #537's runtime-git concern: tarballs over HTTPS via reqwest, decompressed with tar, hashed with sha2. All three are pure-Rust, low-dep, cross-platform.
community_check_updates walks .install_meta.json files, compares contribution_sha against the current registry, returns UpdateAvailable { kind, name, current_version, new_version, capability_diff }.
UI shows badges + a per-contribution "Update" button.
On click, frontend calls community_update. If capability_diff is non-empty, the backend returns Err("CAPABILITY_CHANGE_REQUIRES_CONSENT") unless accept_capability_changes: true was passed.
UI shows a modal with the diff ("This plugin now requests: git. Continue?"); user confirms; frontend retries with accept_capability_changes: true.
Polled at app start, 24h cache, last-known-good fallback.
"Can't be empty" sanity check (an empty revocations file is treated as a fetch failure, not as "everything is fine") prevents an attacker from silencing the system by truncating the file.
Manual override: a claudette community revocations skip <name> debug command exists for self-host scenarios.
Settings UI
New section CommunitySettings.tsx, route key community, sibling to the existing Plugins and Claude Code Plugins sections.
Three tabs:
Browse — search/filter by kind, install button, "Installed" badge on already-installed contributions.
For each contribution dir, reads the manifest, runs git log -1 --format=%H -- <path> for the per-dir SHA.
Builds a deterministic tarball (sorted entries, fixed mtimes), sha256s it.
For external plugins, fetches the upstream tarball by SHA, verifies, writes to mirrors/.
Emits registry.json.
scripts/validate.ts:
JSON-schema-validates each manifest against the schema mirrored from PluginManifest / LanguageContribution / theme metadata.
Verifies author resolves to a real GitHub user/org.
Verifies license is on the allowlist (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, MPL-2.0).
Lints Lua syntax (using luau-cli if available; otherwise a basic regex pre-check).
GH Actions:
validate.yml (PR): runs validator + generator; asserts no diff vs committed registry.json. Fails with a "run bun scripts/generate-registry.ts and commit the result" hint.
regen.yml (push to main): regenerates and commits via a bot PR if needed.
mirror.yml (hourly cron): for any external plugin, refreshes its mirror tarball from the upstream git_ref if the resolved SHA changes; opens a PR with the registry diff for human review.
Schema source-of-truth synchronization
The Rust types in src/plugin_runtime/manifest.rs are canonical. Whenever they change, claudette-community/registry.schema.json must be regenerated. To prevent drift:
Add a Cargo build-time check that emits registry.schema.json from the Rust types via schemars.
A CI job in Claudette uploads the regenerated schema as an artifact; a CI job in claudette-community downloads the latest schema from Claudette's main branch and asserts it matches the committed copy.
Feature flag
communityRegistryEnabled — boolean in app settings (experimental section). Default: false. Mirrors the existing pluginManagementEnabled pattern. Flag controls visibility of the Settings → Community section. The backend module ships unconditionally; only the UI surface is gated. This lets us merge incrementally without exposing half-built UX.
Cross-platform considerations
Path resolution via dirs::data_dir() → ~/.claudette/ on macOS/Linux, %APPDATA%\claudette\ on Windows.
Tarball extraction normalizes path separators; rejects paths containing .. or absolute prefixes (defense against path-traversal in malicious tarballs — same protection crate::grammar_provider already enforces).
Symlinks in tarballs: rejected.
File permissions: extracted files chmod'd to 0644 regardless of tarball metadata.
Performance considerations
Registry fetch: ~50KB compressed in v1, under 1MB even with hundreds of contributions. Negligible.
Mirror tarballs: per-plugin, typically <100KB. Bounded.
Theme runtime loading at app start: synchronous file reads + CSS injection happen before first paint. With 10 user themes that's ~10 small files; well under the 2s cold-start target.
Plugin install: a single tarball download + verify + extract + write. Seconds.
After PR #4, the feature flag flips to default-on in nightly and ships in the next release.
Slash-commands and MCP-recipes are deliberately deferred — the underlying runtime systems aren't fully content-loadable today. Each gets its own follow-up TDD.
Monitoring / Telemetry
Mandatory
None — Claudette has no telemetry without opt-in. All observability is local logs.
Nice to have
tracing events at install / update / uninstall / revocation-applied, written to ~/.claudette/logs/community.log.
A debug command community status that dumps the current cached registry, list of installed contributions, pending updates, and last revocation poll. Hooks into the existing /claudette-debug skill.
Cold-start budget: include theme runtime loading in the existing startup-timing trace.
Testing
Use cases
#
Use case
Expected outcome
1
User opens Settings → Community for the first time
Registry fetched, browse list populated, no installs yet
2
User installs a theme
Tarball downloaded, sha256 verified, files written to ~/.claudette/themes/<id>/, theme appears in picker without restart
3
User installs a plugin
Files written to ~/.claudette/plugins/<name>/, existing plugin discovery picks it up, plugin appears in Plugins settings
4
User installs an external plugin
Mirror tarball fetched (not the upstream), sha256 verified
5
sha256 mismatch on download
Install rejected with a clear error; nothing written to disk
6
Plugin update with same capabilities
Update applied silently on user click
7
Plugin update with new required_clis
Capability-diff modal shown; user must confirm
8
App start with revoked plugin installed
Plugin uninstalled, "Removed" entry shown in UI
9
App start with stale registry cache + offline
Stale cache used; UI shows "Last updated "
10
Direct install of <git-url>@<sha>
Tarball fetched from codeload.github.com/<repo>/tar.gz/<sha>, verified, marked Unverified
CONTRIBUTING.md updates — license allowlist, namespace policy, version bumping rules.
Other
No backfills. No infrastructure beyond the GH Actions above. Sanity test the mirror cron against a sample external plugin before merging PR chore(main): release claudette 0.2.0 #4.
Open Questions
color_scheme: "auto" for themes — should themes be able to declare they follow OS dark/light mode? Recommend: defer; today's dark/light is sufficient.
Multiple versions per contribution — schema supports version but v1 surfaces only the latest. Is "pin to specific version" worth shipping in v1, or v2? Recommend: v2.
Auto-update granularity — once we ship opt-in auto-update, is it per-contribution or one global toggle? Recommend: per-contribution, defaulting off.
Tarball mirror size cap — should mirror.yml reject upstream tarballs over a threshold (e.g. 5MB) to keep claudette-community lean? Recommend: yes, hard cap of 5MB for plugins, 1MB for grammars.
Direct-install discovery — should there be an in-app way to share a <git-url>@<sha> link (deep link), or is paste-only fine? Recommend: paste-only for v1; revisit if users ask.
Should bundled plugins migrate to community-installed? — they currently ship via include_str! and reseed on every app start. Migrating them would consolidate the install path but breaks "works offline on first launch." Recommend: keep bundled plugins bundled; community contributions live alongside.
Author signing in v2 — when we add it, should it use an existing standard (cosign / minisign / SSH signed tags) or a Claudette-specific scheme? Recommend: SSH signed tags (least new infrastructure, GitHub already supports it).
TDD: Community Registry — discover and install themes, plugins, and grammars
Introduction
Claudette has a sandboxed plugin runtime and a built-in theme system, but no path for users to discover and install third-party contributions. The bundled set is small (4 env-providers, 2 SCMs, 1 grammar, ~12 themes) and grows only at Claudette's release cadence. This TDD designs the community registry — the in-app surface that browses, installs, updates, and revokes user-contributed themes, plugins, and grammars from
utensils/claudette-community.Converges: #537 — third-party plugin distribution. The discussion in #537 weighed four distribution models and the author's stated bias was "Option 4 layered on Option 3 mechanics, with a mirroring cron in the registry repo so we don't depend on upstream availability." This TDD adopts that recommendation, expands scope to themes + grammars + future kinds (not just plugins), and commits to concrete schemas and phasing.
Related: #536 (plugin runtime capabilities), #237 (Plugin Management UI redesign).
Resources
utensils/claudette-community— content repo (already scaffolded)src/plugin_runtime/manifest.rs—PluginManifest,PluginKind,LanguageContribution,GrammarContribution(schema source of truth)src/plugin_runtime/seed.rs— existing.content_hashsentinel pattern (the trust anchor we extend)src/ui/src/styles/themes/index.ts—BuiltinThemeMeta(theme metadata shape)src/ui/src/components/settings/sections/PluginsSettings.tsxandClaudeCodePluginsSettings.tsx— existing Settings sections we sit alongsideGlossary
theme,plugin:scm,plugin:env-provider,plugin:language-grammar,slash-command(future),mcp-recipe(future)registry.jsonindex file atclaudette-community/registry.json, generated by CIclaudette-communityitself (themes, grammars, the simple plugins)git_url + sha + sha256, with a tarball mirror inclaudette-communityfor resilience<git-url>@<sha>, marked Unverified in UIinclude_str!+.content_hash. Tomorrow: registry-pinnedsha256verified at install timeCurrent State
src/plugin_runtime/seed.rsbakes bundled plugins viainclude_str!and reseeds them to~/.claudette/plugins/<name>/on startup, with a.content_hashsentinel that distinguishes "our last write" from "user edits."plugins/scm-{github,gitlab},plugins/env-{direnv,mise,dotenv,nix-devshell},plugins/lang-nix.src/ui/src/styles/theme.css, listed inBUILTIN_THEME_META. There is no runtime theme loading path — every theme ships in the bundle.pluginManagementEnabled, the upstream Claude CLI marketplace).utensils/claudette-communityis scaffolded with kind directories (themes/,plugins/{scm,env-providers,language-grammars}/, …), aregistry.jsonstub, contribution guides, and issue/PR templates. No CI, no runtime consumer.Future State
~/.claudette/plugins/<name>/path, no plugin-runtime changes.@import) require fresh user consent — never silent.revocations.json) are uninstalled at app start and shown in a "Removed" history.<git-url>@<sha>; the result is clearly marked Unverified.Not in Scope
signaturefield in the schema for v2.customRegistryUrlssettings field but ship only the defaultutensils/claudette-communityURL.versionso a future v2 can pin.include_str!flow stays. New community-installed contributions live alongside; no consolidation.required_clisallowlist is the capability surface.Technical Design
Summary
A content-driven registry:
claudette-communityis the source of truth andregistry.json(CI-generated) is its index. Claudette fetches the index over HTTPS, lets the user browse, then per-contribution downloads asha256-pinned tarball, verifies it, and writes to~/.claudette/{plugins,themes}/. Existing plugin discovery picks up installed plugins with no runtime changes. A new theme-loader subsystem injects user-installed CSS at app startup. The whole surface sits behind acommunityRegistryEnabledfeature flag.The design preserves #537's invariants: hash-pinning end-to-end, capability re-consent on update, opt-in auto-update, signed revocation list, namespace policy enforced at PR time.
Architecture
Registry index schema (
registry.jsonv1){ "$schema": "https://raw.githubusercontent.com/utensils/claudette-community/main/registry.schema.json", "version": 1, "generated_at": "2026-05-01T12:00:00Z", "source": { "repo": "utensils/claudette-community", "ref": "main", "sha": "0b48c36..." }, "themes": [ { "id": "catppuccin-mocha", "name": "Catppuccin Mocha", "description": "Pastel dark theme", "color_scheme": "dark", "accent_preview": "#cba6f7", "version": "1.0.0", "author": "github-username", "license": "MIT", "tags": ["pastel", "popular"], "submitted_at": "2026-04-12", "path": "themes/catppuccin-mocha", "sha": "<commit sha last touching this directory>", "sha256": "<sha256 of deterministic tarball of this directory at sha>" } ], "plugins": { "scm": [ { "name": "forgejo", "display_name": "Forgejo", "description": "Forgejo PR / CI status", "version": "1.0.0", "kind": "scm", "required_clis": ["forgejo"], "remote_patterns": ["codeberg.org"], "author": "github-username", "license": "MIT", "submitted_at": "2026-04-20", "source": { "type": "in-tree", "path": "plugins/scm/forgejo", "sha": "<commit sha>", "sha256": "<sha256>" } }, { "name": "linear", "version": "1.2.3", "kind": "scm", "author": "thirdparty-username", "license": "Apache-2.0", "source": { "type": "external", "git_url": "https://github.com/thirdparty/claudette-linear.git", "git_ref": "v1.2.3", "sha": "<full commit sha at that ref>", "sha256": "<sha256 of mirror tarball>", "mirror_path": "mirrors/thirdparty-claudette-linear-<sha>.tar.gz" } } ], "env-provider": [], "language-grammar": [] }, "slash_commands": [], "mcp_recipes": [] }Schema notes:
source.type: "in-tree"covers themes, grammars, and any plugin authored insideclaudette-community. The mirror cron is unnecessary for these — they're already in the repo.source.type: "external"covers plugins authored in their own repos. The mirror cron inclaudette-communitykeeps a tarball copy atmirrors/<sha>.tar.gz, so upstream deletion does not break installation.sha256is computed against a deterministic tarball (sorted entries, fixed mtimes). The generator script is responsible for determinism. Verifying at install time means tampering withmainbetween registry generation and download is detected.versionis semver, contributor-declared. Used only for display in v1.authorties to a verified GitHub user/org; the validator script checks the GitHub Profile API at PR time to prevent typosquatting against unrelated users.Distribution / fetching
Registry:
https://raw.githubusercontent.com/utensils/claudette-community/main/registry.json— fetched withIf-None-MatchETag, cached at~/.claudette/registry-cache/registry.jsonwith a 1h TTL. Stale-but-readable on network failure.In-tree contribution: download
https://codeload.github.com/utensils/claudette-community/tar.gz/<source.sha>, extract only files under<source.path>/, verifysha256matches.External plugin: download
https://raw.githubusercontent.com/utensils/claudette-community/<registry-source-sha>/<source.mirror_path>, verifysha256. The mirror is the canonical fetch path — we never fetch directly from the upstream repo at install time. (The mirror cron does that; if upstream is gone, the mirror is what we have.)Crucially: no
gitbinary required. Reasoned alternative to #537's runtime-gitconcern: tarballs over HTTPS viareqwest, decompressed withtar, hashed withsha2. All three are pure-Rust, low-dep, cross-platform.Backend module structure
Tauri command surface (
src-tauri/src/commands/community.rs)Theme runtime loading (the largest new subsystem)
Today,
BUILTIN_THEME_METAis a static array andtheme.cssis a build-time CSS file. To support runtime themes:Backend (
src/community/themes_user.rs):~/.claudette/themes/*/{theme.json, theme.css}.theme.jsonintoUserThemeMeta { id, name, description, color_scheme, accent_preview, css_content }.themes_user_listTauri command.Frontend (boot order in
App.tsx):await invoke("themes_user_list").<style data-theme-source="user" data-theme-id="<id>">{sanitized_css}</style>into<head>.BUILTIN_THEME_METAwith user themes for the picker (useThemes()hook).Sanitization (defense in depth — same trust model as plugins, but CSS has its own exfiltration vectors):
@importrules.url(http://...)/url(https://...)references (background-image, font-face).url(data:...)only if the data URI is under 100KB.Picker UX: built-in vs user-installed themes shown in two groups, with a small "Community" badge on user themes.
Storage layout
.install_meta.jsonshape:{ "source": "community", // "community" | "direct" | "bundled" "registry_sha": "<registry source.sha at install time>", "contribution_sha": "<source.sha>", "sha256": "<verified hash>", "installed_at": "2026-05-01T12:00:00Z", "granted_capabilities": ["forgejo"], // for plugins: required_clis at install "version": "1.0.0" }Update flow with capability re-consent
community_check_updateswalks.install_meta.jsonfiles, comparescontribution_shaagainst the current registry, returnsUpdateAvailable { kind, name, current_version, new_version, capability_diff }.community_update. Ifcapability_diffis non-empty, the backend returnsErr("CAPABILITY_CHANGE_REQUIRES_CONSENT")unlessaccept_capability_changes: truewas passed.git. Continue?"); user confirms; frontend retries withaccept_capability_changes: true.Revocation flow
revocations.jsonschema:{ "version": 1, "generated_at": "...", "signature": "<future: ed25519 sig over body>", "entries": [ { "kind": "plugin:scm", "name": "linear", "sha": "<bad sha>", "reason": "Exfiltrated workspace path to remote endpoint", "revoked_at": "2026-05-30" } ] }claudette community revocations skip <name>debug command exists for self-host scenarios.Settings UI
New section
CommunitySettings.tsx, route keycommunity, sibling to the existing Plugins and Claude Code Plugins sections.Three tabs:
<git-url>@<sha>form, large "Unverified — you are responsible for what this code does" banner.Gated by
communityRegistryEnabledexperimental flag (default off until v1 is stable in nightly).Generator + CI on
claudette-communityscripts/generate-registry.ts(Bun, since the repo is content-only and Bun is the default):themes/,plugins/{scm,env-providers,language-grammars}/(skipping_example/).git log -1 --format=%H -- <path>for the per-dir SHA.mirrors/.registry.json.scripts/validate.ts:PluginManifest/LanguageContribution/ theme metadata.authorresolves to a real GitHub user/org.licenseis on the allowlist (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, MPL-2.0).luau-cliif available; otherwise a basic regex pre-check).GH Actions:
validate.yml(PR): runs validator + generator; asserts no diff vs committedregistry.json. Fails with a "runbun scripts/generate-registry.tsand commit the result" hint.regen.yml(push to main): regenerates and commits via a bot PR if needed.mirror.yml(hourly cron): for any external plugin, refreshes its mirror tarball from the upstreamgit_refif the resolved SHA changes; opens a PR with the registry diff for human review.Schema source-of-truth synchronization
The Rust types in
src/plugin_runtime/manifest.rsare canonical. Whenever they change,claudette-community/registry.schema.jsonmust be regenerated. To prevent drift:registry.schema.jsonfrom the Rust types viaschemars.claudette-communitydownloads the latest schema from Claudette's main branch and asserts it matches the committed copy.Feature flag
communityRegistryEnabled— boolean in app settings (experimentalsection). Default: false. Mirrors the existingpluginManagementEnabledpattern. Flag controls visibility of the Settings → Community section. The backend module ships unconditionally; only the UI surface is gated. This lets us merge incrementally without exposing half-built UX.Cross-platform considerations
dirs::data_dir()→~/.claudette/on macOS/Linux,%APPDATA%\claudette\on Windows...or absolute prefixes (defense against path-traversal in malicious tarballs — same protectioncrate::grammar_provideralready enforces).0644regardless of tarball metadata.Performance considerations
ERD (storage / state)
Release Plan
Four PRs, each independently mergeable behind the feature flag. Each phase ends in something user-visible.
claudette-communitygenerator + validator + CI. No Claudette code touched. End state: every PR toclaudette-communityvalidates manifests, regeneratesregistry.json, and the file is committed. Mirror cron stubbed for external plugins (none yet exist). Addsregistry.schema.json(hand-written for v1; schemars-generated in PR ci: add GitHub Actions for Rust CI, conventional commits, and release please #2).src/community/module (registry fetch + install + verify),community_*Tauri commands, Settings → Community tab (Browse + Installed + Direct install). Themes excluded — install only writes to~/.claudette/plugins/.communityRegistryEnabledflag added. Existing plugin discovery picks up installed plugins; no plugin runtime changes.src/community/themes_user.rs, frontend theme loader + CSS sanitizer, theme picker integration, "Community" badge, install path extended to~/.claudette/themes/.community_check_updates,community_update, capability-diff modal, revocations fetcher + apply-at-startup. Completes the supply-chain story.After PR #4, the feature flag flips to default-on in nightly and ships in the next release.
Slash-commands and MCP-recipes are deliberately deferred — the underlying runtime systems aren't fully content-loadable today. Each gets its own follow-up TDD.
Monitoring / Telemetry
Mandatory
None — Claudette has no telemetry without opt-in. All observability is local logs.
Nice to have
tracingevents at install / update / uninstall / revocation-applied, written to~/.claudette/logs/community.log.community statusthat dumps the current cached registry, list of installed contributions, pending updates, and last revocation poll. Hooks into the existing/claudette-debugskill.Testing
Use cases
~/.claudette/themes/<id>/, theme appears in picker without restart~/.claudette/plugins/<name>/, existing plugin discovery picks it up, plugin appears in Plugins settingsrequired_clis<git-url>@<sha>codeload.github.com/<repo>/tar.gz/<sha>, verified, marked Unverifiedurl(https://evil.com/track.png)..path entry~/.claudette/themes/<id>/, theme picker reverts to previous active theme if it was activerevocations.jsonreturnedTesting notes
verify.rs(sha256 mismatches, path traversal, symlink rejection),revocations.rs(signature, empty-file sanity check),themes_user.rs(sanitizer).wiremockcrate) covering install/update/uninstall + capability re-consent./claudette-debug: install all bundled-equivalent contributions through the registry, verify behavior matches build-time bundling.Steps to Completion
Claudette repo
src/community/module withregistry.rs,install.rs,verify.rs,revocations.rs,meta.rs.src/community/themes_user.rs(PR Terminal emulation library #3).src-tauri/src/commands/community.rsTauri command wrappers.experimental.communityRegistryEnabledto settings.reqwest,tar,flate2,sha2,schemars(build-only).src/ui/src/components/settings/sections/CommunitySettings.tsx+ Browse / Installed / Direct-install tabs.src/ui/src/utils/userThemes.ts, integration inApp.tsx.src/ui/src/components/settings/CapabilityDiffDialog.tsx.schemarsjob that emitsregistry.schema.jsonand uploads it as a CI artifact.claudette-communityreposcripts/generate-registry.ts— deterministic tarball + sha256 + per-dir git SHA + JSON emission.scripts/validate.ts— manifest schema validation + author/license checks + Lua syntax pre-check..github/workflows/validate.yml— per-PR validator + generator-diff check..github/workflows/regen.yml— regen on push to main, bot-PR if needed..github/workflows/mirror.yml— hourly cron for external plugin mirrors.registry.schema.json— initial hand-written schema; replaced by Claudette-generated artifact in PR ci: add GitHub Actions for Rust CI, conventional commits, and release please #2.CONTRIBUTING.mdupdates — license allowlist, namespace policy, version bumping rules.Other
Open Questions
color_scheme: "auto"for themes — should themes be able to declare they follow OS dark/light mode? Recommend: defer; today'sdark/lightis sufficient.versionbut v1 surfaces only the latest. Is "pin to specific version" worth shipping in v1, or v2? Recommend: v2.mirror.ymlreject upstream tarballs over a threshold (e.g. 5MB) to keepclaudette-communitylean? Recommend: yes, hard cap of 5MB for plugins, 1MB for grammars.<git-url>@<sha>link (deep link), or is paste-only fine? Recommend: paste-only for v1; revisit if users ask.include_str!and reseed on every app start. Migrating them would consolidate the install path but breaks "works offline on first launch." Recommend: keep bundled plugins bundled; community contributions live alongside.