create-kai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +311 -0
- package/dist/index.js +4433 -0
- package/dist/templates/angular/_gitignore +7 -0
- package/dist/templates/angular/angular.json +53 -0
- package/dist/templates/angular/package.json +30 -0
- package/dist/templates/angular/src/app/app.config.ts +11 -0
- package/dist/templates/angular/src/app/app.css +36 -0
- package/dist/templates/angular/src/app/app.html +51 -0
- package/dist/templates/angular/src/app/app.ts +78 -0
- package/dist/templates/angular/src/app/components/composer/composer.css +31 -0
- package/dist/templates/angular/src/app/components/composer/composer.html +18 -0
- package/dist/templates/angular/src/app/components/composer/composer.ts +81 -0
- package/dist/templates/angular/src/app/components/icons/moon-icon/moon-icon.css +3 -0
- package/dist/templates/angular/src/app/components/icons/moon-icon/moon-icon.html +12 -0
- package/dist/templates/angular/src/app/components/icons/moon-icon/moon-icon.ts +15 -0
- package/dist/templates/angular/src/app/components/icons/sun-icon/sun-icon.css +3 -0
- package/dist/templates/angular/src/app/components/icons/sun-icon/sun-icon.html +14 -0
- package/dist/templates/angular/src/app/components/icons/sun-icon/sun-icon.ts +13 -0
- package/dist/templates/angular/src/app/components/sidebar/sidebar.css +24 -0
- package/dist/templates/angular/src/app/components/sidebar/sidebar.html +12 -0
- package/dist/templates/angular/src/app/components/sidebar/sidebar.ts +43 -0
- package/dist/templates/angular/src/app/components/theme-toggle/theme-toggle.html +13 -0
- package/dist/templates/angular/src/app/components/theme-toggle/theme-toggle.ts +21 -0
- package/dist/templates/angular/src/app/components/thread-view/thread-view.css +6 -0
- package/dist/templates/angular/src/app/components/thread-view/thread-view.html +6 -0
- package/dist/templates/angular/src/app/components/thread-view/thread-view.ts +45 -0
- package/dist/templates/angular/src/app/state/chat.store.ts +47 -0
- package/dist/templates/angular/src/app/state/conversations.store.ts +42 -0
- package/dist/templates/angular/src/app/state/voice-input.ts +57 -0
- package/dist/templates/angular/src/app/types.ts +2 -0
- package/dist/templates/angular/src/chat-data.ts +106 -0
- package/dist/templates/angular/src/index.html +16 -0
- package/dist/templates/angular/src/main.ts +16 -0
- package/dist/templates/angular/src/styles.css +10 -0
- package/dist/templates/angular/tsconfig.app.json +9 -0
- package/dist/templates/angular/tsconfig.json +22 -0
- package/dist/templates/nextjs/_gitignore +11 -0
- package/dist/templates/nextjs/app/chat-data.ts +125 -0
- package/dist/templates/nextjs/app/components/Composer.tsx +88 -0
- package/dist/templates/nextjs/app/components/HydrationBadge.tsx +68 -0
- package/dist/templates/nextjs/app/components/Sidebar.tsx +48 -0
- package/dist/templates/nextjs/app/components/ThemeToggle.tsx +27 -0
- package/dist/templates/nextjs/app/components/ThreadView.tsx +48 -0
- package/dist/templates/nextjs/app/components/icons/MoonIcon.tsx +25 -0
- package/dist/templates/nextjs/app/components/icons/SunIcon.tsx +25 -0
- package/dist/templates/nextjs/app/components/icons/index.ts +2 -0
- package/dist/templates/nextjs/app/globals.css +153 -0
- package/dist/templates/nextjs/app/hooks/index.ts +1 -0
- package/dist/templates/nextjs/app/hooks/useConversations.ts +42 -0
- package/dist/templates/nextjs/app/layout.tsx +50 -0
- package/dist/templates/nextjs/app/page.tsx +17 -0
- package/dist/templates/nextjs/app/theme.ts +10 -0
- package/dist/templates/nextjs/app/workspace.tsx +148 -0
- package/dist/templates/nextjs/next.config.mjs +22 -0
- package/dist/templates/nextjs/package.json +23 -0
- package/dist/templates/nextjs/postcss.config.mjs +16 -0
- package/dist/templates/nextjs/tsconfig.json +23 -0
- package/dist/templates/react/_gitignore +6 -0
- package/dist/templates/react/index.html +16 -0
- package/dist/templates/react/package.json +25 -0
- package/dist/templates/react/src/App.tsx +108 -0
- package/dist/templates/react/src/chat-data.ts +106 -0
- package/dist/templates/react/src/components/Composer.tsx +87 -0
- package/dist/templates/react/src/components/Sidebar.tsx +38 -0
- package/dist/templates/react/src/components/ThemeToggle.tsx +22 -0
- package/dist/templates/react/src/components/ThreadView.tsx +40 -0
- package/dist/templates/react/src/components/icons/MoonIcon.tsx +25 -0
- package/dist/templates/react/src/components/icons/SunIcon.tsx +25 -0
- package/dist/templates/react/src/components/icons/index.ts +2 -0
- package/dist/templates/react/src/hooks/index.ts +1 -0
- package/dist/templates/react/src/hooks/useConversations.ts +37 -0
- package/dist/templates/react/src/index.css +111 -0
- package/dist/templates/react/src/main.tsx +15 -0
- package/dist/templates/react/src/vite-env.d.ts +1 -0
- package/dist/templates/react/tsconfig.app.json +28 -0
- package/dist/templates/react/tsconfig.json +7 -0
- package/dist/templates/react/tsconfig.node.json +25 -0
- package/dist/templates/react/vite.config.ts +11 -0
- package/dist/templates/solid/_gitignore +6 -0
- package/dist/templates/solid/index.html +13 -0
- package/dist/templates/solid/package.json +23 -0
- package/dist/templates/solid/src/App.tsx +151 -0
- package/dist/templates/solid/src/chat-data.ts +234 -0
- package/dist/templates/solid/src/components/Composer.tsx +90 -0
- package/dist/templates/solid/src/components/Sidebar.tsx +36 -0
- package/dist/templates/solid/src/components/ThemeToggle.tsx +25 -0
- package/dist/templates/solid/src/components/ThreadView.tsx +122 -0
- package/dist/templates/solid/src/components/icons/index.tsx +84 -0
- package/dist/templates/solid/src/index.tsx +5 -0
- package/dist/templates/solid/src/lib/chat.ts +64 -0
- package/dist/templates/solid/src/lib/conversations.ts +54 -0
- package/dist/templates/solid/src/lib/types.ts +2 -0
- package/dist/templates/solid/src/styles.css +11 -0
- package/dist/templates/solid/tsconfig.json +18 -0
- package/dist/templates/solid/vite.config.ts +10 -0
- package/dist/templates/svelte/_gitignore +6 -0
- package/dist/templates/svelte/index.html +16 -0
- package/dist/templates/svelte/package.json +23 -0
- package/dist/templates/svelte/src/App.svelte +116 -0
- package/dist/templates/svelte/src/app.d.ts +20 -0
- package/dist/templates/svelte/src/chat-data.ts +106 -0
- package/dist/templates/svelte/src/components/Composer.svelte +102 -0
- package/dist/templates/svelte/src/components/Sidebar.svelte +61 -0
- package/dist/templates/svelte/src/components/ThemeToggle.svelte +45 -0
- package/dist/templates/svelte/src/components/ThreadView.svelte +49 -0
- package/dist/templates/svelte/src/components/icons/MoonIcon.svelte +24 -0
- package/dist/templates/svelte/src/components/icons/SunIcon.svelte +24 -0
- package/dist/templates/svelte/src/index.css +111 -0
- package/dist/templates/svelte/src/lib/chat.svelte.ts +52 -0
- package/dist/templates/svelte/src/lib/conversations.svelte.ts +46 -0
- package/dist/templates/svelte/src/lib/types.ts +2 -0
- package/dist/templates/svelte/src/lib/voiceInput.ts +57 -0
- package/dist/templates/svelte/src/main.ts +18 -0
- package/dist/templates/svelte/svelte.config.js +8 -0
- package/dist/templates/svelte/tsconfig.app.json +28 -0
- package/dist/templates/svelte/tsconfig.json +7 -0
- package/dist/templates/svelte/tsconfig.node.json +25 -0
- package/dist/templates/svelte/vite.config.ts +15 -0
- package/dist/templates/tanstack-start/_gitignore +14 -0
- package/dist/templates/tanstack-start/package.json +28 -0
- package/dist/templates/tanstack-start/serve.mjs +129 -0
- package/dist/templates/tanstack-start/serve.traversal.test.mjs +445 -0
- package/dist/templates/tanstack-start/src/chat-data.ts +124 -0
- package/dist/templates/tanstack-start/src/components/Composer.tsx +87 -0
- package/dist/templates/tanstack-start/src/components/HydrationBadge.tsx +63 -0
- package/dist/templates/tanstack-start/src/components/Sidebar.tsx +43 -0
- package/dist/templates/tanstack-start/src/components/ThemeToggle.tsx +26 -0
- package/dist/templates/tanstack-start/src/components/ThreadView.tsx +48 -0
- package/dist/templates/tanstack-start/src/components/icons/MoonIcon.tsx +25 -0
- package/dist/templates/tanstack-start/src/components/icons/SunIcon.tsx +25 -0
- package/dist/templates/tanstack-start/src/components/icons/index.ts +2 -0
- package/dist/templates/tanstack-start/src/hooks/index.ts +1 -0
- package/dist/templates/tanstack-start/src/hooks/useConversations.ts +42 -0
- package/dist/templates/tanstack-start/src/router.tsx +16 -0
- package/dist/templates/tanstack-start/src/routes/__root.tsx +62 -0
- package/dist/templates/tanstack-start/src/routes/index.tsx +135 -0
- package/dist/templates/tanstack-start/src/styles.css +153 -0
- package/dist/templates/tanstack-start/src/theme.ts +10 -0
- package/dist/templates/tanstack-start/tsconfig.json +16 -0
- package/dist/templates/tanstack-start/vite.config.ts +28 -0
- package/dist/templates/vanilla/_gitignore +6 -0
- package/dist/templates/vanilla/index.html +16 -0
- package/dist/templates/vanilla/package.json +19 -0
- package/dist/templates/vanilla/src/chat-data.ts +106 -0
- package/dist/templates/vanilla/src/index.css +111 -0
- package/dist/templates/vanilla/src/main.ts +53 -0
- package/dist/templates/vanilla/src/state.ts +119 -0
- package/dist/templates/vanilla/src/view.ts +222 -0
- package/dist/templates/vanilla/src/vite-env.d.ts +1 -0
- package/dist/templates/vanilla/src/voice-input.ts +55 -0
- package/dist/templates/vanilla/tsconfig.json +24 -0
- package/dist/templates/vanilla/vite.config.ts +14 -0
- package/dist/templates/vue/_gitignore +6 -0
- package/dist/templates/vue/index.html +16 -0
- package/dist/templates/vue/package.json +23 -0
- package/dist/templates/vue/src/App.vue +104 -0
- package/dist/templates/vue/src/chat-data.ts +106 -0
- package/dist/templates/vue/src/components/Composer.vue +96 -0
- package/dist/templates/vue/src/components/Sidebar.vue +48 -0
- package/dist/templates/vue/src/components/ThemeToggle.vue +27 -0
- package/dist/templates/vue/src/components/ThreadView.vue +50 -0
- package/dist/templates/vue/src/components/icons/MoonIcon.vue +24 -0
- package/dist/templates/vue/src/components/icons/SunIcon.vue +24 -0
- package/dist/templates/vue/src/composables/index.ts +3 -0
- package/dist/templates/vue/src/composables/useChat.ts +47 -0
- package/dist/templates/vue/src/composables/useConversations.ts +42 -0
- package/dist/templates/vue/src/composables/useVoiceInput.ts +57 -0
- package/dist/templates/vue/src/index.css +111 -0
- package/dist/templates/vue/src/main.ts +18 -0
- package/dist/templates/vue/src/types.ts +4 -0
- package/dist/templates/vue/src/vite-env.d.ts +1 -0
- package/dist/templates/vue/tsconfig.app.json +29 -0
- package/dist/templates/vue/tsconfig.json +7 -0
- package/dist/templates/vue/tsconfig.node.json +25 -0
- package/dist/templates/vue/vite.config.ts +24 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# create-kai
|
|
2
|
+
|
|
3
|
+
Scaffold a runnable [`@kitn.ai/ui`](https://ui.kitn.ai) chat app.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create kai@latest
|
|
7
|
+
npx create-kai my-app
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Press Enter through every prompt and you get React + full-screen + conversation
|
|
11
|
+
history + the kit's local mock: a project that streams a reply on the first
|
|
12
|
+
`npm run dev`, with no API key and no backend.
|
|
13
|
+
|
|
14
|
+
## Status
|
|
15
|
+
|
|
16
|
+
**First slice of v1.** A `ready` framework has been scaffolded, installed from
|
|
17
|
+
the registry, built by its own build script, and driven in a browser — a message
|
|
18
|
+
sent, and a reply streaming into `<kai-thread>`. Remaining frameworks, layouts
|
|
19
|
+
and gateways are declared in the tables but not offered.
|
|
20
|
+
|
|
21
|
+
**`create-kai --list` is the roster.** This paragraph used to name which
|
|
22
|
+
frameworks were ready, and it was wrong within a day of each one landing — so
|
|
23
|
+
the list lives in `src/frameworks.ts` and at runtime in `--list --json`, and
|
|
24
|
+
nowhere else.
|
|
25
|
+
|
|
26
|
+
Vue went first among the `registration: 'elements'` + `composedWorkspace: true`
|
|
27
|
+
cells and paid for the shared machinery: a framework could go `ready` with **no
|
|
28
|
+
patches at all** and the build would still print "2 patches verified", shipping
|
|
29
|
+
the kit's own example title and `nx build ui` into a user's project. That hole
|
|
30
|
+
is closed by the repo-internals check below, which reads the emitted output
|
|
31
|
+
instead of the patch list — and it is what caught Svelte and HTML on their first
|
|
32
|
+
`ready` build, naming every offending line, exactly as intended.
|
|
33
|
+
|
|
34
|
+
Each of the later cells then needed only its own patches. The one decision
|
|
35
|
+
neither React nor Vue had to make: the vanilla template's vite config describes
|
|
36
|
+
ITSELF ("unlike the React/Vue examples", "this is the showcase"), which is true
|
|
37
|
+
of a starter in `examples/` and false of the project a user just scaffolded.
|
|
38
|
+
Nothing in the repo-internals list catches that — it is not an unfollowable
|
|
39
|
+
instruction, just a sentence about the wrong project — so that patch rewrites
|
|
40
|
+
the paragraph rather than stopping short of it.
|
|
41
|
+
|
|
42
|
+
## Gateways
|
|
43
|
+
|
|
44
|
+
`mock` needs nothing. A real gateway needs a **server route**, because its key
|
|
45
|
+
must not reach the browser — `keyExposure: 'needs-proxy'` on the integration is
|
|
46
|
+
what says so. So a gateway is wirable per **(gateway, framework) cell**, never
|
|
47
|
+
per gateway: the integration has to have a route, and the framework has to
|
|
48
|
+
declare somewhere to put it (`FrameworkDef.route`).
|
|
49
|
+
|
|
50
|
+
`create-kai --list --json` reports both axes, including which frameworks each
|
|
51
|
+
wired gateway can be scaffolded onto. Read that, not the paragraphs below —
|
|
52
|
+
they explain the *shape* of the remaining work, and the counts move.
|
|
53
|
+
|
|
54
|
+
**Wired today: `openrouter` and `anthropic`,** on the frameworks whose route
|
|
55
|
+
host is declared.
|
|
56
|
+
|
|
57
|
+
`openrouter` was the first because it needs the least: `deps.npm` is empty (the
|
|
58
|
+
route is global `fetch`, no SDK), it returns `openai-sse` and forwards
|
|
59
|
+
`upstream.body` unchanged, and its handler calls none of the kit's content
|
|
60
|
+
helpers.
|
|
61
|
+
|
|
62
|
+
`anthropic` was listed here as *cheap but blocked* until the kit exported
|
|
63
|
+
`chatRoutePreamble` — its route calls `wireParts` / `wireText` to re-map
|
|
64
|
+
attachments, and those declarations were private. It now costs one line, and
|
|
65
|
+
two facts made that true which neither `streamFormat` nor any other field would
|
|
66
|
+
tell you:
|
|
67
|
+
|
|
68
|
+
- Its route **re-frames to OpenAI SSE** (`reframeToOpenAISse`) before returning,
|
|
69
|
+
so the emitted front end reads it with `readOpenAIStream` unchanged — despite
|
|
70
|
+
`streamFormat: 'native'`.
|
|
71
|
+
- It takes OpenAI-shaped messages *in* and maps them server-side, so the front
|
|
72
|
+
end still sends `toOpenAIMessages(...)`. The go-live patch is untouched.
|
|
73
|
+
|
|
74
|
+
### What the browser receives is not `streamFormat`
|
|
75
|
+
|
|
76
|
+
`streamFormat` describes what the **provider** emits. `BROWSER_WIRE` in
|
|
77
|
+
`src/catalog.ts` describes what comes back **out of the route**, and the two
|
|
78
|
+
differ for `anthropic`. This matters because the mismatch is silent: the kit's
|
|
79
|
+
own catalog note says feeding a foreign dialect to `readOpenAIStream` "does not
|
|
80
|
+
throw — it parses to nothing and the turn ends silently empty". `routeWireProblem`
|
|
81
|
+
requires an entry per wired gateway rather than defaulting one.
|
|
82
|
+
|
|
83
|
+
### What the rest cost
|
|
84
|
+
|
|
85
|
+
Three separate walls, and most of the remaining integrations are behind more
|
|
86
|
+
than one. The groups are the kit registry's own — `listGatewayGroups()` — not a
|
|
87
|
+
split invented here.
|
|
88
|
+
|
|
89
|
+
**Cheap.** `openai` is now the cheapest unwired one: `outOfBand: 'none'`,
|
|
90
|
+
TypeScript, empty `deps.npm`, an OpenAI-format route. `mastra` is unblocked by
|
|
91
|
+
the preamble export too, but is out-of-band (below).
|
|
92
|
+
|
|
93
|
+
**Unblocked on the preamble, still blocked on the reader.** `vercel-ai-sdk`'s
|
|
94
|
+
route returns an AI-SDK stream, which `readOpenAIStream` cannot read. Wiring it
|
|
95
|
+
needs a **reader axis in the go-live patch** — the patch hard-codes
|
|
96
|
+
`readOpenAIStream` today. `BROWSER_WIRE` refuses it rather than letting that be
|
|
97
|
+
discovered as an empty bubble.
|
|
98
|
+
|
|
99
|
+
`vercel-ai-sdk` and `langgraph` also carry real `deps.npm`, which the
|
|
100
|
+
`package.json` rewrite already handles but which neither wired gateway
|
|
101
|
+
exercises — both declare none. That claim is graded by a unit test against
|
|
102
|
+
`langgraph` instead of by the smoke run.
|
|
103
|
+
|
|
104
|
+
**Needs something running that a scaffold cannot provide.** `ollama` (a local
|
|
105
|
+
server), `pi` (a local binary), `mastra` (a local server), `pydantic-ai` (a
|
|
106
|
+
Python runtime). These are `outOfBand !== 'none'`, and the field exists to say
|
|
107
|
+
exactly this. A scaffold can emit the route and the env file; it cannot make the
|
|
108
|
+
thing answer. `ollama` is also the one integration that is `frontend-safe` *and*
|
|
109
|
+
out-of-band — it needs no proxy at all, so wiring it is a different job from the
|
|
110
|
+
others rather than a smaller one.
|
|
111
|
+
|
|
112
|
+
**Cannot share the TypeScript route path at all.** `pydantic-ai` is
|
|
113
|
+
`language: 'python'`, and `pi` has no `webRoute` — only an `express`
|
|
114
|
+
`routeTemplate`. Neither goes through `emitRoute`, which assembles the portable
|
|
115
|
+
handler; both need their own emit path and their own host.
|
|
116
|
+
|
|
117
|
+
### The framework half
|
|
118
|
+
|
|
119
|
+
The route destination is what the eight frameworks disagree about most, and it
|
|
120
|
+
is the axis that actually gates widening:
|
|
121
|
+
|
|
122
|
+
- **Meta-frameworks are cheap** — one file, no config edit, and the route ships
|
|
123
|
+
in production. `nextjs` is done (`app/api/chat/route.ts`); `svelte`
|
|
124
|
+
(`src/routes/api/chat/+server.ts`, and `POST(event)` not `POST(request)`) and
|
|
125
|
+
`tanstack-start` (`createFileRoute(...)({ server: { handlers } })`) are the
|
|
126
|
+
same shape with different declarations.
|
|
127
|
+
- **Vite SPAs cost three files and a config edit**, and the result is
|
|
128
|
+
**development only** — `vite build` emits no server. `react` is done; `vue`,
|
|
129
|
+
`solid` and `html` are the same work. `vue`'s config edit is the risky one:
|
|
130
|
+
its plugin list carries `isCustomElement`, and clobbering it makes every
|
|
131
|
+
`kai-*` tag stop resolving.
|
|
132
|
+
- **`angular` is its own shape** — the route belongs in the `src/server.ts` that
|
|
133
|
+
`ng add @angular/ssr` generates, registered *before* the catch-all, and a
|
|
134
|
+
non-SSR Angular app cannot host `/api/chat` at all.
|
|
135
|
+
- **`html` has no server anywhere.** The handler has to run elsewhere and be
|
|
136
|
+
proxied.
|
|
137
|
+
|
|
138
|
+
### What is read from the kit, and what it costs
|
|
139
|
+
|
|
140
|
+
`chatRoutePreamble(fragment)`, `CLIENT_MODEL_IDS` and `defaultModelFor` come
|
|
141
|
+
from `agent-tooling/route-emit.ts` — a **leaf module** whose only import is
|
|
142
|
+
type-only. This package used to carry copies of the first two plus guards to
|
|
143
|
+
watch them drift; both copies and one guard are gone.
|
|
144
|
+
|
|
145
|
+
The preamble is a **function**, not a constant, and that shape is load-bearing:
|
|
146
|
+
the content helpers are injected only where a route calls them, because an
|
|
147
|
+
unused declaration is a hard `--noUnusedLocals` error. Emitted proof — the
|
|
148
|
+
`anthropic` route declares `wireParts` / `wireText` / `WirePart`, the
|
|
149
|
+
`openrouter` route declares none of them, and both compile.
|
|
150
|
+
|
|
151
|
+
**WHICH MODULE THEY COME FROM IS THE WHOLE COST.** They were briefly exported
|
|
152
|
+
from `agent-tooling/mcp/tools/scaffold.ts` instead, and that one import line cost
|
|
153
|
+
two things nobody priced in advance:
|
|
154
|
+
|
|
155
|
+
1. **Bundle.** `dist/index.js` went 203 kB → 904 kB, **505 kB of it zod the CLI
|
|
156
|
+
never executes** — because `scaffold.ts` builds the MCP tool's input schema at
|
|
157
|
+
module scope, and a module-scope side effect is not tree-shakeable, so esbuild
|
|
158
|
+
had to keep all 5,300 lines and everything they import. Cold start 30 ms →
|
|
159
|
+
45 ms, on every `npx create-kai`.
|
|
160
|
+
2. **tsconfig coupling.** 5,300 lines of kit source joined this package's `tsc`
|
|
161
|
+
program, which runs `noUnusedLocals` while the kit's own typecheck does not —
|
|
162
|
+
so a flag this package chose became a constraint on a file it does not own,
|
|
163
|
+
and it forced a deletion in the kit to keep this build green.
|
|
164
|
+
|
|
165
|
+
Moving them to `route-emit.ts` undid both: **917.2 kB → 220.3 kB** measured on the
|
|
166
|
+
same commit (zod entirely out of the graph, not merely smaller), and
|
|
167
|
+
`scaffold.ts` out of this package's `tsc` program.
|
|
168
|
+
|
|
169
|
+
Two pairs of numbers appear above and they are not in conflict: **203 → 904 kB**
|
|
170
|
+
is what the regression cost when it landed, and **917.2 → 220.3 kB** is this
|
|
171
|
+
package built with and without the fix on today's base. Both ends drifted up by
|
|
172
|
+
~13 kB in between because `integrations/vercel-ai-sdk.ts` grew, and that file is
|
|
173
|
+
in the CLI's graph legitimately — it is a catalog entry. Re-measure both ends
|
|
174
|
+
together rather than quoting either half against a different commit.
|
|
175
|
+
|
|
176
|
+
**The coupling is reduced, not gone.** 15 kit source files are still in this
|
|
177
|
+
package's `tsc` program under `noUnusedLocals` — the registry, `types.ts`, the
|
|
178
|
+
11 integrations and `route-emit.ts`. That is the catalog this package exists to
|
|
179
|
+
read, so it is the coupling that was always intended; what left is the MCP tool
|
|
180
|
+
that was never meant to be here.
|
|
181
|
+
|
|
182
|
+
`bundleGraphProblem` in `src/build-guards.ts` is what stops this coming back. It
|
|
183
|
+
grades the esbuild **metafile's module graph** — not the output text and not a
|
|
184
|
+
byte ceiling, because a ceiling has to be raised as the templates grow and
|
|
185
|
+
raising it is the moment nobody looks. It bans `zod` (the cost) and everything
|
|
186
|
+
under `agent-tooling/mcp/` (the cause). Watched failing three ways: the rule
|
|
187
|
+
against a written-out graph, the rule against the real bundle, and `npm run
|
|
188
|
+
build` exiting 1 with the same message when the original import is put back.
|
|
189
|
+
|
|
190
|
+
### What is deliberately not shared
|
|
191
|
+
|
|
192
|
+
The per-framework **route wrappers**. The kit MCP's `WEB_ROUTE_ADAPTERS` emit one
|
|
193
|
+
paste-able string concatenating three files with `// ── separators ──` and a
|
|
194
|
+
commented-out config line. This CLI writes real files that have to compile, so
|
|
195
|
+
the wrapper lives beside the framework table that owns every other per-framework
|
|
196
|
+
path.
|
|
197
|
+
|
|
198
|
+
## How it is put together
|
|
199
|
+
|
|
200
|
+
Two sources of truth, neither of them copied:
|
|
201
|
+
|
|
202
|
+
- **Templates** are `examples/starters/*`, copied into `dist/templates/` by
|
|
203
|
+
`scripts/build.mjs`. The starters are CI-built, so drift is caught there.
|
|
204
|
+
Everything except the `package.json` rewrite and the patches in
|
|
205
|
+
`src/patches.ts` is a byte-for-byte copy.
|
|
206
|
+
- **Gateways and renderable surfaces** come from
|
|
207
|
+
`packages/ui/src/agent-tooling/`, imported by relative path and bundled at
|
|
208
|
+
build time (`src/catalog.ts`). Env var names, `deps`, `keyExposure`, route
|
|
209
|
+
templates and `renderSurface` are read, never restated. A second copy of any
|
|
210
|
+
of those has a build failure as its failure mode.
|
|
211
|
+
|
|
212
|
+
The CLI is bundled to one zero-dependency file so `npx` cold start is fast. It
|
|
213
|
+
is **not** the `kai` MCP (`npx @kitn.ai/ui mcp`), and it is not a runtime
|
|
214
|
+
dependency of anything it scaffolds.
|
|
215
|
+
|
|
216
|
+
## Commands
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npm run build # copy templates, verify patches, bundle dist/index.js
|
|
220
|
+
npm test # unit + golden + the kit-contract drift guard
|
|
221
|
+
npm run typecheck
|
|
222
|
+
npm run verify:pack # assert the PUBLISHED tarball is shippable
|
|
223
|
+
npm run smoke # scaffold -> install -> build, against the workspace kit
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`npm run build` must run before `npm test` (the tests read `dist/templates`),
|
|
227
|
+
and `nx build ui` must have run before the kit-contract test (it reads the kit's
|
|
228
|
+
built `.d.ts`).
|
|
229
|
+
|
|
230
|
+
## The guards, and what each one is for
|
|
231
|
+
|
|
232
|
+
Every one of these has been watched failing; a guard nobody has seen go red is
|
|
233
|
+
not evidence.
|
|
234
|
+
|
|
235
|
+
| Guard | Catches |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `scripts/build.mjs` patch check | a template patch that silently stopped matching, which would ship `workspace:*` instructions into a user's project |
|
|
238
|
+
| `scripts/build.mjs` repo-internals check | the patch you did **not** write. The patch check above proves the declared patches match; it passes vacuously on a framework with an empty patch list, which is how Vue's first `ready` build emitted "@kitn.ai/ui Vue example" as the browser-tab title and told the user to run `nx build ui`. This one patches in memory and greps the RESULT |
|
|
239
|
+
| `scripts/build.mjs` app-path check | `paths.app` naming a file the template does not have. It is written into `kai.json` and quoted in the emitted README, and nothing else opens it at build time — so React's `src/App.tsx` copied onto a Vue row would go unnoticed |
|
|
240
|
+
| `scripts/build.mjs` devDep check | a devDependency range disagreeing with `packages/ui`. `.npmrc` sets `node-linker=hoisted`, so one version wins workspace-wide — an `@types/node: ^22` here downgraded the KIT from 26 and broke its emitted-code suite |
|
|
241
|
+
| `test/kit-contract.test.ts` | a template importing something the kit does not export |
|
|
242
|
+
| `scripts/build.mjs` bundle-graph check | the CLI bundle reaching a module it must not. Three symbols imported out of the MCP's `tools/scaffold.ts` took `dist/index.js` from 203 kB to 904 kB, 505 kB of it zod, and **every other check stayed green** — emitted output was byte-identical, `verify:scaffold` was 616/616. It grades the esbuild metafile's real module graph, so it bans the CAUSE (anything under `agent-tooling/mcp/`) and not only the symptom |
|
|
243
|
+
| `scripts/verify-pack.mjs` | npm stripping `.gitignore` out of the tarball, and templates missing from `files` |
|
|
244
|
+
| `scripts/smoke.mjs` | an emitted project that installs but does not build. `--framework all` covers every ready framework; without a flag it only ever built React, which meant it answered "does React still build" no matter which framework you had just turned on |
|
|
245
|
+
|
|
246
|
+
## Publish gate
|
|
247
|
+
|
|
248
|
+
`create-kai` pins `^<kit version>`, derived at build time from
|
|
249
|
+
`packages/ui/package.json`. It must not publish until the kit version it pins is
|
|
250
|
+
on npm and carries everything the templates import.
|
|
251
|
+
|
|
252
|
+
Check before publishing, against the tarball the pin resolves to:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm pack @kitn.ai/ui@<pinned> && tar -xzf kitn.ai-ui-<pinned>.tgz
|
|
256
|
+
KAI_KIT_ROOT=./package npx vitest run test/kit-contract.test.ts
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Green means an emitted project will build for a user.
|
|
260
|
+
|
|
261
|
+
**The blocker this section used to describe is cleared.** It read: the latest
|
|
262
|
+
published kit is 0.20.1, which predates `@kitn.ai/ui/wire`, `MessagePart` and
|
|
263
|
+
`createMockResponder`, so an emitted project installs cleanly and then fails
|
|
264
|
+
`npm run build` with nine type errors. **0.21.0 is published and carries all
|
|
265
|
+
three.** The gate above is green against it for every ready template, and
|
|
266
|
+
every ready framework's emitted project, pinned to `^0.21.0`, installs from the
|
|
267
|
+
registry, passes its own build script, and streams in a browser.
|
|
268
|
+
|
|
269
|
+
Re-run it against whatever the pin resolves to; do not read this paragraph as
|
|
270
|
+
standing permission.
|
|
271
|
+
|
|
272
|
+
## Adding a framework
|
|
273
|
+
|
|
274
|
+
`src/frameworks.ts` is the table. Flip `status` to `ready` and drop its `note`,
|
|
275
|
+
add its patches to `src/patches.ts`, and the build copies its template, the
|
|
276
|
+
prompt offers it, and `--list` reports it.
|
|
277
|
+
|
|
278
|
+
The build tells you which patches you owe: flip the status first and run it, and
|
|
279
|
+
the repo-internals check names every repo-internal line the template would ship,
|
|
280
|
+
file by file. That is the intended order — you are not expected to find them by
|
|
281
|
+
reading.
|
|
282
|
+
|
|
283
|
+
Then find out whether it actually runs:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
node scripts/smoke.mjs --framework <id> # scaffold -> install -> build
|
|
287
|
+
node scripts/smoke.mjs --framework <id> --keep # ... and leave it to `npm run dev`
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**`--keep` and a browser are the step that counts.** A framework is not `ready`
|
|
291
|
+
because it built; it is `ready` because a message sent in a real browser streamed
|
|
292
|
+
a reply into `<kai-thread>`. Vue's build was green before its patches existed and
|
|
293
|
+
while its README pointed at `src/App.tsx`.
|
|
294
|
+
|
|
295
|
+
When you drive it, assert on the RIGHT PAGE and on GROWTH:
|
|
296
|
+
|
|
297
|
+
- Every Vite starter's dev script has a fixed `server.port`, and Vite does
|
|
298
|
+
**not** use `strictPort` — a port already in use makes it quietly serve on the
|
|
299
|
+
next one instead. Drive it with `--port <n> --strictPort`, and kill by
|
|
300
|
+
process GROUP (`spawn(..., { detached: true })` + `process.kill(-pid)`);
|
|
301
|
+
killing the `npm` wrapper alone orphans the `vite` child, which then squats
|
|
302
|
+
the port for the next framework you test. Both traps fired here: the html run
|
|
303
|
+
hit a leaked svelte server on 5176 and "passed", and because all these
|
|
304
|
+
starters render the same design the screenshots were pixel-identical, which
|
|
305
|
+
read as success. Assert `document.title` — every scaffold patches it to the
|
|
306
|
+
project name — so the run is bound to the app you meant to test.
|
|
307
|
+
- Sample `<kai-thread>` on an interval and require the text to GROW across
|
|
308
|
+
several distinct samples. One non-empty read is satisfied by the seeded
|
|
309
|
+
conversation that is already on screen before you type anything.
|
|
310
|
+
- The composer is `contenteditable="plaintext-only"` inside a shadow root, not a
|
|
311
|
+
`textarea`. A bare `textarea` selector finds the sidebar's search box instead.
|