feat(kiloclaw) "one click" install of a ClawByte into KiloClaw instance#3651
feat(kiloclaw) "one click" install of a ClawByte into KiloClaw instance#3651St0rmz1 wants to merge 20 commits into
Conversation
…HTTP
Lays the foundation for app.kilo.ai 1-click install of ClawBytes from
kilo.ai. Not yet wired end-to-end (no tRPC mutation, no client
dispatch, no cookie-through-provisioning); this stages the
verification, registry, and per-user chat-injection HTTP surface.
apps/web (cloud Next.js):
- lib/kiloclaw/install-sources.ts: generalized source registry. v1
ships a single `byte` source mapped to kilo.ai's data.json
endpoint; KILO_AI_BASE_URL overrides for local dev. Adding a new
source = appending one map entry.
- lib/kiloclaw/install.ts: fetchInstallPayload(source, slug) does
Zod parse + Ed25519 signature verify against pinned
CLAWBYTE_SIGNING_PUBLIC_KEY. Rejects on missing key, kid
mismatch, expired/future signedAt, version drift, or tampered
envelope. Returns null on hard rejects so callers can't fall back
to unsigned content.
- lib/kiloclaw/install.test.ts: 11 cases covering happy ph,
upstream 4xx/5xx, kid mismatch, tampered prompt/title, version
drift, age window, unsigned payloads, and unconfigured pubkey.
- app/(app)/claw/install/[source]/[slug]/page.tsx +
InstallClient.tsx: server route fetches the payload behind the
existing claw-layout auth gate; client shell renders a preview
with title/description and a placeholder for the dispatch CTA.
services/kilo-chat (Cloudflare Worker):
- routes/internal.ts + auth-internal.ts: new /internal/v1/post-
message-as-user HTTP endpoint, gated by an x-internal-api-key
header verified against INTERNAL_API_SECRET. Thin wrapper around
the existing postMessageAsUser RPC so non-Worker callers (the
cloud Next.js app) can deliver an as-user chat turn without
needing a service binding.
- wrangler.jsonc: adds the INTERNAL_API_SECRET secrets-store
binding (shared store, prod secret).
- worker-configuration.d.ts: regenerated to include the new
binding.
the plan wandered, bring it back inline with version 1 plan
install conversation
payload signature
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the latest commit (re-sync Resolved Issues (from prior commits)
Files Reviewed (25 files)
Reviewed by claude-sonnet-4.6 · 211,645 tokens Review guidance: REVIEW.md from base branch |
after messageTextSchema
| }); | ||
| } | ||
|
|
||
| const instance = await deps.getActiveInstance(userId); |
There was a problem hiding this comment.
Must-fix: This dispatch picks getActiveInstance(userId) after generic clawAccessProcedure access, but those are not bound to the same billing anchor. In an inconsistent state, such as a current subscription anchored to a destroyed or different row while an orphaned active row remains, the gate can pass and this sends a prompt into an unentitled runtime. Require access for this exact instance before resolving sandbox/chat, for example with requireKiloClawAccessAtInstance(userId, instance.id), or reuse the existing personal anchor resolution and fail closed on mismatch.
| const url = `${getKiloChatBaseUrl()}/internal/v1/post-message-as-user`; | ||
| let res: Response; | ||
| try { | ||
| res = await fetch(url, { |
There was a problem hiding this comment.
Suggested/nit: This secret-bearing fetch uses a base URL from NEXT_PUBLIC_KILO_CHAT_URL and follows redirects by default. A misconfigured or redirecting destination can send the internal API key and prompt outside kilo-chat. Prefer a server-only internal URL or allowlisted origin, and set redirect: 'error' before attaching the key.
| <KiloClawbsterIcon className="h-4 w-auto" /> | ||
| {sourceLabel} | ||
| </Badge> | ||
| <CardTitle className="mt-2 text-xl">{payload.title}</CardTitle> |
There was a problem hiding this comment.
Suggested/nit: payload.title and payload.description are external signed strings and can contain long unbroken tokens. On narrow screens, they can overflow this fixed-width card instead of reflowing. Add break-words or overflow-wrap:anywhere to both text blocks.
| <Card className="w-full text-left"> | ||
| <CardHeader> | ||
| <Badge variant="secondary" className="w-fit gap-1.5"> | ||
| <KiloClawbsterIcon className="h-4 w-auto" /> |
There was a problem hiding this comment.
Suggested/nit: This badge icon is decorative, but the SVG is exposed to assistive tech without a label. Mark this use aria-hidden="true" and focusable="false" by passing those props through KiloClawbsterIcon, or hide the SVG at this call site.
Summary
Adds an install flow for ClawBytes so a user can go from a byte in the catalog to running it in their KiloClaw chat with one confirmation.
claw/install/[source]/[slug]renders a confirmation card showing the byte title and description with Confirm Install and Cancel actions. The page fetches and verifies on the server, and dispatches only on an explicit Confirm click through a POST mutation, so loading the URL by itself never runs anything.bytemaps to the catalogdata.json). The host always comes from the registry rather than user input, the slug is percent encoded into one path segment, and redirects are not followed.forceNewConversationoption on the shared message primitive) instead of appending to the user's most recent chat, and then navigates straight into that conversation.POST /internal/v1/post-message-as-user) guarded by a shared secret, because the app and the chat service run on different platforms and cannot use a service binding.Verification
Tested locally against a running app, catalog, chat service, and a provisioned instance.
Visual Changes
Reviewer Notes
Suggested focus areas:
apps/web/src/lib/kiloclaw/install.tsandinstall-dispatch.ts.