@pikku/skills 0.12.22 → 0.12.25
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/CHANGELOG.md +101 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +2 -2
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +264 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +1 -27
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/{pikku-permissions/SKILL.md → pikku-auth/references/permissions.md} +1 -20
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
- package/skills/pikku-build/SKILL.md +87 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +75 -23
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +1 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +7 -1
- package/skills/pikku-concepts/SKILL.md +72 -7
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +12 -2
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +60 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +14 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +5 -5
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +293 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-scenario/SKILL.md +60 -45
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +15 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +199 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/{pikku-realtime/SKILL.md → pikku-wiring/references/realtime.md} +2 -25
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +2 -2
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-build-platform
|
|
3
|
-
description: >-
|
|
4
|
-
Build an app that exercises every Pikku surface — workflows, schedules, queues, an AI agent,
|
|
5
|
-
realtime, MCP, multiple locales, contract versioning and scenario coverage — on top of the full
|
|
6
|
-
pikku-build-app workflow. For proving what the platform does, not for shipping the smallest
|
|
7
|
-
thing that works. TRIGGER when: the user picked "Platform", asked for a showcase or reference
|
|
8
|
-
app, or asked to demonstrate what Pikku can do. DO NOT TRIGGER when: the user wants a product
|
|
9
|
-
built (use pikku-build-app), something quick (use pikku-build-quick), or one specific surface
|
|
10
|
-
wired into an existing app — a single workflow, cron job or agent (use that surface's own skill,
|
|
11
|
-
e.g. pikku-workflow, pikku-schedule, pikku-agent).
|
|
12
|
-
---
|
|
13
|
-
|
|
14
1
|
# Build a platform showcase on Pikku
|
|
15
2
|
|
|
16
|
-
**This skill is a delta. `
|
|
3
|
+
**This skill is a delta. `references/app.md` is the base — read it and follow it in
|
|
17
4
|
full.** Everything there applies: knowledge base first, personas and roles,
|
|
18
5
|
milestones planned then built one at a time, scenarios, design pass, deploy
|
|
19
6
|
gates, Fabric-readiness. This file adds the surfaces that turn an app into a
|
|
20
7
|
demonstration of the platform, and says where each one slots into that workflow.
|
|
21
8
|
|
|
22
|
-
Read `
|
|
9
|
+
Read `references/app.md` now, then come back. Do not blend the two into one plan —
|
|
23
10
|
the phases below hang off its phases by number.
|
|
24
11
|
|
|
25
12
|
## What "platform" means here
|
|
@@ -32,7 +19,7 @@ proves it.** A cron job that logs "tick" is not a schedule — it is a comment.
|
|
|
32
19
|
Budget the extra surfaces at one milestone each. They are not free, and a
|
|
33
20
|
half-wired workflow engine is worse than no workflow engine.
|
|
34
21
|
|
|
35
|
-
## Choosing surfaces — during `
|
|
22
|
+
## Choosing surfaces — during `references/app.md` §5 (planning)
|
|
36
23
|
|
|
37
24
|
When you plan milestones, each surface below becomes its own milestone note in
|
|
38
25
|
`knowledge/milestones/`, ordered after the spine it depends on.
|
|
@@ -85,11 +72,11 @@ on a human or a timer and must survive a restart.
|
|
|
85
72
|
gap in the middle? If every operation completes in one request, you do not need
|
|
86
73
|
workflows and forcing one is noise.
|
|
87
74
|
- **Prove it:** a scenario that starts the workflow, advances it as a second
|
|
88
|
-
persona, and asserts the end state. `pikku-
|
|
75
|
+
persona, and asserts the end state. `pikku-react` covers driving it
|
|
89
76
|
from the UI.
|
|
90
77
|
- Three workflows ship with the template. Read them before writing yours.
|
|
91
78
|
|
|
92
|
-
### Schedules — `pikku-
|
|
79
|
+
### Schedules — `pikku-wiring`
|
|
93
80
|
|
|
94
81
|
Recurring work: a nightly rollup, a reminder sweep, an expiry pass.
|
|
95
82
|
|
|
@@ -99,7 +86,7 @@ Recurring work: a nightly rollup, a reminder sweep, an expiry pass.
|
|
|
99
86
|
- **Prove it:** invoke the scheduled function directly in a scenario and assert
|
|
100
87
|
its effect. Do not test by waiting.
|
|
101
88
|
|
|
102
|
-
### Queues — `pikku-
|
|
89
|
+
### Queues — `pikku-wiring`
|
|
103
90
|
|
|
104
91
|
Work that must happen but not now, and may retry: email fan-out, image
|
|
105
92
|
processing, third-party calls that fail.
|
|
@@ -108,7 +95,7 @@ processing, third-party calls that fail.
|
|
|
108
95
|
fails in ways worth retrying.
|
|
109
96
|
- **Prove it:** enqueue in one scenario step, assert the effect in a `then`.
|
|
110
97
|
|
|
111
|
-
### An AI agent — `pikku-agent
|
|
98
|
+
### An AI agent — `pikku-agent`
|
|
112
99
|
|
|
113
100
|
The template ships agent wiring and `@ai-sdk/openai`. An agent that answers
|
|
114
101
|
questions over the app's own data is the showcase; a general chatbot is not.
|
|
@@ -124,7 +111,7 @@ questions over the app's own data is the showcase; a general chatbot is not.
|
|
|
124
111
|
matching `defineSecret`, never `process.env`, or deploy has nothing to
|
|
125
112
|
provision (PKU951).
|
|
126
113
|
|
|
127
|
-
### Realtime and events — `pikku-
|
|
114
|
+
### Realtime and events — `pikku-wiring`
|
|
128
115
|
|
|
129
116
|
`pikku enable events` gives a realtime channel plus an SSE stream, and the
|
|
130
117
|
generated typed client.
|
|
@@ -134,20 +121,20 @@ generated typed client.
|
|
|
134
121
|
- **Prove it:** a browser scenario is the only honest proof — assert the second
|
|
135
122
|
persona's screen changed without a reload.
|
|
136
123
|
|
|
137
|
-
### MCP — `pikku-
|
|
124
|
+
### MCP — `pikku-wiring`
|
|
138
125
|
|
|
139
126
|
Exposes functions as Model Context Protocol tools, so an outside agent can drive
|
|
140
127
|
the app. Cheap once functions exist, and a genuine differentiator to show.
|
|
141
128
|
|
|
142
|
-
### Triggers and webhooks — `pikku-
|
|
129
|
+
### Triggers and webhooks — `pikku-wiring`, `pikku enable webhook`
|
|
143
130
|
|
|
144
131
|
Inbound triggers and outgoing webhook delivery. This is where `wireHTTP` is
|
|
145
132
|
correct rather than a smell: a third-party caller needs a real REST shape.
|
|
146
133
|
|
|
147
|
-
### Locales — `pikku-i18n
|
|
134
|
+
### Locales — `pikku-i18n`
|
|
148
135
|
|
|
149
|
-
`
|
|
150
|
-
locales, and make one of them RTL** (`pikku-
|
|
136
|
+
`references/app.md` already requires every string to be a key. **Here, ship three
|
|
137
|
+
locales, and make one of them RTL** (`pikku-i18n`). Two LTR locales prove the
|
|
151
138
|
plumbing; an RTL one proves the layout, and it will find real bugs — mirrored
|
|
152
139
|
icons, hardcoded `marginLeft`, a nav that opens on the wrong side.
|
|
153
140
|
|
|
@@ -159,7 +146,7 @@ every added locale is cloned from, so three locales is `locales: ["en", …]` an
|
|
|
159
146
|
never a repointed base. Shipping locales is also not a reason for anything in
|
|
160
147
|
the code to stop being English: identifiers are English in every project, and
|
|
161
148
|
the language of `description`/`title`/`template` is `metaLocale` in
|
|
162
|
-
`pikku.config.json`. See `
|
|
149
|
+
`pikku.config.json`. See `references/app.md` §1a.
|
|
163
150
|
|
|
164
151
|
### Emails — `pikku-emails`
|
|
165
152
|
|
|
@@ -168,7 +155,7 @@ localised like every other string. The base workflow asks for one; **a showcase
|
|
|
168
155
|
sends three** — a welcome, a transactional confirmation, and one sent from a
|
|
169
156
|
schedule or queue rather than a request, because that is the interesting path.
|
|
170
157
|
|
|
171
|
-
### Contract versioning — `pikku-
|
|
158
|
+
### Contract versioning — `pikku-meta`
|
|
172
159
|
|
|
173
160
|
```sh
|
|
174
161
|
bunx --bun pikku versions init
|
|
@@ -186,7 +173,7 @@ the domain has a piece that genuinely belongs to no single app.
|
|
|
186
173
|
|
|
187
174
|
## Coverage — where the bar is higher than the base workflow
|
|
188
175
|
|
|
189
|
-
`
|
|
176
|
+
`references/app.md` §7a already has the mechanics and the per-milestone habit:
|
|
190
177
|
run the server instrumented, run the scenarios against it, read
|
|
191
178
|
`coverage/scenario-coverage.json`, and triage every gap as a missing scenario, a
|
|
192
179
|
function that should not exist, or a documented deferral. Do all of that here.
|
|
@@ -205,7 +192,7 @@ Two things change in a showcase:
|
|
|
205
192
|
|
|
206
193
|
## The full gate
|
|
207
194
|
|
|
208
|
-
Everything in `
|
|
195
|
+
Everything in `references/app.md` §9, plus the checks a showcase should be able to
|
|
209
196
|
survive:
|
|
210
197
|
|
|
211
198
|
```sh
|
|
@@ -227,7 +214,7 @@ bunx --bun pikku scenario run local --spawn --run browser
|
|
|
227
214
|
|
|
228
215
|
## Deploy
|
|
229
216
|
|
|
230
|
-
`
|
|
217
|
+
`references/app.md` §9 covers the open-source paths (`--provider standalone`,
|
|
231
218
|
`cloudflare`, `aws`). One thing specific to this mode: **the extra surfaces are
|
|
232
219
|
extra deploy units.** Workflow workers, queue workers, schedules and the events
|
|
233
220
|
channel each appear in `pikku deploy plan` as their own entries. Read the plan
|
|
@@ -236,10 +223,8 @@ built.
|
|
|
236
223
|
|
|
237
224
|
## Reference
|
|
238
225
|
|
|
239
|
-
- Base workflow: `
|
|
240
|
-
- Per-surface skills: `pikku-workflow`, `pikku-
|
|
241
|
-
`pikku-
|
|
242
|
-
`pikku-
|
|
243
|
-
`pikku-emails`, `pikku-versioning`, `pikku-addon`, `pikku-security`,
|
|
244
|
-
`pikku-audit`
|
|
226
|
+
- Base workflow: `references/app.md` — read it first, follow it in full
|
|
227
|
+
- Per-surface skills: `pikku-workflow`, `pikku-wiring`, `pikku-agent`,
|
|
228
|
+
`pikku-i18n`, `pikku-emails`,
|
|
229
|
+
`pikku-meta`, `pikku-addon`, `pikku-auth`, `pikku-services`
|
|
245
230
|
- Every feature, end to end: https://pikkufabric.com/llm-all-features.txt
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-template-clone
|
|
3
|
-
description: 'Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `npm create pikku`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.'
|
|
4
|
-
allowed-tools: Bash(git status *), Bash(git add *), Bash(git commit *), Bash(git rm *), Bash(git mv *), Bash(git log *)
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Pikku Template Post-Clone Cleanup
|
|
8
2
|
|
|
9
3
|
## Agent Operating Procedure
|
|
@@ -1,19 +1,3 @@
|
|
|
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
|
-
---
|
|
16
|
-
|
|
17
1
|
# Build an app on Pikku, fast
|
|
18
2
|
|
|
19
3
|
You have a scaffolded project with skills installed. Get it to working, seeded,
|
|
@@ -58,7 +42,7 @@ Identifiers are English in every project, whatever the product's market;
|
|
|
58
42
|
`template`, which the Console renders) stays `en` unless the user already told
|
|
59
43
|
you otherwise. If the request says the app's UI is not English, that is the
|
|
60
44
|
message catalogue only: add the locale and set `defaultLocale`, and leave
|
|
61
|
-
`baseLocale` at `en`. `
|
|
45
|
+
`baseLocale` at `en`. `references/app.md` §1a has the three axes in full; getting
|
|
62
46
|
them confused is how a project ends up unable to add a second language.
|
|
63
47
|
|
|
64
48
|
## 2. Personas — 60 seconds, not optional
|
|
@@ -191,7 +175,7 @@ taller than the viewport. It is the most likely width your demo gets opened at.
|
|
|
191
175
|
If you have five spare minutes, `npx impeccable install` (Node 22.18+) scores
|
|
192
176
|
each screen against interaction heuristics and names what is wrong. Feed it
|
|
193
177
|
screenshots, not source. It will polish the default look; it will not give the
|
|
194
|
-
app a look — that is `
|
|
178
|
+
app a look — that is `references/app.md` §8a.
|
|
195
179
|
|
|
196
180
|
## 5. One smoke scenario
|
|
197
181
|
|
|
@@ -235,7 +219,7 @@ that this is a quick build — no knowledge base, no milestones, no design pass,
|
|
|
235
219
|
access control clicked-through rather than proven.
|
|
236
220
|
|
|
237
221
|
**Upgrading to a real build is additive, not a rewrite.** If they want it, switch
|
|
238
|
-
to `
|
|
222
|
+
to `references/app.md` and do this, in order:
|
|
239
223
|
|
|
240
224
|
1. Write `knowledge/` for what already exists — `entities/` for what you built,
|
|
241
225
|
`decisions/` for what you chose silently, `questions/` for what you guessed
|
|
@@ -244,7 +228,7 @@ to `pikku-build-app` and do this, in order:
|
|
|
244
228
|
its gherkin block.
|
|
245
229
|
3. Write the refusal scenarios — the ones proving one persona cannot reach
|
|
246
230
|
another's rows. This is the gap that matters most.
|
|
247
|
-
4. Then pick up `
|
|
231
|
+
4. Then pick up `references/app.md` at its §4 (apps) or §5 (milestones) for
|
|
248
232
|
anything new.
|
|
249
233
|
|
|
250
234
|
Nothing built here has to be thrown away to do that — which is the whole reason
|
|
@@ -16,7 +16,7 @@ bunx --bun pikku deploy apply --provider standalone --runtime bun
|
|
|
16
16
|
bundles the project into a single unit and emits either a `bundle.js` you run
|
|
17
17
|
with Node, or a self-contained executable compiled with `bun build --compile`.
|
|
18
18
|
`cloudflare` (the default) and `aws` are the other providers — read the
|
|
19
|
-
`pikku-deploy
|
|
19
|
+
`pikku-deploy` skill before using it, as it ships the handler
|
|
20
20
|
factories the deploy codegen expects, and hand-rolling an `ExportedHandler` is
|
|
21
21
|
how a worker deploy fails at runtime instead of at build.
|
|
22
22
|
|
|
@@ -81,6 +81,12 @@ Everything above is open source. This is the contract that keeps
|
|
|
81
81
|
serves imports as a person with no way in.
|
|
82
82
|
- **`knowledge/` passes `validate`, with every milestone at `built`.** This is
|
|
83
83
|
the part Fabric itself reads and continues from.
|
|
84
|
+
- **Every `built` milestone passes `pikku knowledge plan progress`.** A note that
|
|
85
|
+
says `built` is a claim; the plan reconciled against the generated meta is the
|
|
86
|
+
check. Anything the first pass still owes is either built now or deferred with
|
|
87
|
+
its reason on the record; anything the check calls a problem — something that
|
|
88
|
+
exists and does not do what was planned — is fixed, whatever pass it came from,
|
|
89
|
+
because deferring it defers a hole rather than the work.
|
|
84
90
|
- **Every milestone has a passing scenario**, including its refusals.
|
|
85
91
|
- **Permissions live in the `permissions` field**, not in function bodies and not
|
|
86
92
|
in the frontends. A check hidden in a component does not survive a new client.
|
|
@@ -74,6 +74,71 @@ which is what decides whether a thrown error becomes a 409 or a 500.
|
|
|
74
74
|
`pikku doc` needs `@pikku/cli` 0.12.115 or newer. On an older pin, fall back to the door's
|
|
75
75
|
skill and `pikku meta --json`, and do not guess at names the doc would have given you.
|
|
76
76
|
|
|
77
|
+
## The CLI commands
|
|
78
|
+
|
|
79
|
+
`pikku doc` is the API surface — the `#pikku/*` exports. It does **not** list
|
|
80
|
+
commands, so this table is where they exist. `pikku <command> --help` has the
|
|
81
|
+
flags; the "Read" column is the skill that teaches the thing, where one does.
|
|
82
|
+
|
|
83
|
+
**Generating**
|
|
84
|
+
|
|
85
|
+
| Command | What it does | Read |
|
|
86
|
+
| ------------------------------------------ | ---------------------------------------------------- | ----------------------------- |
|
|
87
|
+
| `all` | Everything: types, schemas, wirings, clients | this skill |
|
|
88
|
+
| `bootstrap` | Type files only (the setup phase) | this skill |
|
|
89
|
+
| `schemas` | JSON Schemas for function input/output types | this skill |
|
|
90
|
+
| `fetch` / `websocket` / `rpc` / `realtime` | One client each, when you do not want `all` | `pikku-wiring`, `pikku-react` |
|
|
91
|
+
| `react-query` / `tanstack-start` | React Query hooks; the TanStack Start `makeApi` shim | `pikku-react` |
|
|
92
|
+
| `queue-service` | The queue service wrapper | `pikku-wiring` |
|
|
93
|
+
| `openapi` | An OpenAPI spec from the HTTP routes | — |
|
|
94
|
+
| `nextjs` | Next.js backend and HTTP wrappers | `pikku-deploy` |
|
|
95
|
+
| `new` | Scaffold a function or wiring | `pikku-wiring` |
|
|
96
|
+
| `enable` | Turn a Pikku feature on | `pikku-build` |
|
|
97
|
+
| `import` | Import workflows from another system | `pikku-n8n-import` |
|
|
98
|
+
|
|
99
|
+
**Running**
|
|
100
|
+
|
|
101
|
+
| Command | What it does | Read |
|
|
102
|
+
| ---------------------------- | ------------------------------------------------------------------------------ | --------------------------------------- |
|
|
103
|
+
| `dev` | Local dev server, all services wired, watch + HMR | `pikku-build` |
|
|
104
|
+
| `serve` | Bundled bun/node runner — no watch, no codegen | `pikku-deploy` |
|
|
105
|
+
| `watch` | Regenerate on file change, without a server | — |
|
|
106
|
+
| `scenario list\|run` | Scenarios as e2e tests and health checks | `pikku-scenario` |
|
|
107
|
+
| `persona run` | A declared persona as a model-driven virtual user against a stage | `pikku-scenario`, persona-run reference |
|
|
108
|
+
| `persona list\|sync\|secret` | Who is declared; what an environment will provision; minting their credentials | `pikku-scenario`, persona-run reference |
|
|
109
|
+
| `db` | Local development database | `pikku-kysely` |
|
|
110
|
+
|
|
111
|
+
**Inspecting and evolving**
|
|
112
|
+
|
|
113
|
+
| Command | What it does | Read |
|
|
114
|
+
| --------------------- | ----------------------------------------------------------------------- | ---------------------------- |
|
|
115
|
+
| `doc` | The installed API surface | this skill |
|
|
116
|
+
| `meta` / `info` | What the project declares, machine- and human-readable | `pikku-meta` |
|
|
117
|
+
| `validate` | Every check that applies — app structure, an addon's published file set | `pikku-build`, `pikku-addon` |
|
|
118
|
+
| `versions` / `semver` | Contract hashes, breaking-change detection, the release semver | `pikku-meta` |
|
|
119
|
+
| `audit` / `update` | Advisories; which `@pikku/*` can move and what peers that needs | `pikku-meta` |
|
|
120
|
+
| `scopes` / `roles` | Declared authorization scopes; roles from `defineSystemRole` | `pikku-auth` |
|
|
121
|
+
| `knowledge` | The knowledge base — what this app is, in its users' language | `pikku-knowledge` |
|
|
122
|
+
| `emails` | Email template generation | `pikku-emails` |
|
|
123
|
+
|
|
124
|
+
**Shipping, and the CLI itself**
|
|
125
|
+
|
|
126
|
+
| Command | What it does | Read |
|
|
127
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
|
|
128
|
+
| `deploy` | Deploy to cloud infrastructure | `pikku-deploy` |
|
|
129
|
+
| `fabric` | PikkuFabric: login, link, deploy, domains, secrets, logs | `pikku-fabric` |
|
|
130
|
+
| `binary` | Compile an entrypoint to a native binary (`bun build --compile`) | — |
|
|
131
|
+
| `dist` | Copy what `tsc` cannot emit — `.gen.json` meta, hand-authored `.d.ts` — into the build output. Run it after `tsc`, as a package's build script | — |
|
|
132
|
+
| `login` / `logout` / `whoami` | The CLI's session against a pikku server | — |
|
|
133
|
+
| `skills` | Install these skills into an agent (Claude Code, opencode, pi) | — |
|
|
134
|
+
|
|
135
|
+
`-c/--config`, `--log-level`, `--json` and the filter flags are **global
|
|
136
|
+
options**, not commands — they attach to the generating commands above.
|
|
137
|
+
|
|
138
|
+
A dash means no skill covers it beyond this line. `--help` is then the whole of
|
|
139
|
+
it — which is a reason to read `--help` rather than to assume the command does
|
|
140
|
+
what its name suggests.
|
|
141
|
+
|
|
77
142
|
## Core Mental Model
|
|
78
143
|
|
|
79
144
|
```text
|
|
@@ -101,7 +166,7 @@ The function never imports Express, never reads `req.body`, never touches `ws.se
|
|
|
101
166
|
|
|
102
167
|
## Concept Mapping: Generic Backend → Pikku
|
|
103
168
|
|
|
104
|
-
Controllers/routes → `pikkuFunc`; auth/sessions → `pikku-
|
|
169
|
+
Controllers/routes → `pikkuFunc`; auth/sessions and authorization checks → `pikku-auth`, a separate install; request interception → `pikku-middleware`; DI → `pikku-services`; transports (HTTP/WS/queue/cron) → their `wire*` + skill. For the full Generic Backend → Pikku mapping table (with side-by-side code examples), read `references/concept-mapping.md`.
|
|
105
170
|
|
|
106
171
|
## Functions
|
|
107
172
|
|
|
@@ -143,7 +208,7 @@ pikkuFunc({
|
|
|
143
208
|
// "What Language You Write In".
|
|
144
209
|
title?: string, // Human-readable name
|
|
145
210
|
description?: string, // What the function does
|
|
146
|
-
version?: number, // Contract version (see pikku-
|
|
211
|
+
version?: number, // Contract version (see pikku-meta)
|
|
147
212
|
override?: string, // Logical name override, so several exports share a versioned base
|
|
148
213
|
tags?: string[], // For grouping and middleware targeting
|
|
149
214
|
|
|
@@ -153,13 +218,13 @@ pikkuFunc({
|
|
|
153
218
|
errors?: Array<typeof PikkuError>, // Errors this function may throw
|
|
154
219
|
|
|
155
220
|
// Reachability
|
|
156
|
-
expose?: boolean, // Allow external RPC calls (see pikku-
|
|
221
|
+
expose?: boolean, // Allow external RPC calls (see pikku-wiring)
|
|
157
222
|
remote?: boolean, // Allow remote RPC calls
|
|
158
|
-
mcp?: boolean, // Expose as MCP tool (see pikku-
|
|
223
|
+
mcp?: boolean, // Expose as MCP tool (see pikku-wiring)
|
|
159
224
|
readonly?: boolean, // Declares the function performs no writes
|
|
160
225
|
deploy?: 'serverless' | 'server' | 'auto',
|
|
161
226
|
|
|
162
|
-
// Authorization — see pikku-
|
|
227
|
+
// Authorization — see pikku-auth
|
|
163
228
|
auth?: boolean, // Override default auth requirement
|
|
164
229
|
scopes?: ScopeId[], // AND-ed, checked before permissions; session required
|
|
165
230
|
permissions?: PermissionGroup, // OR-ed pool
|
|
@@ -317,7 +382,7 @@ src/
|
|
|
317
382
|
├── services.ts # Service factories (see pikku-services)
|
|
318
383
|
├── lifecycle.ts # Server lifecycle hooks (pikku dev/serve only)
|
|
319
384
|
├── middleware.ts # Middleware definitions (see pikku-middleware)
|
|
320
|
-
├── permissions.ts # Permission definitions (see pikku-
|
|
385
|
+
├── permissions.ts # Permission definitions (see pikku-auth)
|
|
321
386
|
└── .pikku/ # Generated (gitignored)
|
|
322
387
|
├── function/ # #pikku/function
|
|
323
388
|
├── http/ # #pikku/http
|
|
@@ -415,7 +480,7 @@ language, it is telling you about axis three and nothing else.
|
|
|
415
480
|
|
|
416
481
|
## Environment Variables
|
|
417
482
|
|
|
418
|
-
Never use `process.env` inside Pikku functions. Use the `variables` service (see `pikku-
|
|
483
|
+
Never use `process.env` inside Pikku functions. Use the `variables` service (see `pikku-services`):
|
|
419
484
|
|
|
420
485
|
```typescript
|
|
421
486
|
const apiKey = services.variables.get('API_KEY')
|
|
@@ -7,19 +7,19 @@ Authoritative mapping table plus side-by-side code examples showing how common b
|
|
|
7
7
|
| Generic Backend Concept | Pikku Equivalent | Skill |
|
|
8
8
|
| --------------------------------------- | --------------------------------------------------------------- | ----------------- |
|
|
9
9
|
| **Controller / Route Handler** | `pikkuFunc` / `pikkuSessionlessFunc` | `pikku-concepts` |
|
|
10
|
-
| **Route definition** (`GET /users/:id`) | `wireHTTP({ route, method, func })` | `pikku-
|
|
11
|
-
| **Middleware** (Express/Koa-style) | `pikkuMiddleware` | `pikku-
|
|
12
|
-
| **Auth Guard / Auth Middleware** | `authBearer()` / `authCookie()` / `authApiKey()` | `pikku-
|
|
13
|
-
| **Authorization / Permissions** | `pikkuPermission` / `pikkuAuth` | `pikku-
|
|
10
|
+
| **Route definition** (`GET /users/:id`) | `wireHTTP({ route, method, func })` | `pikku-wiring` |
|
|
11
|
+
| **Middleware** (Express/Koa-style) | `pikkuMiddleware` | `pikku-middleware` |
|
|
12
|
+
| **Auth Guard / Auth Middleware** | `authBearer()` / `authCookie()` / `authApiKey()` | `pikku-auth` |
|
|
13
|
+
| **Authorization / Permissions** | `pikkuPermission` / `pikkuAuth` | `pikku-auth` |
|
|
14
14
|
| **DTO / Request Validation** | Standard Schema (Zod, Valibot, ArkType) | `pikku-concepts` |
|
|
15
15
|
| **Dependency Injection** | `pikkuServices` (singleton) + `pikkuWireServices` (per-request) | `pikku-services` |
|
|
16
|
-
| **WebSocket handlers** | `wireChannel` | `pikku-
|
|
17
|
-
| **Job Queue workers** | `wireQueueWorker` | `pikku-
|
|
18
|
-
| **Cron / Scheduled tasks** | `wireScheduler` | `pikku-
|
|
16
|
+
| **WebSocket handlers** | `wireChannel` | `pikku-wiring` |
|
|
17
|
+
| **Job Queue workers** | `wireQueueWorker` | `pikku-wiring` |
|
|
18
|
+
| **Cron / Scheduled tasks** | `wireScheduler` | `pikku-wiring` |
|
|
19
19
|
| **Module / Feature grouping** | Tags + wiring files | `pikku-concepts` |
|
|
20
20
|
| **Error handling** | Throw typed errors (`NotFoundError`, `ForbiddenError`) | `pikku-concepts` |
|
|
21
21
|
| **Type-safe API client** | `npx pikku all` generates clients | `pikku-concepts` |
|
|
22
|
-
| **Secrets / Config** | `defineSecret`, `defineVariable`, `services.variables` | `pikku-
|
|
22
|
+
| **Secrets / Config** | `defineSecret`, `defineVariable`, `services.variables` | `pikku-services` |
|
|
23
23
|
|
|
24
24
|
## Route Handler / Controller → pikkuFunc
|
|
25
25
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-deploy
|
|
3
|
+
description: >-
|
|
4
|
+
Use when deploying a Pikku app to a runtime — Express, Fastify, uWebSockets.js, the `ws` library,
|
|
5
|
+
Next.js, AWS Lambda, Cloudflare Workers or Azure Functions. Covers the bootstrap every runtime
|
|
6
|
+
shares, choosing between them, and the behaviour that differs: which accept
|
|
7
|
+
`RunHTTPWiringOptions`, how each one runs scheduled tasks, and where CORS and health checks live.
|
|
8
|
+
TRIGGER when: writing or debugging `start.ts` / a worker entry / a Lambda handler, code imports
|
|
9
|
+
`@pikku/express`, `@pikku/fastify`, `@pikku/uws`, `@pikku/ws`, `@pikku/next`, `@pikku/lambda`,
|
|
10
|
+
`@pikku/cloudflare` or `@pikku/azure-functions`, or the user asks how to serve, host or deploy a
|
|
11
|
+
Pikku app. DO NOT TRIGGER when: defining functions or wirings with no runtime-specific code.
|
|
12
|
+
installGroups: [core]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Pikku Deployment
|
|
16
|
+
|
|
17
|
+
## Agent Operating Procedure
|
|
18
|
+
|
|
19
|
+
Use this skill as an execution checklist, not reference material.
|
|
20
|
+
|
|
21
|
+
1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
22
|
+
2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
|
|
23
|
+
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
24
|
+
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
25
|
+
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
26
|
+
|
|
27
|
+
Signatures and option keys come from `pikku doc` — run `pikku doc --ai` for the
|
|
28
|
+
installed surface. This skill is the part the compiler cannot tell you: which
|
|
29
|
+
runtime to pick, and what each one does differently once you have.
|
|
30
|
+
|
|
31
|
+
## Pick a runtime
|
|
32
|
+
|
|
33
|
+
Two families, and the difference decides how you write the entry file.
|
|
34
|
+
|
|
35
|
+
**Long-running servers** own a process. Services are built once at startup and
|
|
36
|
+
live in module scope for the life of the server.
|
|
37
|
+
|
|
38
|
+
| Runtime | Package | Reach for it when |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| Express | `@pikku/express` | An existing Express app, or you want static assets and upload handling |
|
|
41
|
+
| Fastify | `@pikku/fastify` | An existing Fastify app, or you want its stricter defaults |
|
|
42
|
+
| uWebSockets.js | `@pikku/uws` | Highest throughput, HTTP and WebSocket on one port |
|
|
43
|
+
| ws | `@pikku/ws` | WebSocket only, attached to an `http.Server` you own |
|
|
44
|
+
|
|
45
|
+
**Per-invocation runtimes** are handed a request and torn down. Services are
|
|
46
|
+
cached in module scope across warm invocations, and the deploy codegen writes
|
|
47
|
+
that caching for you.
|
|
48
|
+
|
|
49
|
+
| Runtime | Package | Reach for it when |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| AWS Lambda | `@pikku/lambda` | API Gateway, EventBridge, SQS |
|
|
52
|
+
| Cloudflare Workers | `@pikku/cloudflare` | Edge, Durable Objects for channels |
|
|
53
|
+
| Azure Functions | `@pikku/azure-functions` | Azure hosting — note channels are not implemented |
|
|
54
|
+
| Next.js | `@pikku/next` | Pikku behind Next routes, or RPC from Server Components |
|
|
55
|
+
|
|
56
|
+
Then read the reference for the one you picked: `references/express.md`,
|
|
57
|
+
`references/fastify.md`, `references/uws.md`, `references/ws.md`,
|
|
58
|
+
`references/nextjs.md`, `references/lambda.md`, `references/cloudflare.md`,
|
|
59
|
+
`references/azure.md`.
|
|
60
|
+
|
|
61
|
+
## The bootstrap every runtime shares
|
|
62
|
+
|
|
63
|
+
Importing the generated bootstrap registers your wirings; nothing routes without
|
|
64
|
+
it. `createConfig` and `createSingletonServices` come from your own
|
|
65
|
+
`services.ts`.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import './.pikku/pikku-bootstrap.gen.js'
|
|
69
|
+
import { createConfig, createSingletonServices } from './services.js'
|
|
70
|
+
|
|
71
|
+
const config = await createConfig()
|
|
72
|
+
const singletonServices = await createSingletonServices(config)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A long-running server takes it from there. A per-invocation runtime wraps the
|
|
76
|
+
same two calls in a memoised factory, because the module may be reused across
|
|
77
|
+
invocations — every `@pikku/*` serverless package ships those factories, and
|
|
78
|
+
hand-written handlers are for the cases the deploy codegen does not cover.
|
|
79
|
+
|
|
80
|
+
## What differs, and where it bites
|
|
81
|
+
|
|
82
|
+
### `RunHTTPWiringOptions` is not accepted everywhere
|
|
83
|
+
|
|
84
|
+
`maxBodySize`, `respondWith404`, `coerceDataFromSchema` and `bubbleErrors` reach
|
|
85
|
+
the request only on the runtimes that thread them through.
|
|
86
|
+
|
|
87
|
+
| Runtime | Accepts options | Where an oversized body is actually stopped |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| Express | `init(httpOptions)` | The `express.json` parser limit, fed by `maxBodySize` |
|
|
90
|
+
| Fastify | `init(httpOptions)` | Fastify's own `bodyLimit`, set from `maxBodySize` |
|
|
91
|
+
| uWS | HTTP handler only | The handler counts bytes itself and answers `413` |
|
|
92
|
+
| ws | Yes, on the handler | `maxPayload` on the `WebSocketServer` |
|
|
93
|
+
| Next.js | Only via your own `PikkuNextJS` handler | Next's own limits |
|
|
94
|
+
| Lambda | **No** | API Gateway's payload limit |
|
|
95
|
+
| Cloudflare | Partially — `runFetch(request, hibernation, options)` | The platform's limit |
|
|
96
|
+
| Azure | **No** | Azure's request limits |
|
|
97
|
+
|
|
98
|
+
On Fastify, leaving `maxBodySize` unset keeps Fastify's stricter 1MB default
|
|
99
|
+
rather than loosening it to Pikku's 10MB fallback. On uWS the chunks are dropped
|
|
100
|
+
rather than concatenated, so an oversized request never accumulates in memory.
|
|
101
|
+
|
|
102
|
+
### Scheduled tasks run differently on all three serverless runtimes
|
|
103
|
+
|
|
104
|
+
Same `wireScheduler` declaration, three behaviours. This is the one most likely
|
|
105
|
+
to produce a silent production bug.
|
|
106
|
+
|
|
107
|
+
- **Cloudflare** matches `controller.cron` and **returns after the first match**.
|
|
108
|
+
Two tasks sharing a cron expression means only one ever runs.
|
|
109
|
+
- **Lambda** runs **every** task in the bundle, ignores the event, and logs
|
|
110
|
+
rather than rethrows a failure — one bad task cannot stop the others.
|
|
111
|
+
- **Azure** runs **every** task in the bundle, ignores both the timer argument
|
|
112
|
+
and each task's own cron, and does **not** catch per-task failures — the first
|
|
113
|
+
throw aborts the rest.
|
|
114
|
+
|
|
115
|
+
Where a runtime runs everything in the bundle, the deployment unit is what
|
|
116
|
+
decides which tasks fire, not the schedule you wrote. Reach for
|
|
117
|
+
`runScheduledTask({ name })` when one deployment genuinely bundles several tasks
|
|
118
|
+
that must fire separately.
|
|
119
|
+
|
|
120
|
+
### CORS and health checks are not uniform
|
|
121
|
+
|
|
122
|
+
- **Express** registers the health check in the **constructor**, so it answers
|
|
123
|
+
before any middleware and cannot be wrapped in auth. `enableCors` must be
|
|
124
|
+
called before `init()`.
|
|
125
|
+
- **Fastify** registers it in `init()`, so nothing answers before that runs. Its
|
|
126
|
+
`enableCors` exists but **throws `Method not implemented.`** — register
|
|
127
|
+
`@fastify/cors` yourself.
|
|
128
|
+
- **uWS** registers it in `init()` and has **no** `enableCors`, no static assets
|
|
129
|
+
and no `content` support at all.
|
|
130
|
+
- Serverless runtimes have neither; the platform in front of them owns both.
|
|
131
|
+
On Lambda, only `runFetchV2` echoes the request `Origin`, so v1 preflights
|
|
132
|
+
fail in the browser unless API Gateway or CloudFront adds the header.
|
|
133
|
+
|
|
134
|
+
### Channels need a shared store off a single process
|
|
135
|
+
|
|
136
|
+
A long-running server can hold channel state in memory. Every per-invocation
|
|
137
|
+
runtime cannot: `$connect` and `$default` are separate invocations, so
|
|
138
|
+
`channelStore` must be a real shared store (`PgChannelStore` and friends).
|
|
139
|
+
Cloudflare instead keeps state in a Durable Object, and **Azure has no channel
|
|
140
|
+
support** — `createAzureWebSocketHandler` is a stub that answers `501`.
|
|
141
|
+
|
|
142
|
+
## What NOT to do
|
|
143
|
+
|
|
144
|
+
- Do not hand-roll Cloudflare's `setupServices`. It calls
|
|
145
|
+
`setSingletonServices()`, and the core runners resolve through that global
|
|
146
|
+
slot rather than the value you were returned — a setup that only returns
|
|
147
|
+
services leaves every request throwing "Singleton services not initialized" as
|
|
148
|
+
a CF `1101`.
|
|
149
|
+
- Do not discard what a Lambda WebSocket handler returns.
|
|
150
|
+
`connectWebsocket` returns a complete `APIGatewayProxyResult`; answering a
|
|
151
|
+
hardcoded `200` instead accepts every connection, including the ones your
|
|
152
|
+
channel's auth rejected.
|
|
153
|
+
- Do not bind a `WebSocketServer` to the HTTP server on `ws` or uWS.
|
|
154
|
+
`noServer: true` is required, not stylistic — the handler performs the upgrade
|
|
155
|
+
itself so middleware and auth run against the upgrade request first.
|
|
156
|
+
- Do not assume a runtime rethrows. Express, Azure and Lambda's `runFetch` each
|
|
157
|
+
swallow or flatten errors differently; the reference for your runtime says
|
|
158
|
+
which.
|