@pikku/skills 0.12.9 → 0.12.11

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +768 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +4 -4
  4. package/skills/pikku-addon/SKILL.md +20 -14
  5. package/skills/pikku-addon/references/addon-package-manifest.md +2 -2
  6. package/skills/{pikku-ai-agent → pikku-agent}/SKILL.md +24 -24
  7. package/skills/pikku-ai-vercel/SKILL.md +18 -18
  8. package/skills/pikku-ai-voice/SKILL.md +15 -15
  9. package/skills/pikku-audit/SKILL.md +28 -13
  10. package/skills/pikku-aws/SKILL.md +2 -2
  11. package/skills/pikku-better-auth/SKILL.md +97 -17
  12. package/skills/pikku-build-app/SKILL.md +621 -0
  13. package/skills/pikku-build-app/references/multi-app.md +117 -0
  14. package/skills/pikku-build-app/references/ship.md +98 -0
  15. package/skills/pikku-build-app/references/theming.md +70 -0
  16. package/skills/pikku-build-platform/SKILL.md +239 -0
  17. package/skills/pikku-build-quick/SKILL.md +238 -0
  18. package/skills/pikku-cli/SKILL.md +7 -7
  19. package/skills/pikku-cli/references/complete-example.md +1 -1
  20. package/skills/pikku-concepts/SKILL.md +10 -7
  21. package/skills/pikku-concepts/references/concept-mapping.md +1 -1
  22. package/skills/pikku-config/SKILL.md +5 -3
  23. package/skills/pikku-deploy-azure/SKILL.md +5 -4
  24. package/skills/pikku-deploy-cloudflare/SKILL.md +9 -9
  25. package/skills/pikku-deploy-uws/SKILL.md +5 -2
  26. package/skills/pikku-deps/SKILL.md +42 -3
  27. package/skills/pikku-emails/SKILL.md +5 -5
  28. package/skills/pikku-fabric/SKILL.md +27 -3
  29. package/skills/pikku-fabric-debug/SKILL.md +1 -1
  30. package/skills/pikku-feature/SKILL.md +5 -4
  31. package/skills/pikku-http/SKILL.md +4 -4
  32. package/skills/pikku-http/references/http-options.md +13 -13
  33. package/skills/pikku-i18n/SKILL.md +2 -1
  34. package/skills/pikku-info/SKILL.md +1 -1
  35. package/skills/pikku-knowledge/SKILL.md +13 -13
  36. package/skills/pikku-kysely/SKILL.md +68 -41
  37. package/skills/pikku-machine-auth/SKILL.md +10 -10
  38. package/skills/pikku-mcp/SKILL.md +23 -20
  39. package/skills/pikku-middleware/SKILL.md +19 -12
  40. package/skills/pikku-middleware/references/middleware-patterns.md +14 -8
  41. package/skills/pikku-mongodb/SKILL.md +11 -11
  42. package/skills/pikku-n8n-import/SKILL.md +12 -12
  43. package/skills/pikku-n8n-import/SPEC.md +3 -0
  44. package/skills/pikku-n8n-import/references/addon-mapping.md +14 -8
  45. package/skills/pikku-n8n-import/references/code-translation.md +26 -22
  46. package/skills/pikku-n8n-import/references/loops-and-control.md +7 -7
  47. package/skills/pikku-paraglide/SKILL.md +11 -6
  48. package/skills/pikku-permissions/SKILL.md +19 -15
  49. package/skills/pikku-product-second-opinion/README.md +3 -3
  50. package/skills/pikku-product-second-opinion/SKILL.md +83 -73
  51. package/skills/pikku-product-second-opinion/example/sample-report.md +15 -12
  52. package/skills/pikku-product-second-opinion/references/report-template.md +10 -7
  53. package/skills/pikku-queue/SKILL.md +1 -1
  54. package/skills/pikku-react/SKILL.md +53 -13
  55. package/skills/pikku-realtime/SKILL.md +51 -19
  56. package/skills/pikku-rpc/SKILL.md +1 -1
  57. package/skills/pikku-rtl/SKILL.md +1 -1
  58. package/skills/pikku-scenario/SKILL.md +164 -44
  59. package/skills/pikku-schedule/SKILL.md +6 -1
  60. package/skills/pikku-schema-ajv/SKILL.md +2 -2
  61. package/skills/pikku-schema-cfworker/SKILL.md +1 -1
  62. package/skills/pikku-security/SKILL.md +9 -5
  63. package/skills/pikku-services/SKILL.md +27 -18
  64. package/skills/pikku-services/references/audit-wire-service.md +14 -8
  65. package/skills/pikku-software-archaeology/SKILL.md +27 -23
  66. package/skills/pikku-software-archaeology/references/blueprint.schema.json +580 -102
  67. package/skills/pikku-software-archaeology/scripts/validate.mjs +146 -69
  68. package/skills/pikku-tag-middleware/SKILL.md +1 -0
  69. package/skills/pikku-template-clone/SKILL.md +2 -1
  70. package/skills/pikku-trigger/SKILL.md +3 -3
  71. package/skills/pikku-versioning/SKILL.md +87 -3
  72. package/skills/pikku-websocket/SKILL.md +4 -3
  73. package/skills/pikku-workflow/SKILL.md +2 -2
  74. package/skills/pikku-workflow/references/workflow-reference.md +24 -10
  75. package/skills/pikku-ws/SKILL.md +5 -2
@@ -0,0 +1,238 @@
1
+ ---
2
+ name: pikku-build-quick
3
+ description: >-
4
+ Build a working app on open-source Pikku fast — scaffold to running screens, skipping the
5
+ knowledge base and the milestone ladder. For spikes, throwaway demos, and ideas nobody has
6
+ committed to yet. TRIGGER when: the user asked for something quick, a prototype, a spike, a
7
+ demo of an idea, or "just get it running", or picked "Quick" from the build-mode question. DO
8
+ NOT TRIGGER when: the request is an unqualified "build me an X on Pikku" with no signal of
9
+ speed or throwaway-ness — App is the default and small or toy-sounding apps do not change that
10
+ (use pikku-build-app); the user wants a real product someone else will pick up (use
11
+ pikku-build-app); the user wants a demo of Pikku itself — one that shows off surfaces like
12
+ workflows, queues, realtime or i18n (use pikku-build-platform); or the user is adding a feature
13
+ to an app that already exists rather than building one from a fresh scaffold (use
14
+ pikku-feature).
15
+ installGroups: [core]
16
+ ---
17
+
18
+ # Build an app on Pikku, fast
19
+
20
+ You have a scaffolded project with skills installed. Get it to working, seeded,
21
+ signed-in screens in as few steps as possible.
22
+
23
+ **What this mode deliberately skips**, and what that costs:
24
+
25
+ | Skipped | Cost |
26
+ |---|---|
27
+ | `knowledge/` | Another agent — or you next week — cannot resume this. Nothing records *why*. |
28
+ | Milestone planning | No build order, no per-piece proof. Fine at this size, painful past it. |
29
+ | Design direction | It will look like the template. |
30
+ | Refusal scenarios | Access control is asserted, not proven. |
31
+
32
+ **Say this out loud to the user, once, when you finish.** A quick build that gets
33
+ mistaken for a real one is the only way this mode does damage. §6 is the way out.
34
+
35
+ ## Agent Operating Procedure
36
+
37
+ 1. Read `AGENTS.md` at the project root before your first screen — routing slots,
38
+ `useNavItems()`, and the shipped component kit.
39
+ 2. Keep generated files generated. Never hand-edit `.pikku/`, `*.gen.*`, or the SDK.
40
+ 3. Run `pikku all` after touching functions, wirings or schemas. It is the gate,
41
+ and its criticals are real.
42
+
43
+ ## 1. One question, then build
44
+
45
+ Ask **one** thing, and only if the original request left it open: **who uses
46
+ it — one kind of person, or several?** Everything else you decide yourself.
47
+
48
+ - **One kind** — no roles to declare. The rule is ownership: you see yours, not
49
+ theirs.
50
+ - **Several** — declare a role each in §2 and keep the count honest. An invented
51
+ role becomes invented screens.
52
+
53
+ Do not ask about design, deployment, or scope. This is the quick mode; the
54
+ defaults are the point.
55
+
56
+ ## 2. Personas — 60 seconds, not optional
57
+
58
+ `packages/functions/src/personas.ts` ships with a `visitor`. Add one persona per
59
+ kind of person, plus **a second one of the primary kind** — that is what makes
60
+ "you see yours, not theirs" observable when you click around.
61
+
62
+ **One kind of person** — no `defineSystemRole` at all. Ownership is the only
63
+ rule, and it lives in each function's `permissions`, not in a role:
64
+
65
+ ```typescript
66
+ import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
67
+
68
+ definePersonas({
69
+ visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
70
+ amina: { name: 'Amina', jobTitle: 'Gardener', account: {} },
71
+ bilal: { name: 'Bilal', jobTitle: 'Gardener', account: {} },
72
+ })
73
+ ```
74
+
75
+ **Several kinds** — one role each, and only for the kinds the user actually
76
+ named:
77
+
78
+ ```typescript
79
+ import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
80
+ import { defineSystemRole } from '#pikku'
81
+
82
+ defineSystemRole({
83
+ owner: { displayName: 'Owner', description: 'Sees only their own rows', scopes: [] },
84
+ tenant: { displayName: 'Tenant', description: 'Sees only their own tenancy', scopes: [] },
85
+ })
86
+
87
+ definePersonas({
88
+ visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
89
+ amina: { name: 'Amina', jobTitle: 'Owner', roles: ['owner'], account: {} },
90
+ bilal: { name: 'Bilal', jobTitle: 'Owner', roles: ['owner'], account: {} },
91
+ chidi: { name: 'Chidi', jobTitle: 'Tenant', roles: ['tenant'], account: {} },
92
+ })
93
+ ```
94
+
95
+ Two owners in both examples, deliberately: one owner cannot demonstrate that
96
+ owners are separated from each other.
97
+
98
+ - **Keep `visitor`.** The shipped scenarios name `actors.visitor`; removing it
99
+ fails `pikku all` and nothing you write after that registers.
100
+ - **One `definePersonas` call for the whole project.**
101
+ - **Never write an email address** — each is derived from the persona id and
102
+ `scenarios.emailDomain` in `pikku.config.json`.
103
+ - `roles` is typechecked against `defineSystemRole`; an undeclared role is a
104
+ build error.
105
+
106
+ ## 3. Build
107
+
108
+ Run `bunx --bun pikku bootstrap` once first. It wires the `#pikku` import alias
109
+ codegen depends on; without it your first `db migrate` fails with
110
+ `Cannot find package '#pikku'`.
111
+
112
+ Then, in this order — it is the order codegen depends on:
113
+
114
+ 1. **Migration** — SQL in `db/sqlite/`, numbered on from what is there. Apply
115
+ with `bunx --bun pikku db migrate`, which regenerates the Kysely types.
116
+ 2. **Seed** — rows in `db/sqlite-dev-seed.sql`. There is no seed command:
117
+ `bunx --bun pikku db reset` wipes, migrates and seeds in one go, and is the
118
+ only thing that applies the file. It always starts from a wiped database, so
119
+ the file is plain `INSERT`s — no `ON CONFLICT DO NOTHING`. **Be generous, and
120
+ seed rows for both personas.** An empty app demos badly, and you cannot see a
121
+ layout break against zero rows.
122
+ 3. **Functions** — one `pikkuFunc` per `*.function.ts`, `expose: true`. Pikku
123
+ generates the typed RPC client and React Query hooks; you do NOT write HTTP
124
+ routes. `wireHTTP` only for a real REST shape (a third-party webhook).
125
+ 4. `bunx --bun pikku all`
126
+ 5. **UI** — pages in `apps/app/src/pages/`, one route file each in
127
+ `apps/app/src/routes/`, calling `usePikkuQuery` / `usePikkuMutation` from
128
+ `@project/functions-sdk/pikku/api.gen`. Compose `@/components/<Name>` —
129
+ `PageHeader`, `Panel`, `StatGrid`, `DataTable` — rather than hand-rolling.
130
+ Register each screen in `useNavItems()`; that one file feeds the desktop
131
+ sidebar and the phone navigation.
132
+
133
+ **Aim for two or three real entities and three screens** — a working surface, a
134
+ detail view, and somewhere to land. One table with a form on it is not an app,
135
+ and it is not faster to build.
136
+
137
+ Rules that stay non-negotiable even here, because breaking them costs more time
138
+ than they save:
139
+
140
+ - Input and output types come from `input:`/`output:` zod schemas. Never generic
141
+ type params, never an inline return type. The schema is the type.
142
+ - Permission checks go in the `permissions` field, never the function body. An
143
+ exposed function with no session and no permission is reachable by anyone over
144
+ `POST /rpc/:rpcName` (PKU574).
145
+ - No `process.env` inside a function — use the injected `variables` / `secrets`
146
+ services.
147
+ - A `z.date()` **input** arrives over RPC as an ISO string, not a `Date`.
148
+ `new Date(value)` before calling date methods, or it throws
149
+ `.getTime is not a function` at runtime.
150
+ - On SQLite, `db/annotations.ts` is where a `DATETIME` becomes a `Date` and a
151
+ `JSON` column becomes a typed object. Without an entry they are `string` and
152
+ `unknown` (PKU481). Add the annotation rather than casting.
153
+ - Surface errors inline next to the control that failed. No empty catch.
154
+ - Every user-facing string is a translation key, not a literal. It is one extra
155
+ keystroke now and a rewrite later.
156
+ - Never hardcode a host or port — the API base resolves to same-origin `/api`.
157
+
158
+ Then run it:
159
+
160
+ ```sh
161
+ bun run prebuild && bun run dev
162
+ ```
163
+
164
+ API on :3000, app on the port vite prints. A frontend against a dead API looks
165
+ exactly like an app bug, so if every request fails, check both came up.
166
+
167
+ ## 4. Look at it — actually
168
+
169
+ Sign up, click every screen. **HTTP 200 is not evidence:** pages are
170
+ client-rendered, so the server returns 200 with an empty shell and a page whose
171
+ component throws still looks fine to `curl`. Open a browser, or drive it
172
+ headlessly and assert on rendered text.
173
+
174
+ **Screenshot at 390px too.** A layout that is fine at 1440 routinely breaks on a
175
+ phone — an overflowing table, a row of buttons wrapped into a pile, a modal
176
+ taller than the viewport. It is the most likely width your demo gets opened at.
177
+
178
+ If you have five spare minutes, `npx impeccable install` (Node 22.18+) scores
179
+ each screen against interaction heuristics and names what is wrong. Feed it
180
+ screenshots, not source. It will polish the default look; it will not give the
181
+ app a look — that is `pikku-build-app` §8a.
182
+
183
+ ## 5. One smoke scenario
184
+
185
+ Not the full ladder — one journey, end to end, as a real persona, so the app has
186
+ at least one thing that stays true.
187
+
188
+ ```typescript
189
+ import { pikkuScenario } from '#pikku/workflow/pikku-workflow-types.gen.js'
190
+
191
+ export const ownerCreatesAndSeesItScenario = pikkuScenario<void, { id: string }>({
192
+ title: 'An owner creates a thing and sees it',
193
+ tags: ['scenario', 'smoke'],
194
+ func: async (_services, _data, { scenario, actors }) => {
195
+ const row = await scenario.do('creates', 'createThing', { name: 'first' }, { actor: actors.amina })
196
+ await scenario.then('sees it listed', 'thingShowsInList', { id: row.id }, { actor: actors.amina })
197
+ return { id: row.id }
198
+ },
199
+ })
200
+ ```
201
+
202
+ - **`do` takes an RPC name; `given`/`when`/`then` take a declared
203
+ `pikkuScenarioStep`.** An RPC name in a `then` will not resolve.
204
+ - **Every scenario must assert.** A ladder with no `then` is a PKU680 critical —
205
+ it fails `pikku all`, stopping codegen rather than a test.
206
+ - **Add `SCENARIO_ACTOR_SECRET` to `.env`.** `bun run dev` writes that file with
207
+ only a `BETTER_AUTH_SECRET`; without the actor secret
208
+ `/api/auth/sign-in/actor` is disabled and every scenario fails at sign-in, for
209
+ a reason that reads like an auth bug.
210
+ - **There is no state reset** — scope what you create to unique ids.
211
+
212
+ Keep the three shipped scenarios in `packages/functions/test/scenarios/` green.
213
+
214
+ ```sh
215
+ bunx --bun pikku scenario run local --spawn
216
+ ```
217
+
218
+ ## 6. Hand it over honestly
219
+
220
+ Tell the user, in one short paragraph: what runs, what it is seeded with, and
221
+ that this is a quick build — no knowledge base, no milestones, no design pass,
222
+ access control clicked-through rather than proven.
223
+
224
+ **Upgrading to a real build is additive, not a rewrite.** If they want it, switch
225
+ to `pikku-build-app` and do this, in order:
226
+
227
+ 1. Write `knowledge/` for what already exists — `entities/` for what you built,
228
+ `decisions/` for what you chose silently, `questions/` for what you guessed
229
+ at. Then `pikku knowledge index && pikku knowledge validate`.
230
+ 2. Backfill a milestone note per screen you built, at `status: built`, each with
231
+ its gherkin block.
232
+ 3. Write the refusal scenarios — the ones proving one persona cannot reach
233
+ another's rows. This is the gap that matters most.
234
+ 4. Then pick up `pikku-build-app` at its §4 (apps) or §5 (milestones) for
235
+ anything new.
236
+
237
+ Nothing built here has to be thrown away to do that — which is the whole reason
238
+ this mode is allowed to skip those steps in the first place.
@@ -41,7 +41,7 @@ All three factories come from `#pikku` (the generated types re-export
41
41
  loses your project's service and middleware types.
42
42
 
43
43
  ```typescript
44
- import { wireCLI } from '#pikku'
44
+ import { wireCLI } from '#pikku/cli'
45
45
 
46
46
  wireCLI({
47
47
  program: string, // Program name (e.g. 'todos')
@@ -65,7 +65,7 @@ wireCLI({
65
65
  ### `pikkuCLICommand(config)`
66
66
 
67
67
  ```typescript
68
- import { pikkuCLICommand } from '#pikku'
68
+ import { pikkuCLICommand } from '#pikku/cli'
69
69
 
70
70
  pikkuCLICommand({
71
71
  parameters?: string, // Positional args (e.g. '<text>', '<username> <email>')
@@ -111,7 +111,7 @@ How the parser reads them, which is worth knowing before you name one:
111
111
  ### `pikkuCLIRender(fn)`
112
112
 
113
113
  ```typescript
114
- import { pikkuCLIRender } from '#pikku'
114
+ import { pikkuCLIRender } from '#pikku/cli'
115
115
 
116
116
  const renderer = pikkuCLIRender<OutputType>((services, data) => {
117
117
  // Format and print output to terminal
@@ -122,10 +122,10 @@ const renderer = pikkuCLIRender<OutputType>((services, data) => {
122
122
  ### Wire object (`wire.cli`)
123
123
 
124
124
  ```typescript
125
- wire.cli.program // program name
126
- wire.cli.command // string[] — the resolved command path
127
- wire.cli.data // all positionals and options, merged
128
- wire.cli.channel // the channel when served remotely (see below)
125
+ wire.cli.program // program name
126
+ wire.cli.command // string[] — the resolved command path
127
+ wire.cli.data // all positionals and options, merged
128
+ wire.cli.channel // the channel when served remotely (see below)
129
129
  ```
130
130
 
131
131
  ## Usage Patterns
@@ -32,7 +32,7 @@ export const deleteUser = pikkuFunc({
32
32
  })
33
33
 
34
34
  // wirings/cli.wiring.ts
35
- import { wireCLI, pikkuCLICommand, pikkuCLIRender } from '#pikku'
35
+ import { wireCLI, pikkuCLICommand, pikkuCLIRender } from '#pikku/cli'
36
36
 
37
37
  const userRenderer = pikkuCLIRender<{ user: User }>((_services, { user }) => {
38
38
  console.log(`Created user: ${user.username} (${user.email}) [${user.role}]`)
@@ -27,7 +27,7 @@ Pikku is a TypeScript framework that separates business logic from transport mec
27
27
 
28
28
  For deep-dive on each topic, see the dedicated skills:
29
29
 
30
- - **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-ai-agent`, `pikku-workflow`
30
+ - **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-agent`, `pikku-workflow`
31
31
  - **Authorization**: `pikku-security` (authentication/sessions), `pikku-permissions` (permission checks, scopes), `pikku-middleware` (global/tag/route middleware)
32
32
  - **Infrastructure**: `pikku-services`, `pikku-config`
33
33
  - **Project introspection**: `pikku-info`
@@ -44,7 +44,7 @@ pikkuFunc (pure business logic)
44
44
  ├── wireMCPTool → Model Context Protocol (AI tools)
45
45
  ├── wireCLI → CLI commands
46
46
  ├── wireTrigger → Event-driven (Redis pub/sub, PG LISTEN/NOTIFY)
47
- ├── pikkuAIAgent → AI agents / chatbots
47
+ ├── pikkuAgent → AI agents / chatbots
48
48
  ├── pikkuWorkflow → Multi-step durable workflows
49
49
  └── wire.rpc → Internal function-to-function calls
50
50
  ```
@@ -122,7 +122,7 @@ pikkuFunc({
122
122
  permissionsInBody?: boolean, // Last resort; needs allow.permissionsInBody in config
123
123
  middleware?: PikkuMiddleware[], // See pikku-middleware
124
124
 
125
- // Agent tooling — see pikku-ai-agent
125
+ // Agent tooling — see pikku-agent
126
126
  approvalRequired?: boolean,
127
127
  approvalDescription?: (services, data) => Promise<string>,
128
128
 
@@ -144,7 +144,7 @@ reject every caller it exists to serve. Gate those with `permissions`, which
144
144
  receive the optional session and may pass anonymous.
145
145
 
146
146
  **Generics XOR `input`/`output` — never both.** A function's data and return
147
- types come from *one* source: either the `input`/`output` schemas (preferred —
147
+ types come from _one_ source: either the `input`/`output` schemas (preferred —
148
148
  they double as runtime validation and OpenAPI) or type generics
149
149
  (`pikkuFunc<In, Out>({ ... })`). Passing both makes the two disagree and forces
150
150
  `as any` casts. Do not annotate the `func` return type inline either — let the
@@ -230,13 +230,15 @@ await server.start()
230
230
 
231
231
  **Lifecycle hooks do not run on this path** — only `pikku dev` and `pikku serve` invoke them. Do your startup work directly in the entrypoint instead.
232
232
 
233
- `pikku validate` warns when a project starts a server by hand *and* depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
233
+ `pikku validate` warns when a project starts a server by hand _and_ depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
234
234
 
235
235
  ## Code Generation
236
236
 
237
237
  Run `npx pikku all` to generate:
238
238
 
239
- - `pikku-types.gen.ts` — Typed function factories and wiring functions
239
+ - one directory per wiring (`function/`, `http/`, `workflow/`, …), each with an
240
+ `index.ts` reached as `#pikku/<name>` — typed function factories and wiring
241
+ functions, split so an app pulls in only the wirings it uses
240
242
  - `pikku-fetch.gen.ts` — Type-safe HTTP client
241
243
  - `pikku-websocket.gen.ts` — Type-safe WebSocket client
242
244
  - `pikku-bootstrap.gen.ts` — Runtime initialization (auto-imports all wirings)
@@ -271,7 +273,8 @@ src/
271
273
  ├── middleware.ts # Middleware definitions (see pikku-security)
272
274
  ├── permissions.ts # Permission definitions (see pikku-security)
273
275
  └── .pikku/ # Generated (gitignored)
274
- ├── pikku-types.gen.ts
276
+ ├── function/ # #pikku/function
277
+ ├── http/ # #pikku/http
275
278
  ├── pikku-fetch.gen.ts
276
279
  └── pikku-bootstrap.gen.ts
277
280
  ```
@@ -472,7 +472,7 @@ if (!canEdit(user, todo)) {
472
472
  **Pikku:**
473
473
 
474
474
  ```typescript
475
- import { NotFoundError, ForbiddenError } from '@pikku/core/errors'
475
+ import { NotFoundError, ForbiddenError } from '#pikku/error'
476
476
 
477
477
  const updateTodo = pikkuFunc(async (services, { id, title }, wire) => {
478
478
  const todo = services.todoStore.getTodo(id)
@@ -75,7 +75,9 @@ greppable. Call it at the point the value reaches the thing that needs it:
75
75
  ```typescript
76
76
  // services.ts — allowed
77
77
  const createSingletonServices = pikkuServices(async (config, { secrets }) => ({
78
- stripe: new StripeService((await secrets.getSecret('STRIPE_CONFIG')).reveal()),
78
+ stripe: new StripeService(
79
+ (await secrets.getSecret('STRIPE_CONFIG')).reveal()
80
+ ),
79
81
  }))
80
82
 
81
83
  // functions/*.ts — ask the service, never the secret store
@@ -162,7 +164,7 @@ defineCredential({
162
164
 
163
165
  ### Usage
164
166
 
165
- ```typescript
167
+ ````typescript
166
168
  // Per-user API key — no oauth2 block
167
169
  defineCredential({
168
170
  name: 'stripe',
@@ -208,7 +210,7 @@ export const createWireServices = pikkuWireServices(async (_services, wire) => {
208
210
  export const postMessage = pikkuFunc({
209
211
  func: async ({ slack }, { channel, text }) => slack.postMessage(channel, text),
210
212
  })
211
- ```
213
+ ````
212
214
 
213
215
  A `wire` credential resolves per user, so an unconnected user hits
214
216
  `MissingCredentialError` rather than silently acting as someone else; a
@@ -59,10 +59,11 @@ import { createAzureHandler } from '@pikku/azure-functions'
59
59
  import { createConfig, createSingletonServices } from './services.js'
60
60
  import './.pikku/pikku-bootstrap.gen.js'
61
61
 
62
- const handlers = createAzureHandler(
63
- { createConfig, createSingletonServices },
64
- ['fetch', 'queue', 'scheduled']
65
- )
62
+ const handlers = createAzureHandler({ createConfig, createSingletonServices }, [
63
+ 'fetch',
64
+ 'queue',
65
+ 'scheduled',
66
+ ])
66
67
 
67
68
  app.http('api', {
68
69
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
@@ -42,14 +42,14 @@ export default createCloudflareHandler(
42
42
  )
43
43
  ```
44
44
 
45
- | Factory | For |
46
- | --- | --- |
47
- | `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled` |
48
- | `createCloudflareWorkerHandler(factories)` | HTTP / agent / RPC / workflow-orchestrator units |
49
- | `createCloudflareCronHandler(factories)` | cron units |
50
- | `createCloudflareQueueHandler(factories)` | queue-consumer and workflow-step units |
51
- | `createCloudflareMCPHandler(factories)` | MCP units (same HTTP transport) |
52
- | `createCloudflareWebSocketHandler(factories)` | channel units, delegating to the DO |
45
+ | Factory | For |
46
+ | -------------------------------------------------- | ------------------------------------------------ |
47
+ | `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled` |
48
+ | `createCloudflareWorkerHandler(factories)` | HTTP / agent / RPC / workflow-orchestrator units |
49
+ | `createCloudflareCronHandler(factories)` | cron units |
50
+ | `createCloudflareQueueHandler(factories)` | queue-consumer and workflow-step units |
51
+ | `createCloudflareMCPHandler(factories)` | MCP units (same HTTP transport) |
52
+ | `createCloudflareWebSocketHandler(factories)` | channel units, delegating to the DO |
53
53
 
54
54
  `factories` is `{ createConfig, createSingletonServices, createPlatformServices? }`.
55
55
 
@@ -71,7 +71,7 @@ const services = await setupServices(env, {
71
71
  **Do not hand-roll this.** Beyond building `LocalVariablesService` /
72
72
  `LocalSecretService` and caching the result, it calls `setSingletonServices()` —
73
73
  and the core runners (`fetchData`, `runQueueJob`, `runScheduled`) resolve
74
- services through that global slot, *not* through the value you were returned. A
74
+ services through that global slot, _not_ through the value you were returned. A
75
75
  setup function that only returns the services leaves every request throwing
76
76
  "Singleton services not initialized" as a CF `1101`. It also stashes the env via
77
77
  `setCloudflareEnv`, which `getCloudflareEnv()` reads for bindings.
@@ -102,14 +102,17 @@ yarn add @pikku/ws
102
102
  ```
103
103
 
104
104
  ```typescript
105
- import { pikkuWebsocketHandler } from '@pikku/ws'
105
+ import { DEFAULT_WS_MAX_PAYLOAD, pikkuWebsocketHandler } from '@pikku/ws'
106
106
  import { stopSingletonServices } from '@pikku/core'
107
107
  import { Server } from 'http'
108
108
  import { WebSocketServer } from 'ws'
109
109
  import './.pikku/pikku-bootstrap.gen.js'
110
110
 
111
111
  const server = new Server()
112
- const wss = new WebSocketServer({ noServer: true })
112
+ const wss = new WebSocketServer({
113
+ noServer: true,
114
+ maxPayload: DEFAULT_WS_MAX_PAYLOAD,
115
+ })
113
116
 
114
117
  pikkuWebsocketHandler({
115
118
  server,
@@ -4,8 +4,11 @@ description: >-
4
4
  Use for the Pikku dependency security audit: the `pikku audit` CLI command, the
5
5
  `.pikku/audit.json` artifact, the `SecurityAuditReport` type in @pikku/core, and the console
6
6
  Security screen (getSecurityAudit / runSecurityAudit / updateDependency + SecurityAuditView).
7
- TRIGGER when: user asks about `pikku audit`, dependency vulnerabilities/advisories, outdated
8
- dependencies, the Security screen/page in the console, updating a vulnerable dependency, or
7
+ Also covers `pikku update`, which moves the @pikku/* dependency set forward and reports the
8
+ peers those versions need.
9
+ TRIGGER when: user asks about `pikku audit` or `pikku update`, dependency
10
+ vulnerabilities/advisories, outdated dependencies, upgrading Pikku itself, peer dependency
11
+ conflicts, the Security screen/page in the console, updating a vulnerable dependency, or
9
12
  reading/rendering audit.json. DO NOT TRIGGER when: user asks about authentication/sessions/JWT
10
13
  (use pikku-security), permissions (use pikku-permissions), or secrets/env vars (use
11
14
  pikku-config).
@@ -43,11 +46,47 @@ installGroups: [core]
43
46
  `bun outdated`, normalised into one `SecurityAuditReport` with per-severity /
44
47
  per-update-level counts). Other PMs are detected but **stubbed** with a `note`
45
48
  field until their shapes are normalised — issues/updates come back empty.
46
- - `bun audit` exits non-zero when it *finds* advisories but still writes the
49
+ - `bun audit` exits non-zero when it _finds_ advisories but still writes the
47
50
  payload to stdout, so a non-zero exit **with output** is data. A non-zero exit
48
51
  with **no** output — or a launch failure, timeout, or a blown 32MB buffer —
49
52
  throws, precisely so a failed run can't masquerade as "0 advisories".
50
53
 
54
+ ## The `pikku update` command
55
+
56
+ Narrower than `audit --outdated`, and the only one that writes: it moves the
57
+ **@pikku/\* set** forward and reports the peers those versions need. Use `audit`
58
+ to learn a dependency is vulnerable; use `update` to move Pikku itself.
59
+
60
+ - `pikku update` — reports only. Nothing is written without `--update`.
61
+ - `pikku update --update` — writes the new ranges into every covered
62
+ package.json, then runs an install. `--no-install` writes and stops.
63
+ - `pikku update --update-peers` — implies `--update` and additionally writes the
64
+ ranges unsatisfied peers require, **for peers the project already declares**.
65
+ A peer it does not declare is reported and never added — adding a dependency
66
+ is not an update. Separate from `--update` because a peer bump can cross a
67
+ **major** of a third-party package (`ai` 5 → 6), which is not a call to make
68
+ on the user's behalf.
69
+ - `--tag <dist-tag>` (default `latest`) reads each package's own dist-tag, so
70
+ `--tag next` moves the whole set onto prereleases. `--registry <url>` defaults
71
+ to `npm_config_registry`.
72
+ - Coverage is the nearest package.json walking up from the project root, plus
73
+ every workspace it declares — a monorepo updates in one pass. All four
74
+ dependency fields are read, `peerDependencies` included, so an addon's own
75
+ declared peer range moves with it.
76
+
77
+ Statuses, per dependency: `outdated` (the range floor is behind latest — this is
78
+ what `--update` writes), `stale-install` (the range already admits latest but
79
+ node_modules is behind — an install fixes it, no edit needed), `linked` (a
80
+ `workspace:`/`file:`/`link:`/`portal:` range — a deliberate local checkout,
81
+ counted but never listed), `manual` (a registry range we refuse to substitute
82
+ into: a union, an x-range, a `*`), `unresolved` (the registry had no such tag —
83
+ this must **never** read as "current", the same rule as a failed audit).
84
+
85
+ Peers are read off the version the run **lands on**, not the one installed —
86
+ the point is what the target needs. An @pikku peer the same run already brings
87
+ forward is not reported, and an unsatisfied _optional_ peer the project never
88
+ declared is skipped.
89
+
51
90
  ## Console integration (@pikku/addon-console)
52
91
 
53
92
  Three RPCs, all reading/writing the same artifact via the meta service. Shared
@@ -36,8 +36,8 @@ generated output is never edited by hand.
36
36
  ```jsonc
37
37
  // pikku.config.json
38
38
  {
39
- "emailTemplatesDir": "emails", // relative to rootDir; omit to disable emails
40
- "outDir": ".pikku" // gen lands in <outDir>/email/
39
+ "emailTemplatesDir": "emails", // relative to rootDir; omit to disable emails
40
+ "outDir": ".pikku", // gen lands in <outDir>/email/
41
41
  }
42
42
  ```
43
43
 
@@ -121,9 +121,9 @@ render with sample data and read the result rather than trusting that it compile
121
121
 
122
122
  ```ts
123
123
  const rendered = renderEmailTemplate({
124
- name: 'verify-email', // EmailTemplateName (autocompleted)
125
- locale: 'en', // optional, defaults to 'en'
126
- data: { verifyUrl: url }, // EmailTemplateVariables<'verify-email'>
124
+ name: 'verify-email', // EmailTemplateName (autocompleted)
125
+ locale: 'en', // optional, defaults to 'en'
126
+ data: { verifyUrl: url }, // EmailTemplateVariables<'verify-email'>
127
127
  })
128
128
  // rendered: { name, locale, subject, html, text?, variables, hash }
129
129
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pikku-fabric
3
- description: 'Build and convert apps for the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `fabric.config.json`, and the pikku-verify workflow. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, or asking about Fabric deployment, database, or project conventions. DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy-cloudflare, pikku-deploy-fastify, etc. instead.'
3
+ description: 'Build and convert apps for the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `fabric.config.json`, and the pikku-verify workflow. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, or asking about Fabric deployment, database, or project conventions. TRIGGER when: user asks about a `pikku fabric validate` finding, including app-missing-actor-quick-login. DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy-cloudflare, pikku-deploy-fastify, etc. instead.'
4
4
  installGroups: [fabric]
5
5
  ---
6
6
 
@@ -115,7 +115,7 @@ and refuses a database outside the runtime directory. Anything a real environmen
115
115
  needs — accounts, role grants — is provisioning, not seeding, and belongs in
116
116
  `pikku persona sync` or a migration.
117
117
 
118
- A Better Auth app has a second constraint: the plugins you enable (`admin()`,
118
+ A Better Auth app has a second constraint: the plugins you enable (`ban()`,
119
119
  `actor()`, …) each declare columns, and `pikku db migrate` refuses to run while
120
120
  the applied schema is missing any of them. `pikku db generate` writes the
121
121
  migration that closes the gap.
@@ -311,13 +311,37 @@ Always call the `pikku-verify` tool after modifying functions, wirings, or schem
311
311
 
312
312
  The output card shows whether any breaking changes were detected.
313
313
 
314
+ ### `app-missing-actor-quick-login-<app>`
315
+
316
+ The `fabric validate` finding people most often misread. It fires when an app has
317
+ a **login screen** but no dev actor switcher, and it is not a style nit: a sandbox
318
+ reviewer has no seed password, so without the control they are locked out of the
319
+ app they were asked to look at.
320
+
321
+ Satisfy it with `<DevActorSwitcher />` from `@pikku/mantine/dev`, or with your
322
+ own UI built on `useDevActors()` from `@pikku/react` — validate accepts either
323
+ call site as evidence, so custom rendering passes. See **pikku-react** for the
324
+ props and **pikku-scenario** for where the actor list comes from.
325
+
326
+ The validator also accepts the shapes that predate the package — a hand-rolled
327
+ `signInAsActor()` or a literal `POST /auth/sign-in/actor` — so an older app does
328
+ not fail the build. **Treat that as a grace period, not the target: migrate those
329
+ to `<DevActorSwitcher />`.** The hand-copied version is exactly the duplication
330
+ the package exists to remove, and the copies drift — the ones that prompted this
331
+ had already diverged on the `import.meta.env.DEV` gate that keeps the shared
332
+ secret out of production bundles.
333
+
334
+ Do **not** satisfy it with Better Auth's `/dev/quick-login`. That is a different
335
+ endpoint with a different purpose — one fixed admin, not the declared personas —
336
+ and it does not clear this rule.
337
+
314
338
  ## Hard rules
315
339
 
316
340
  These apply in every Fabric app:
317
341
 
318
342
  - **No `process.env`** — use `variables.get('NAME')` and `secrets.getSecret('NAME')`. Declare with `defineVariable` / `defineSecret`.
319
343
  - **No `as any`** — fix types properly.
320
- - **No generic `Error`** — throw `NotFoundError`, `ConflictError`, `BadRequestError`, `UnauthorizedError` from `@pikku/core/errors`.
344
+ - **No generic `Error`** — throw `NotFoundError`, `ConflictError`, `BadRequestError`, `UnauthorizedError` from `#pikku/error`.
321
345
  - **No auth checks in function bodies** — use `permissions:` field on the function config with a `pikkuPermission` factory.
322
346
  - **No hand-editing `.pikku/db/schema.gen.ts`** — write a migration and re-run `pikku db migrate`.
323
347
  - **One runtime unit per file** — never define multiple functions/workflows in a single source file.
@@ -87,7 +87,7 @@ without it, even though the flag reads as optional.
87
87
  pikku fabric status # active + in-flight deployment, per stage, with gitSha
88
88
  ```
89
89
 
90
- Check this *before* deep-diving. A stage still serving an older `gitSha`, or a
90
+ Check this _before_ deep-diving. A stage still serving an older `gitSha`, or a
91
91
  deploy stuck in flight, explains a whole class of "my fix did nothing".
92
92
 
93
93
  ## Known gaps — do not misread these as bugs in your app
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pikku-feature
3
- description: 'Drive create-a-feature work for a Pikku project: discover project context, work on a feature branch, implement + verify + commit, and ask the user to review via the diff. TRIGGER when: the user asks to "create a feature", "build a todo app", "add X to my Pikku project", "wire up a new endpoint", or anything that implies turning a natural-language request into Pikku functions/wirings/migrations. DO NOT TRIGGER when: the user asks for a one-off code edit in an existing function, or asks about Pikku concepts (use pikku-concepts).'
3
+ description: 'Drive create-a-feature work inside a Pikku project that already exists: discover project context, work on a feature branch, implement + verify + commit, and ask the user to review via the diff. TRIGGER when: the user asks to "create a feature", "add X to my Pikku project", "wire up a new endpoint", or anything that implies turning a natural-language request into Pikku functions/wirings/migrations within a working app. DO NOT TRIGGER when: the user asks for a one-off code edit in an existing function, asks about Pikku concepts (use pikku-concepts), or is building a whole app from a fresh scaffold rather than extending one (use pikku-build-app, or pikku-build-quick / pikku-build-platform).'
4
4
  installGroups: [core]
5
5
  allowed-tools: Bash(yarn pikku meta *), Bash(yarn pikku all *), Bash(yarn tsc), Bash(git status *), Bash(git diff *), Bash(git switch *), Bash(git checkout *), Bash(git checkout -b *), Bash(git add *), Bash(git commit *), Bash(git log *), Bash(git branch *)
6
6
  argument-hint: '<feature description>'
@@ -121,7 +121,7 @@ in-app features don't.
121
121
  queue-dispatched.
122
122
  - **Auth checks belong on the function or wiring**, not in function bodies.
123
123
  Use the `permissions` field with a `pikkuPermission` factory.
124
- - **Throw typed errors** from `@pikku/core/errors` — `NotFoundError`,
124
+ - **Throw typed errors** from `#pikku/error` — `NotFoundError`,
125
125
  `ConflictError`, `BadRequestError`. Never bare `Error`.
126
126
  - **Migrations are inline SQL files** in the project's migrations dir
127
127
  (typically `sql/`). Use a numbered prefix matching existing files.
@@ -141,8 +141,9 @@ Some patterns vary by project; **read a neighbour file before writing**:
141
141
  `CreateTodoOutput`) passed to `input`/`output` on the func config — vs
142
142
  generic-typed config. Schema name **must match codegen expectations** (the
143
143
  exported const name = the schema name in generated `.gen.json`).
144
- - **Imports**: usually `'#pikku'` for `pikkuFunc` / `pikkuSessionlessFunc`
145
- etc. Copy what neighbours do.
144
+ - **Imports**: `#pikku` is a namespace, not a module — one subpath per wiring.
145
+ `pikkuFunc` / `pikkuSessionlessFunc` come from `'#pikku/function'`, `wireHTTP`
146
+ from `'#pikku/http'`. Copy what neighbours do.
146
147
  - **Service usage**: e.g. `kysely`, `redis`. Look at how an existing function
147
148
  destructures services from its first arg. **Check `application-types.d.ts`**
148
149
  to see whether services like `kysely` are typed (`Kysely<DB>`) or untyped