@pikku/skills 0.12.52 → 0.12.54

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 (104) hide show
  1. package/dist/skills.gen.js +2 -2
  2. package/package.json +1 -1
  3. package/skills/pikku-a11y/SKILL.md +22 -15
  4. package/skills/pikku-addon/SKILL.md +5 -5
  5. package/skills/pikku-addon/references/addon-package-manifest.md +7 -0
  6. package/skills/pikku-addon/references/openapi.md +11 -0
  7. package/skills/pikku-admin-to-fabric/SKILL.md +7 -21
  8. package/skills/pikku-agent/SKILL.md +6 -6
  9. package/skills/pikku-agent/references/agents.md +8 -1
  10. package/skills/pikku-agent/references/runner-vercel.md +2 -2
  11. package/skills/pikku-agent/references/voice.md +7 -1
  12. package/skills/pikku-architect/SKILL.md +1 -1
  13. package/skills/pikku-architect/references/plan-defects.md +2 -0
  14. package/skills/pikku-auth/SKILL.md +15 -36
  15. package/skills/pikku-auth/references/better-auth.md +22 -45
  16. package/skills/pikku-auth/references/jose.md +6 -0
  17. package/skills/pikku-auth/references/machine-auth.md +6 -0
  18. package/skills/pikku-auth/references/sessions.md +6 -0
  19. package/skills/pikku-blueprint-to-fabric/SKILL.md +6 -21
  20. package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +1 -1
  21. package/skills/pikku-build/SKILL.md +29 -26
  22. package/skills/pikku-build/references/app.md +60 -41
  23. package/skills/pikku-build/references/design.md +31 -19
  24. package/skills/pikku-build/references/feature.md +14 -0
  25. package/skills/pikku-build/references/mocks.md +23 -0
  26. package/skills/pikku-build/references/multi-app.md +9 -2
  27. package/skills/pikku-build/references/native-app.md +9 -0
  28. package/skills/pikku-build/references/openapi.md +7 -0
  29. package/skills/pikku-build/references/platform.md +20 -10
  30. package/skills/pikku-build/references/quick.md +14 -4
  31. package/skills/pikku-build/references/scenarios.md +12 -1
  32. package/skills/pikku-build/references/ship.md +6 -1
  33. package/skills/pikku-build/references/theming.md +74 -125
  34. package/skills/pikku-changes/SKILL.md +6 -6
  35. package/skills/pikku-concepts/SKILL.md +8 -17
  36. package/skills/pikku-concepts/references/concept-mapping.md +18 -0
  37. package/skills/pikku-convert-screen/SKILL.md +45 -0
  38. package/skills/pikku-deploy/references/cloudflare.md +8 -0
  39. package/skills/pikku-deploy/references/lambda.md +8 -0
  40. package/skills/pikku-deploy/references/uws.md +1 -1
  41. package/skills/pikku-deploy/references/ws.md +3 -3
  42. package/skills/pikku-emails/SKILL.md +3 -9
  43. package/skills/pikku-fabric/SKILL.md +7 -295
  44. package/skills/pikku-fabric/references/database.md +128 -0
  45. package/skills/pikku-fabric/references/debugging.md +9 -5
  46. package/skills/pikku-fabric/references/deploy.md +179 -0
  47. package/skills/pikku-guide/SKILL.md +10 -10
  48. package/skills/pikku-i18n/SKILL.md +7 -7
  49. package/skills/pikku-i18n/references/enum-labels.md +9 -0
  50. package/skills/pikku-i18n/references/messages.md +16 -3
  51. package/skills/pikku-i18n/references/rtl.md +18 -38
  52. package/skills/pikku-knowledge/SKILL.md +9 -10
  53. package/skills/pikku-kysely/SKILL.md +3 -21
  54. package/skills/pikku-list-query/SKILL.md +2 -2
  55. package/skills/pikku-meta/references/audit.md +9 -0
  56. package/skills/pikku-meta/references/meta.md +6 -0
  57. package/skills/pikku-meta/references/versioning.md +10 -0
  58. package/skills/pikku-middleware/SKILL.md +7 -16
  59. package/skills/pikku-n8n-import/SKILL.md +9 -1
  60. package/skills/pikku-n8n-import/references/addon-mapping.md +6 -0
  61. package/skills/pikku-n8n-import/references/code-translation.md +7 -0
  62. package/skills/pikku-permissions/SKILL.md +41 -10
  63. package/skills/pikku-permissions/references/scopes.md +59 -0
  64. package/skills/pikku-react/SKILL.md +2 -2
  65. package/skills/pikku-react/references/client.md +1 -34
  66. package/skills/pikku-react/references/react-query.md +11 -0
  67. package/skills/pikku-react/references/workflows.md +11 -0
  68. package/skills/pikku-realtime/SKILL.md +9 -3
  69. package/skills/pikku-report/SKILL.md +10 -9
  70. package/skills/pikku-scenario/SKILL.md +11 -17
  71. package/skills/pikku-scenario/references/persona-run.md +11 -1
  72. package/skills/pikku-scenario/references/personas.md +7 -1
  73. package/skills/pikku-seo/SKILL.md +12 -16
  74. package/skills/pikku-service-backends/SKILL.md +0 -10
  75. package/skills/pikku-service-backends/references/aws.md +6 -0
  76. package/skills/pikku-service-backends/references/mongodb.md +7 -0
  77. package/skills/pikku-service-backends/references/redis.md +6 -0
  78. package/skills/pikku-services/references/audit.md +10 -0
  79. package/skills/pikku-services/references/config.md +6 -0
  80. package/skills/pikku-services/references/services.md +8 -1
  81. package/skills/pikku-software-archaeology/SKILL.md +9 -15
  82. package/skills/pikku-software-archaeology/references/blueprint.schema.json +7 -7
  83. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  84. package/skills/pikku-software-archaeology/references/second-opinion.md +13 -1
  85. package/skills/pikku-tailwind/SKILL.md +111 -0
  86. package/skills/pikku-theme/SKILL.md +187 -0
  87. package/skills/pikku-theme/references/from-a-reference.md +44 -0
  88. package/skills/pikku-theme/references/presets.md +40 -0
  89. package/skills/pikku-ui/SKILL.md +41 -0
  90. package/skills/pikku-wiring/SKILL.md +13 -12
  91. package/skills/pikku-wiring/references/channel.md +7 -1
  92. package/skills/pikku-wiring/references/cli.md +7 -1
  93. package/skills/pikku-wiring/references/gateway-slack.md +6 -0
  94. package/skills/pikku-wiring/references/http.md +10 -3
  95. package/skills/pikku-wiring/references/mcp.md +8 -0
  96. package/skills/pikku-wiring/references/queue.md +6 -0
  97. package/skills/pikku-wiring/references/realtime.md +34 -4
  98. package/skills/pikku-wiring/references/rpc.md +6 -0
  99. package/skills/pikku-wiring/references/scheduler.md +6 -0
  100. package/skills/pikku-wiring/references/trigger.md +8 -0
  101. package/skills/pikku-workflow/SKILL.md +8 -6
  102. package/skills/pikku-auth/references/permissions.md +0 -261
  103. package/skills/pikku-mantine/SKILL.md +0 -80
  104. package/skills/pikku-wiring/references/realtime-other-routes.md +0 -23
@@ -3,14 +3,15 @@ name: pikku-wiring
3
3
  description: >-
4
4
  Use when exposing a Pikku function over a transport — HTTP routes and SSE, WebSocket channels,
5
5
  typed realtime pub/sub, internal and exposed RPC, queue workers, cron schedules, event triggers,
6
- MCP tools/resources/prompts, CLI commands, or a Slack gateway. Covers choosing the wiring, the
7
- model every wiring shares, and what differs: which function type each needs, where a session
8
- comes from, and which calls throw instead of returning. TRIGGER when: code uses wireHTTP,
9
- defineHTTPRoutes, wireChannel, wireQueueWorker, wireScheduler, wireTrigger, wireTriggerSource,
10
- wireCLI, wireMCPResource, wireMCPPrompt, `mcp: true`, `sse: true`, `expose: true`, rpc.invoke,
11
- SlackGatewayAdapter, or the user asks how to expose, route, schedule, queue, stream or publish a
12
- function. DO NOT TRIGGER when: writing the function body itself (use pikku-concepts), declaring
13
- authorization (use pikku-auth), or serving the app on a runtime (use pikku-deploy).
6
+ MCP tools/resources/prompts, CLI commands, or a Slack gateway. Covers choosing the wiring, what
7
+ every wiring shares, and what differs: function type, session source, and which calls throw
8
+ instead of returning. TRIGGER when: code uses wireHTTP, defineHTTPRoutes, wireChannel,
9
+ wireQueueWorker, wireScheduler, wireTrigger, wireTriggerSource, wireCLI, wireMCPResource,
10
+ wireMCPPrompt, `mcp: true`, `sse: true`, `expose: true`, rpc.invoke, SlackGatewayAdapter, or the
11
+ user asks how to expose, route, schedule, queue, stream or publish a function. DO NOT TRIGGER
12
+ when: writing the function body (use pikku-concepts), authorization (use pikku-permissions), serving on
13
+ a runtime (use pikku-deploy), building a live-updating view (use pikku-realtime), or sending
14
+ outgoing webhooks (use pikku-webhook).
14
15
  installGroups: [core]
15
16
  ---
16
17
 
@@ -53,7 +54,7 @@ Three consequences that hold for every wiring below:
53
54
  are merged into the function's `data` argument.
54
55
  - **Permissions are never declared on the wiring.** Wire-level permissions were
55
56
  removed in #972 — declare them on the function (`pikkuFunc({ permissions })`,
56
- see `pikku-auth`) or app-wide via `addGlobalPermission`. Tags and
57
+ see `pikku-permissions`) or app-wide via `addGlobalPermission`. Tags and
57
58
  patterns now target *middleware* only.
58
59
  - **The wire is the third argument**, not a service. `channel`, `rpc`, `session`,
59
60
  `setSession`, `mcp`, `queue`, `scheduledTask` and `cli` all live there.
@@ -83,15 +84,15 @@ subpath it exempts — the service implementations bootstrap picks.
83
84
 
84
85
  | Reach for | When | Reference |
85
86
  | --- | --- | --- |
86
- | **HTTP** | REST endpoints, web APIs, and SSE streams (`sse: true`, `get` only) | `references/http.md` |
87
+ | **HTTP** | REST endpoints, web APIs, and SSE streams (`sse: true`, `get` only) | `references/http.md`; every field in `references/http-options.md` |
87
88
  | **Channel** | A hand-designed WebSocket protocol with your own action routing | `references/channel.md` |
88
- | **Realtime** | Typed pub/sub push — the scaffolded `/events` channel and SSE topics | `references/realtime.md` |
89
+ | **Realtime** | Typed pub/sub push — the scaffolded `/events` channel and SSE topics, plus subscribing to any other `sse: true` route or channel from the client | `references/realtime.md` |
89
90
  | **RPC** | One function calling another, or dispatching a name from outside | `references/rpc.md` |
90
91
  | **Queue** | Reliable background work that must survive a crash and retry | `references/queue.md` |
91
92
  | **Scheduler** | Recurring work on a cron expression | `references/scheduler.md` |
92
93
  | **Trigger** | Reacting in-process to an external event source (Redis pub/sub, PG LISTEN) | `references/trigger.md` |
93
94
  | **MCP** | Exposing functions to an AI assistant as tools, resources or prompts | `references/mcp.md` |
94
- | **CLI** | A terminal program with commands, subcommands and options | `references/cli.md` |
95
+ | **CLI** | A terminal program with commands, subcommands and options | `references/cli.md`; end-to-end walkthrough in `references/cli-complete-example.md` |
95
96
  | **Gateway** | An inbound integration from a third-party product — Slack is the shipped adapter | `references/gateway-slack.md` |
96
97
 
97
98
  Realtime and Channel are the pair most often confused. If the shape is "server
@@ -1,5 +1,11 @@
1
1
  # Pikku WebSocket Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Usage Patterns
7
+ - Complete Example
8
+
3
9
  ## API Reference
4
10
 
5
11
  ### `wireChannel(config)`
@@ -33,7 +39,7 @@ wireChannel({
33
39
 
34
40
  Note there is **no `permissions` key on a message wiring** — wire-level
35
41
  permissions were removed in #972. Authorization lives on the function's own
36
- `permissions` field (see `pikku-auth`).
42
+ `permissions` field (see `pikku-permissions`).
37
43
 
38
44
  ### `pikkuChannelMiddleware(fn)`
39
45
 
@@ -1,5 +1,11 @@
1
1
  # Pikku CLI Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Usage Patterns
7
+ - Complete Example
8
+
3
9
  ## API Reference
4
10
 
5
11
  ### `wireCLI(config)`
@@ -212,4 +218,4 @@ session for local runs. Don't hand-write or edit the generated channel file.
212
218
 
213
219
  ## Complete Example
214
220
 
215
- For a full functions + renderers + nested-subcommand wiring walkthrough, see `cli-complete-example.md`.
221
+ For a full functions + renderers + nested-subcommand wiring walkthrough, see the CLI complete example listed in `SKILL.md`.
@@ -1,5 +1,11 @@
1
1
  # Pikku Gateway Slack
2
2
 
3
+ ## Contents
4
+
5
+ - Installation
6
+ - API Reference
7
+ - Usage Patterns
8
+
3
9
  ## Installation
4
10
 
5
11
  ```bash
@@ -1,5 +1,12 @@
1
1
  # Pikku HTTP Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Data Flow
7
+ - Usage Patterns
8
+ - Complete Example
9
+
3
10
  ## API Reference
4
11
 
5
12
  All three come from `#pikku/http` (the generated `.pikku/http/index.ts`), which
@@ -14,7 +21,7 @@ Function input/output types come from the function's own `input:`/`output:` zod
14
21
 
15
22
  Config cascading across groups: `basePath` concatenates down the chain, `tags` merge (union), `auth` child overrides parent.
16
23
 
17
- For the full option tables (every `wireHTTP` field, the `defineHTTPRoutes`/`wireHTTPRoutes` config shape), read `http-options.md`.
24
+ For the full option tables (every `wireHTTP` field, the `defineHTTPRoutes`/`wireHTTPRoutes` config shape), read the HTTP options reference listed in `SKILL.md`.
18
25
 
19
26
  ### `addHTTPMiddleware(pattern, middlewares)`
20
27
 
@@ -23,7 +30,7 @@ addHTTPMiddleware('*', [authBearer()]) // All routes
23
30
  addHTTPMiddleware('/api/*', [rateLimit()]) // Pattern match
24
31
  ```
25
32
 
26
- > HTTP-route-level permissions (`addHTTPPermission`, a `permissions` field on the wiring) were removed in #972. Declare authorization on the function definition (`pikkuFunc({ permissions })`, see `pikku-auth`), or app-wide via `addGlobalPermission`. Tags/patterns are for _middleware_ only now.
33
+ > HTTP-route-level permissions (`addHTTPPermission`, a `permissions` field on the wiring) were removed in #972. Declare authorization on the function definition (`pikkuFunc({ permissions })`, see `pikku-permissions`), or app-wide via `addGlobalPermission`. Tags/patterns are for _middleware_ only now.
27
34
 
28
35
  ## Data Flow
29
36
 
@@ -86,7 +93,7 @@ wireHTTP({ method: 'get', route: '/books', func: listBooks, auth: false })
86
93
  wireHTTP({ method: 'delete', route: '/books/:bookId', func: deleteBook })
87
94
  ```
88
95
 
89
- Authorization is not a wiring concern — declare it on the function via `permissions` (see `pikku-auth`), or app-wide via `addGlobalPermission`.
96
+ Authorization is not a wiring concern — declare it on the function via `permissions` (see `pikku-permissions`), or app-wide via `addGlobalPermission`.
90
97
 
91
98
  ### Middleware
92
99
 
@@ -1,5 +1,13 @@
1
1
  # Pikku MCP Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - The shape of MCP in Pikku
6
+ - API Reference
7
+ - Reaching the server
8
+ - Authentication
9
+ - Red flags
10
+
3
11
  ## The shape of MCP in Pikku
4
12
 
5
13
  MCP has three surfaces, and Pikku wires them differently:
@@ -1,5 +1,11 @@
1
1
  # Pikku Queue Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Usage Patterns
7
+ - Complete Example
8
+
3
9
  ## API Reference
4
10
 
5
11
  ### `wireQueueWorker(config)`
@@ -1,5 +1,17 @@
1
1
  # Pikku Realtime
2
2
 
3
+ ## Contents
4
+
5
+ - 1. Declare your topics
6
+ - 2. Enable the server side
7
+ - 3. Generate the typed client
8
+ - 4. Publish events from a function
9
+ - 5. Wire it up — share fetch with PikkuRPC
10
+ - 6. Subscribe from React
11
+ - Other SSE / WebSocket routes
12
+ - When to pick which transport
13
+ - What NOT to do
14
+
3
15
  ## 1. Declare your topics
4
16
 
5
17
  In your project's types file (e.g. `types/eventhub-topics.d.ts`):
@@ -95,7 +107,7 @@ export class PikkuRealtime {
95
107
  handler: (data: EventHubTopics[K]) => void
96
108
  ): { close: () => void }
97
109
 
98
- // generic escape hatches — see realtime-other-routes.md
110
+ // generic escape hatches — see "Other SSE / WebSocket routes" below
99
111
  subscribeToSSE<T>(
100
112
  path: string,
101
113
  handler: (data: T) => void
@@ -236,9 +248,27 @@ function TodoList() {
236
248
 
237
249
  ## Other SSE / WebSocket routes
238
250
 
239
- The same client also subscribes to generic `sse: true` routes and raw `wireChannel`
240
- sockets (`subscribeToSSE`, `connectToChannel`). See
241
- [realtime-other-routes.md](realtime-other-routes.md).
251
+ The same client also handles generic SSE + channel routes (not just
252
+ `/events` topics). Use the path; the base URL is inherited from `PikkuFetch`.
253
+
254
+ ```ts
255
+ // Any `sse: true` HTTP route
256
+ const sub = realtime.subscribeToSSE<{ progress: number }>(
257
+ `/workflow-run/${runId}/stream`,
258
+ (event) => setProgress(event.progress)
259
+ )
260
+ // later: sub.close()
261
+
262
+ // Any wireChannel — open a raw socket, wrap in PikkuWebSocket for typed I/O
263
+ const ws = realtime.connectToChannel('/ws/kanban')
264
+ const typed = new PikkuWebSocket<'kanban-live'>(ws)
265
+ typed.getRoute('command').subscribe('message', (data) => {
266
+ /* ... */
267
+ })
268
+ ```
269
+
270
+ Discover what's available with `pikku meta clients --json` — `channels` and any HTTP
271
+ `sse: true` routes are listed there.
242
272
 
243
273
  ## When to pick which transport
244
274
 
@@ -1,5 +1,11 @@
1
1
  # Pikku RPC Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Usage Patterns
7
+ - Complete Example
8
+
3
9
  ## API Reference
4
10
 
5
11
  ### RPC Methods (on `wire.rpc`)
@@ -1,5 +1,11 @@
1
1
  # Pikku Scheduled Tasks
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Usage Patterns
7
+ - Complete Example
8
+
3
9
  ## API Reference
4
10
 
5
11
  ### `wireScheduler(config)`
@@ -1,5 +1,13 @@
1
1
  # Pikku Trigger Wiring
2
2
 
3
+ ## Contents
4
+
5
+ - API Reference
6
+ - Starting triggers
7
+ - Webhook sources
8
+ - Usage Patterns
9
+ - Complete Example
10
+
3
11
  ## API Reference
4
12
 
5
13
  All three come from `#pikku`. A trigger is deliberately split in two: the
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: pikku-workflow
3
3
  description: >-
4
- Use when building multi-step workflows, state machines, or orchestration pipelines with Pikku.
5
- Covers pikkuWorkflowFunc, workflow steps (do, sleep, suspend), graph workflows, and HTTP wiring.
6
- TRIGGER when: code uses pikkuWorkflowFunc/pikkuWorkflowGraph, user asks about workflows,
7
- multi-step processes, durable execution, suspend/resume, or DAG orchestration. DO NOT TRIGGER
8
- when: user asks about simple background jobs (use pikku-wiring) or scheduled tasks (use
9
- pikku-wiring).
4
+ Use when building multi-step, durable workflows with Pikku — pikkuWorkflowFunc,
5
+ pikkuWorkflowGraph, workflow.do / sleep / suspend / approval, retries, compensate, sub-workflows,
6
+ and starting or resuming runs. Includes deciding whether something should be a workflow at all.
7
+ TRIGGER when: code uses pikkuWorkflowFunc or pikkuWorkflowGraph, or the user asks about
8
+ workflows, multi-step processes, durable execution, suspend/resume, approval gates, sagas or DAG
9
+ orchestration. DO NOT TRIGGER when: the user wants a single background job (use the queue
10
+ reference in pikku-wiring) or a cron task (use the scheduler reference in pikku-wiring), or is
11
+ converting an n8n export (use pikku-n8n-import).
10
12
  installGroups: [core]
11
13
  ---
12
14
 
@@ -1,261 +0,0 @@
1
- # Pikku Permissions
2
-
3
- ## ⛔ FIRST: is the caller a machine with a token? ⛔
4
-
5
- **Then this is NOT a permissions problem.** Resolve the token in `addHTTPMiddleware('*')` middleware that calls `setSession`, make the function a `pikkuFunc`, and gate it with `scopes`. A `permissions` check that verifies a bearer token and returns `true` is authentication wearing an authorization hat — and it leaves the function sessionless, so every body still has to work out who called it. See `references/machine-auth.md`. The only exception is a bootstrap endpoint whose caller has no identity yet (a shared-secret registration, a login): that one is sessionless and declares its gate here.
6
-
7
- ## The Rule
8
-
9
- **ALWAYS put authorization checks in the `permissions` field of `pikkuFunc` or `pikkuSessionlessFunc` — NEVER inside the `func` body.**
10
-
11
- This includes: org access checks, repo access checks, role checks, resource ownership, and any other authorization logic. The `permissions` field runs before `func` and is visible to the inspector, so the gate is declared rather than buried — which is what lets `pikku info permissions` and an audit see it at all. Alongside it sits `scopes` (see below) for grant-based gating; between them they are where Pikku enforces authorization. The one sanctioned exception is `permissionsInBody`, covered at the end.
12
-
13
- ```typescript
14
- // CORRECT
15
- export const deleteBook = pikkuFunc({
16
- func: async ({ db }, { bookId }) => {
17
- await db.deleteBook(bookId)
18
- },
19
- permissions: {
20
- owner: isBookOwner, // ← authorization here
21
- },
22
- })
23
-
24
- // WRONG — permission check inside func body
25
- export const deleteBook = pikkuFunc({
26
- func: async ({ db }, { bookId }, { session }) => {
27
- if (!session) throw new UnauthorizedError() // ← never do this
28
- await db.deleteBook(bookId)
29
- },
30
- })
31
- ```
32
-
33
-
34
- ## Permission Factories
35
-
36
- ### `pikkuAuth(fn)` — Session-Only Checks
37
-
38
- Use for checks that read the session but need no request data — and that assert
39
- something **beyond** merely having a session (a flag, a tier, a claim).
40
-
41
- ```typescript
42
- import { pikkuAuth } from '#pikku/auth'
43
-
44
- // Good: a real gate on the session's contents, not just its existence.
45
- export const isVerified = pikkuAuth(
46
- async (_services, session) => !!session?.emailVerified
47
- )
48
- ```
49
-
50
- **Do NOT write an "is signed in" permission.** A checker that just returns
51
- `!!session` is not authorization — it re-checks authentication, which the
52
- function already enforces. A function that needs a signed-in user sets
53
- `auth: true` (the default for `pikkuFunc`); it does not also carry a
54
- `permissions: { signedIn }`.
55
-
56
- ```typescript
57
- // WRONG — redundant with auth: true; adds a permission that gates nothing.
58
- export const isSignedIn = pikkuAuth(async (_s, session) => !!session)
59
- pikkuFunc({ auth: true, permissions: { signedIn: isSignedIn } /* ... */ })
60
-
61
- // RIGHT — auth: true already requires the session; permissions are for capability.
62
- pikkuFunc({ auth: true /* ... */ })
63
- ```
64
-
65
- A permission answers "_may this user do this?_" (role, ownership, tier) — never
66
- "_is there a session?_".
67
-
68
- ### `pikkuPermission(fn)` — Data-Aware Checks
69
-
70
- Use when authorization depends on the actual request data (e.g., resource ownership).
71
-
72
- ```typescript
73
- import { pikkuPermission } from '#pikku/auth'
74
-
75
- export const isBookOwner = pikkuPermission(
76
- async ({ db }, { bookId }, { session }) => {
77
- const book = await db.getBook(bookId)
78
- return book?.authorId === session?.userId
79
- }
80
- )
81
-
82
- export const hasBookAccess = pikkuPermission(
83
- async ({ db }, { bookId }, { session }) => {
84
- return await db.hasAccess(session?.userId, bookId)
85
- }
86
- )
87
- ```
88
-
89
- ## OR / AND Logic
90
-
91
- ```typescript
92
- permissions: {
93
- verified: isVerified, // OR: verified users can access
94
- owner: isBookOwner, // OR: owners can access
95
- reviewer: [isVerified, hasBookAccess], // AND: both must pass
96
- }
97
- // Logic: verified OR owner OR (isVerified AND hasBookAccess)
98
- ```
99
-
100
- Groups are OR'd. Entries within a group array are AND'd.
101
-
102
- ## Where to Apply Permissions
103
-
104
- ### Per-Function (preferred)
105
-
106
- ```typescript
107
- export const deleteBook = pikkuFunc({
108
- func: async ({ db }, { bookId }) => {
109
- await db.deleteBook(bookId)
110
- },
111
- permissions: {
112
- verified: isVerified,
113
- owner: isBookOwner,
114
- },
115
- })
116
- ```
117
-
118
- ### Global (`addGlobalPermission`) — App-Wide AND Gate
119
-
120
- A global permission is an app-wide baseline that **every** function must additionally pass. It is an independent AND gate: it can only ever _narrow_ access — it never grants access a function's own `permissions` would deny.
121
-
122
- ```typescript
123
- import { addGlobalPermission } from '#pikku/auth'
124
-
125
- addGlobalPermission([isEmployee]) // every function now also requires an employee session
126
- ```
127
-
128
- Multiple `addGlobalPermission` calls accumulate and are AND'd together.
129
-
130
- > Wire-, tag-, and HTTP-route-level permissions (`addHTTPPermission`, `addTagPermission`, and a `permissions` field on HTTP/channel/MCP wirings) were **removed in #972**. Permissions now live only on the function definition, plus the optional global gate. Tags are organizational only — use tag/HTTP _middleware_ (`addTagMiddleware`, `addHTTPMiddleware`) for cross-cutting request handling, not authorization.
131
-
132
- ## Scopes — the AND Gate Above Permissions
133
-
134
- Scopes answer "what was this session granted?" before permissions ask "may this
135
- user do this to this resource?". They are AND-ed: every scope listed must be
136
- held. Because they are checked first and fail closed, a scope can only ever
137
- _narrow_ access — it never grants what `permissions` would deny.
138
-
139
- Declare the scope tree once with `defineScope`. The body is a no-op that
140
- tree-shakes away; the CLI reads the call by AST and generates a `ScopeId` union,
141
- so a function naming an undeclared scope fails the build rather than silently
142
- gating on nothing.
143
-
144
- ```typescript
145
- // src/scopes.ts
146
- import { defineScope } from '#pikku/scopes'
147
-
148
- defineScope({
149
- admin: {
150
- displayName: 'Administration',
151
- description: 'Administrative access',
152
- scopes: {
153
- invoices: {
154
- description: 'Invoice management',
155
- scopes: {
156
- create: { description: 'Create invoices' },
157
- void: { description: 'Void invoices' },
158
- },
159
- },
160
- },
161
- },
162
- billing: {},
163
- })
164
- ```
165
-
166
- Every node is grantable, keyed by segment: the above yields `admin`,
167
- `admin:invoices`, `admin:invoices:create`, `admin:invoices:void` and `billing`.
168
- Scopes may be declared across more than one file — the declarations merge.
169
-
170
- ```typescript
171
- export const voidInvoice = pikkuFunc({
172
- scopes: ['admin:invoices:void'],
173
- permissions: { owner: isInvoiceOwner },
174
- func: async ({ db }, { invoiceId }) => { ... },
175
- })
176
- ```
177
-
178
- A grant satisfies a required scope if it is the scope itself, an ancestor of it,
179
- or a wildcard at any level — so a session holding `admin` satisfies
180
- `admin:invoices:void`, and `admin:*` does too. A missing scope throws
181
- `MissingScopeError` naming the first one that failed.
182
-
183
- `scopes` requires a session and so is unavailable on `pikkuSessionlessFunc`:
184
- scopes fail closed, an anonymous caller holds none, and a sessionless function
185
- with scopes would reject every caller it exists to serve. Gate those with
186
- `permissions`, which receive the optional session and may pass anonymous.
187
-
188
- ## The Three Gates
189
-
190
- Authorization is three independent gates, evaluated in this order, all of which must pass:
191
-
192
- 1. **Scopes** (`scopes`) — AND'd, checked before input validation. Fails closed.
193
- 2. **Global permissions** (`addGlobalPermission`) — AND'd together. A broad baseline that can only narrow access.
194
- 3. **The function's own `permissions`** — OR'd groups (OR-of-ANDs), as above.
195
-
196
- The gates are independent: a broad global (e.g. `isEmployee`) can **never** satisfy an admin-only function's own requirement. Each function still enforces its own `scopes` and `permissions` in full.
197
-
198
- ## The Sanctioned Exception: `permissionsInBody`
199
-
200
- A few checks genuinely cannot be expressed as a permission — verifying a webhook
201
- signature, a signed token, or an invite code, where the "identity" arrives in the
202
- payload and there is no session to check. For those, declare
203
- `permissionsInBody: true` on the function and keep the check in the body.
204
-
205
- ```typescript
206
- export const handleStripeWebhook = pikkuSessionlessFunc({
207
- permissionsInBody: true,
208
- auth: false,
209
- func: async ({ stripe }, data, { http }) => {
210
- stripe.webhooks.constructEvent(
211
- data.raw,
212
- http.request.header('stripe-signature'),
213
- secret
214
- )
215
- // ...
216
- },
217
- })
218
- ```
219
-
220
- This is a last resort, and it is purely declarative — it grants nothing and
221
- enforces nothing. Its only job is to tell the auditor that this function's
222
- apparent openness is deliberate, so asserting it falsely disables the very check
223
- that would have caught the mistake. It requires `"allow": { "permissionsInBody": true }`
224
- in `pikku.config.json`, which keeps the decision visible at the project level.
225
- Prefer `permissions` whenever the check can be expressed as one — they are
226
- declared, inspectable, and reusable.
227
-
228
- ## Complete Example
229
-
230
- ```typescript
231
- // src/permissions.ts
232
- import { pikkuAuth, pikkuPermission } from '#pikku/auth'
233
-
234
- export const isVerified = pikkuAuth(
235
- async (_services, session) => !!session?.emailVerified
236
- )
237
-
238
- export const isOrgMember = pikkuPermission(
239
- async ({ db }, { orgId }, { session }) => {
240
- return await db.isMember(session?.userId, orgId)
241
- }
242
- )
243
-
244
- // src/functions/org.function.ts
245
- export const deleteOrg = pikkuFunc({
246
- func: async ({ db }, { orgId }) => {
247
- await db.deleteOrg(orgId)
248
- },
249
- permissions: {
250
- verified: isVerified,
251
- owner: [isVerified, isOrgMember],
252
- },
253
- })
254
- ```
255
-
256
- ## After Changes
257
-
258
- ```bash
259
- pikku all # regenerate if wirings changed
260
- pikku all --tsc # regenerate, then verify permission checker types (fails on type errors)
261
- ```
@@ -1,80 +0,0 @@
1
- ---
2
- name: pikku-mantine
3
- description: >-
4
- Use when building a Mantine UI on top of a Pikku backend — rendering dates that came back from a
5
- generated client, keeping layout flow-relative so the app survives an RTL locale, and branching on
6
- colour scheme without hardcoding a shade. TRIGGER when: putting a value from usePikkuQuery or an
7
- RPC response on screen, writing margins/padding/alignment in Mantine props or CSS, choosing a date
8
- input, or handling light/dark. DO NOT TRIGGER when: the data does not come from a Pikku client
9
- (this is only about what the generated clients hand you), or for user-facing copy (use pikku-i18n).
10
- installGroups: [client]
11
- ---
12
-
13
- # Mantine on a Pikku client
14
-
15
- ## Dates — always format before rendering
16
-
17
- The generated clients run `transformDates`, which revives **fully-zoned ISO-8601 instants** —
18
- `2026-03-14T08:12:00Z`, `2026-03-14T08:12:00.000+01:00` — into `Date` objects and touches nothing
19
- else. A bare `2026-03-14`, a zoneless `2026-03-14T08:12:00` and an impossible `2026-02-31T00:00:00Z`
20
- all stay the strings the server sent. So a field's runtime type follows the VALUE, not the schema:
21
- one column can arrive as a `Date` from one row and a string from the next.
22
-
23
- Two consequences, and both compile:
24
-
25
- - **A string method on one white-screens the page.** `row.createdAt.split('T')[0]` type-checks
26
- against nothing useful and blows up at runtime. There is no string to slice.
27
- - **A raw `Date` dropped into JSX crashes the route.** `<Text>{row.createdAt}</Text>`, a table cell,
28
- a `<Badge>` — React throws `Objects are not valid as a React child (found: [object Date])` and the
29
- page falls into its error boundary. Nothing catches it before the screen is white, which makes it
30
- the most common broken page in a build.
31
-
32
- **Format with dayjs.** It is Mantine's own date library, already shipped alongside `@mantine/dates`,
33
- and it takes either a `Date` or a string. Never `toLocaleDateString`, `date-fns` or `luxon`.
34
-
35
- ```tsx
36
- import dayjs from 'dayjs'
37
-
38
- <Text>{dayjs(row.dueOn).format('D MMM YYYY')}</Text> // 15 Jun 2026
39
- <Text>{dayjs(row.createdAt).format('D MMM YYYY, HH:mm')}</Text>
40
- ```
41
-
42
- A relative "2 days ago" via dayjs `relativeTime` is fine. Coercing instead of formatting
43
- (`` `${d}` ``, `String(d)`, `d + ''`) does not crash but prints
44
- `Mon Jun 15 2026 02:00:00 GMT+0200` — a different bug, equally wrong.
45
-
46
- Date **inputs** are `@mantine/dates` — `DatePickerInput`, `DatePicker`, `Calendar` — never a raw
47
- `<TextInput type="date">`. Those are pickers and they are not a schedule: Mantine ships no
48
- week/time-grid component, so a diary, rota, timetable or booking week is a grid you build, not a
49
- `Calendar` with the time-of-day left out. `Calendar` with `renderDay` IS right for "a few things on
50
- each day of a month", like a content calendar or a holiday planner.
51
-
52
- All of this applies to stub and fixture dates exactly as it does to real data.
53
-
54
- ## RTL-safe styles
55
-
56
- Write layout styles flow-relative so the UI works in both LTR and RTL languages. Pikku's i18n ships
57
- Arabic, Hebrew, Farsi and Urdu support, and a physical margin is what breaks under it.
58
-
59
- | Avoid | Use instead |
60
- | ----------------------------- | ------------------------------------------ |
61
- | `ml`, `mr`, `pl`, `pr` | `ms`, `me`, `ps`, `pe` |
62
- | `text-align: left/right` | `text-align: start/end` |
63
- | `margin-left`, `margin-right` | `margin-inline-start`, `margin-inline-end` |
64
- | `flex-direction: row-reverse` | `dir` attribute or logical properties |
65
-
66
- Mantine shorthand: `ms` = margin-inline-start, `me` = margin-inline-end, `ps` = padding-inline-start,
67
- `pe` = padding-inline-end.
68
-
69
- ## Dark mode
70
-
71
- Use Mantine's `light-dark()` utility or `useMantineColorScheme`, and only with colours that already
72
- come from the theme — never introduce a literal colour or a shade string for one scheme.
73
-
74
- ```tsx
75
- // Correct
76
- <Box bg="var(--mantine-color-body)">
77
-
78
- // Wrong — scheme branch with hardcoded Mantine shades
79
- <Box bg={theme.colorScheme === 'dark' ? 'dark.6' : 'gray.0'}>
80
- ```
@@ -1,23 +0,0 @@
1
- # Subscribing to other SSE / WebSocket routes
2
-
3
- The same `PikkuRealtime` client also handles generic SSE + channel routes (not just
4
- `/events` topics). Use the path; the base URL is inherited from `PikkuFetch`.
5
-
6
- ```ts
7
- // Any `sse: true` HTTP route
8
- const sub = realtime.subscribeToSSE<{ progress: number }>(
9
- `/workflow-run/${runId}/stream`,
10
- (event) => setProgress(event.progress)
11
- )
12
- // later: sub.close()
13
-
14
- // Any wireChannel — open a raw socket, wrap in PikkuWebSocket for typed I/O
15
- const ws = realtime.connectToChannel('/ws/kanban')
16
- const typed = new PikkuWebSocket<'kanban-live'>(ws)
17
- typed.getRoute('command').subscribe('message', (data) => {
18
- /* ... */
19
- })
20
- ```
21
-
22
- Discover what's available with `pikku meta clients --json` — `channels` and any HTTP
23
- `sse: true` routes are listed there.