@pikku/skills 0.12.14 → 0.12.16
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 +60 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-ai-vercel/SKILL.md +1 -0
- package/skills/pikku-better-auth/SKILL.md +211 -24
- package/skills/pikku-build-app/SKILL.md +3 -1
- package/skills/pikku-build-quick/SKILL.md +3 -1
- package/skills/pikku-cli/SKILL.md +1 -1
- package/skills/pikku-emails/SKILL.md +1 -0
- package/skills/pikku-fabric/SKILL.md +19 -5
- package/skills/pikku-i18n/SKILL.md +1 -1
- package/skills/pikku-jose/SKILL.md +1 -0
- package/skills/pikku-kysely/SKILL.md +1 -0
- package/skills/pikku-machine-auth/SKILL.md +1 -0
- package/skills/pikku-meta/SKILL.md +139 -0
- package/skills/pikku-n8n-import/SKILL.md +1 -0
- package/skills/pikku-react-query/SKILL.md +1 -1
- package/skills/pikku-rpc/SKILL.md +1 -0
- package/skills/pikku-ws/SKILL.md +1 -0
- package/skills/pikku-info/SKILL.md +0 -109
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ description: >-
|
|
|
6
6
|
VercelAgentRunner, user asks about Vercel AI SDK integration, AI agent runners, or
|
|
7
7
|
@pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-agent) or
|
|
8
8
|
voice I/O (use pikku-ai-voice).
|
|
9
|
+
installGroups: [fabric]
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# Pikku AI Vercel (Agent Runner)
|
|
@@ -10,6 +10,7 @@ description: >-
|
|
|
10
10
|
user asks about ANY form of authentication, login, logout, sessions, or user identity — always
|
|
11
11
|
answer with this skill. DO NOT TRIGGER when: user asks about JWT middleware (use pikku-security)
|
|
12
12
|
or custom session services (use pikku-services).
|
|
13
|
+
installGroups: [fabric]
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
# Pikku Better Auth Integration
|
|
@@ -136,21 +137,21 @@ resolves the caller's scopes through the registered `ScopeService` and checks th
|
|
|
136
137
|
`admin:*` tree (`ADMIN_SCOPES` exports the ids so you never spell them as bare
|
|
137
138
|
strings):
|
|
138
139
|
|
|
139
|
-
| Gate | Scope required
|
|
140
|
-
| -------------------------------------------------------------------- |
|
|
141
|
-
| `impersonation` (`betterAuthSession` / `betterAuthStatelessSession`) | `admin:impersonate`
|
|
142
|
-
| `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link`
|
|
143
|
-
| the console's user directory | `admin:users:list`
|
|
144
|
-
| create a user out of band | `admin:users:create`
|
|
145
|
-
| ban / unban | `admin:users:ban`
|
|
146
|
-
| delete a user and their data | `admin:users:remove`
|
|
147
|
-
| revoke a user's sessions | `admin:users:sessions`
|
|
148
|
-
| set a user's password | `admin:users:password`
|
|
149
|
-
| read credential values and who holds them | `admin:credentials:read`
|
|
140
|
+
| Gate | Scope required |
|
|
141
|
+
| -------------------------------------------------------------------- | -------------------------- |
|
|
142
|
+
| `impersonation` (`betterAuthSession` / `betterAuthStatelessSession`) | `admin:impersonate` |
|
|
143
|
+
| `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link` |
|
|
144
|
+
| the console's user directory | `admin:users:list` |
|
|
145
|
+
| create a user out of band | `admin:users:create` |
|
|
146
|
+
| ban / unban | `admin:users:ban` |
|
|
147
|
+
| delete a user and their data | `admin:users:remove` |
|
|
148
|
+
| revoke a user's sessions | `admin:users:sessions` |
|
|
149
|
+
| set a user's password | `admin:users:password` |
|
|
150
|
+
| read credential values and who holds them | `admin:credentials:read` |
|
|
150
151
|
| set and delete credentials | `admin:credentials:manage` |
|
|
151
|
-
| view declared scopes, roles, and who holds them | `admin:scopes:read`
|
|
152
|
-
| create roles, change their scopes, grant them | `admin:scopes:manage`
|
|
153
|
-
| read the audit trail | `admin:audit:read`
|
|
152
|
+
| view declared scopes, roles, and who holds them | `admin:scopes:read` |
|
|
153
|
+
| create roles, change their scopes, grant them | `admin:scopes:manage` |
|
|
154
|
+
| read the audit trail | `admin:audit:read` |
|
|
154
155
|
|
|
155
156
|
Holding the bare `admin` scope satisfies all of them — a parent grant covers
|
|
156
157
|
everything nested beneath it — so `admin` is the direct replacement for the old
|
|
@@ -246,6 +247,112 @@ create a session for a banned user, lapsing an expired ban as it goes. It makes
|
|
|
246
247
|
no authorization decision — who may ban is decided by `admin:users:ban` — so it
|
|
247
248
|
never needs to know about scopes or roles.
|
|
248
249
|
|
|
250
|
+
### The plugins `@pikku/better-auth` ships
|
|
251
|
+
|
|
252
|
+
Five, all imported from the package root and passed to `betterAuth({ plugins })`
|
|
253
|
+
like any other. None is automatic — an app wires the ones it needs.
|
|
254
|
+
|
|
255
|
+
| Plugin | Plugin `id` | Adds | Use it when |
|
|
256
|
+
| ------------------- | ------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
257
|
+
| `ban()` | `pikku-ban` | `user.banned/banReason/banExpires` | You ban users (the schema + enforcement half of the above) |
|
|
258
|
+
| `actor()` | `actor` | `POST /sign-in/actor`, `user.actor` | Scenarios or a dev switcher sign in as a persona |
|
|
259
|
+
| `credentialOAuth()` | `credential-oauth` | `POST /credential-oauth/link`, `/credential-oauth/callback/:providerId` | An app links OAuth2 **API credentials** for a user |
|
|
260
|
+
| `delegatedAuth()` | `delegated-auth` | `POST /sign-in/delegated` | An imported upstream API is the system of record for identity |
|
|
261
|
+
| `fabric()` | `fabric` | `POST /sign-in/fabric` | A Fabric-deployed app lets a control-plane operator in |
|
|
262
|
+
|
|
263
|
+
The plugin's `id` is what better-auth stores; the **export name** is what the
|
|
264
|
+
inspector reads off your `plugins` array and what generated metadata is keyed
|
|
265
|
+
by, so the two differ for `ban` and `delegatedAuth`.
|
|
266
|
+
|
|
267
|
+
#### `credentialOAuth()` — link API credentials, not identities
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
credentialOAuth({
|
|
271
|
+
config: [
|
|
272
|
+
{
|
|
273
|
+
providerId: 'github',
|
|
274
|
+
type: 'wire',
|
|
275
|
+
clientId,
|
|
276
|
+
clientSecret,
|
|
277
|
+
authorizationUrl,
|
|
278
|
+
tokenUrl,
|
|
279
|
+
scopes: ['repo'],
|
|
280
|
+
},
|
|
281
|
+
{ providerId: 'slack', type: 'singleton' /* … */ },
|
|
282
|
+
],
|
|
283
|
+
scopeService,
|
|
284
|
+
logger,
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Wraps better-auth's `genericOAuth` to keep its token exchange and refresh, and
|
|
289
|
+
replaces only the two identity-bound endpoints. `genericOAuth`'s own
|
|
290
|
+
`/oauth2/link` models an identity **provider**: it demands a userinfo response
|
|
291
|
+
and refuses to link when the provider's email differs from the user's. A
|
|
292
|
+
credential is not an identity — most credential providers expose only
|
|
293
|
+
`/authorize` and `/token` — so the account row is keyed on _whose_ credential it
|
|
294
|
+
is (`accountId` = the linking user's id), making `(providerId, userId)` unique
|
|
295
|
+
by construction. Tokens land in better-auth's `account` table, so
|
|
296
|
+
`auth.api.getAccessToken()` refreshes them on read.
|
|
297
|
+
|
|
298
|
+
`type` decides the blast radius:
|
|
299
|
+
|
|
300
|
+
- **`wire`** — every user links their own, and the credential is read on
|
|
301
|
+
the wire that runs as them. Signed in is enough.
|
|
302
|
+
- **`singleton`** — one token the whole app shares, owned by a reserved
|
|
303
|
+
`pikku-platform` user row created on demand. Rebinding it changes the
|
|
304
|
+
credential for _everyone_, so it is gated on `admin:credentials:link` (or the
|
|
305
|
+
`admin` root above it), and **fails closed** with no `ScopeService`. Override
|
|
306
|
+
the whole gate with `canLinkSingleton`.
|
|
307
|
+
|
|
308
|
+
An undeclared `providerId` is a 404; an anonymous caller a 401; a refused
|
|
309
|
+
singleton a 403 that leaves no platform user behind.
|
|
310
|
+
|
|
311
|
+
#### `delegatedAuth()` — the upstream API is the identity provider
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
delegatedAuth({
|
|
315
|
+
authenticate: async ({ email, password, apiKey }) => upstream.login(...),
|
|
316
|
+
storeCredential: (userId, identity) =>
|
|
317
|
+
credentialService.set('acme', identity.credential, userId),
|
|
318
|
+
defaultRole: 'member',
|
|
319
|
+
mapRole: (upstreamRole) => ROLE_MAP[upstreamRole],
|
|
320
|
+
scopeService,
|
|
321
|
+
logger,
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`POST /sign-in/delegated` forwards the credentials the user already has to
|
|
326
|
+
`authenticate`. On success it JIT-provisions a real user row (email-keyed and
|
|
327
|
+
`emailVerified` — the upstream just verified them), links it via an `account`
|
|
328
|
+
row (`providerId: 'delegated'`, `accountId: externalId`), persists the upstream
|
|
329
|
+
token **before** minting the session, and returns a normal session cookie.
|
|
330
|
+
Passwords are never stored. Exactly one upstream per app: additional imported
|
|
331
|
+
APIs are linked integrations (`credentialOAuth`), not extra login methods.
|
|
332
|
+
|
|
333
|
+
A resolved role is granted through the `ScopeService` as a pikku role, so it
|
|
334
|
+
lands in `pikku_user_role` rather than on a column. A role the app never
|
|
335
|
+
defined is a provisioning gap, not a sign-in failure — the grant is dropped with
|
|
336
|
+
a warning and the user still gets in.
|
|
337
|
+
|
|
338
|
+
`storeCredential` failing, by contrast, **fails the sign-in**: every proxied
|
|
339
|
+
call would be dead anyway.
|
|
340
|
+
|
|
341
|
+
#### `fabric()` — control-plane operator sign-in
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
fabric({ publicKey: FABRIC_AUTH_PUBLIC_KEY, scopeService, logger })
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`POST /sign-in/fabric` verifies a short-lived RS256 token that the Fabric
|
|
348
|
+
control plane signed for an operator session, then signs them into a synthetic
|
|
349
|
+
`fabric-<id>@fabric.internal` row holding the `admin` scope. Asymmetric on
|
|
350
|
+
purpose: the app holds only the public key, so it can never forge an operator
|
|
351
|
+
login, and the same `FABRIC_AUTH_PUBLIC_KEY` is distributed to every stage with
|
|
352
|
+
no per-environment secret. A missing or empty key disables the endpoint, and a
|
|
353
|
+
token whose `purpose` claim is not `fabric-admin` is rejected. Without a
|
|
354
|
+
`ScopeService` the operator signs in holding nothing.
|
|
355
|
+
|
|
249
356
|
### 2. Production database adapter
|
|
250
357
|
|
|
251
358
|
For real deployments swap `memoryAdapter` for the Kysely adapter backed by an injected DB. Better Auth owns its own tables (`user`, `session`, `account`, `verification`, plus plugin tables) — generate its schema with `npx @better-auth/cli generate` and apply it as a migration.
|
|
@@ -370,14 +477,35 @@ plugins: [actor({ secret: SCENARIO_ACTOR_SECRET })]
|
|
|
370
477
|
session cookie. `secret` may also be a (possibly async) function, so it can come
|
|
371
478
|
off the secrets service instead of a captured value.
|
|
372
479
|
|
|
480
|
+
**Which command is running decides whether it works, not whether a secret is
|
|
481
|
+
set.** `pikku dev` sets `PIKKU_DEV_ACTOR_SIGN_IN` and mints an ephemeral
|
|
482
|
+
`SCENARIO_ACTOR_SECRET` for the run, so local development needs no configuration
|
|
483
|
+
at all. Everywhere else the endpoint refuses (`Actor sign-in is disabled outside
|
|
484
|
+
\`pikku dev\``) — `pikku serve` clears the marker outright, so a secret that
|
|
485
|
+
leaked into a production environment enables nothing and gets a warning naming
|
|
486
|
+
itself instead.
|
|
487
|
+
|
|
488
|
+
A stage that genuinely must run scenarios opts in on purpose, with
|
|
489
|
+
`PIKKU_ALLOW_ACTOR_SIGN_IN=passwordless-actor-sign-in`. Any other value is
|
|
490
|
+
ignored and warned about, so the hatch cannot be opened by copying a `true` from
|
|
491
|
+
the line above, and it is the only hatch — there is no build-time option, because
|
|
492
|
+
an option compiled into the bundle cannot be audited from the environment it
|
|
493
|
+
runs in.
|
|
494
|
+
|
|
495
|
+
**Signing in and provisioning are separate powers.** An unknown address becomes
|
|
496
|
+
an `actor: true` row only under `pikku dev`. With the opt-in set, a stage signs
|
|
497
|
+
in as the personas the deployment provisioned when it started and refuses
|
|
498
|
+
everything else (`No actor account exists for that address`), so holding the
|
|
499
|
+
secret on such a stage does not let anyone invent identities. Those rows are
|
|
500
|
+
written by `provisionPersonas` from `@pikku/better-auth`, called in the server's
|
|
501
|
+
own lifecycle, so provisioning needs no actor secret and works on a stage whose
|
|
502
|
+
endpoint is shut.
|
|
503
|
+
|
|
373
504
|
**`SCENARIO_ACTOR_SECRET` is a credential as powerful as the most privileged
|
|
374
|
-
persona.**
|
|
375
|
-
`admin` persona is an actor holding real admin — anyone with the secret
|
|
376
|
-
a session as one.
|
|
377
|
-
|
|
378
|
-
which is the supported switch (`Actor sign-in is not configured`), rather than
|
|
379
|
-
conditionally registering the plugin. Do not treat "actors only" as a licence to
|
|
380
|
-
enable it in production.
|
|
505
|
+
persona.** Provisioning grants declared roles to actor accounts, so an
|
|
506
|
+
`admin` persona is an actor holding real admin — anyone with the secret _and_ the
|
|
507
|
+
opt-in can take a session as one. Do not treat "actors only" as a licence to open
|
|
508
|
+
the hatch in production.
|
|
381
509
|
|
|
382
510
|
Within that boundary, three properties bound the damage:
|
|
383
511
|
|
|
@@ -385,9 +513,9 @@ Within that boundary, three properties bound the damage:
|
|
|
385
513
|
column; an email matching a row without it is refused with `User is not an
|
|
386
514
|
actor`. So the secret cannot take over a **real user's** account — the blast
|
|
387
515
|
radius is the actor accounts and whatever roles they were granted.
|
|
388
|
-
- **Unknown emails are created
|
|
389
|
-
|
|
390
|
-
|
|
516
|
+
- **Unknown emails are created only under `pikku dev`**, flagged `actor: true`,
|
|
517
|
+
so a local scenario declaring a new persona needs no seed step. Anywhere else
|
|
518
|
+
the account has to have been provisioned at boot first.
|
|
391
519
|
- **The comparison is constant-time and length-hiding**, so a wrong secret leaks
|
|
392
520
|
neither the length nor a prefix of the right one.
|
|
393
521
|
|
|
@@ -395,6 +523,65 @@ This is the endpoint `pikku scenario` signs its actors in through, and the one
|
|
|
395
523
|
the frontend dev switcher posts to — see `pikku-scenario` for declaring the
|
|
396
524
|
actors and `pikku-react` for `useDevActors()`.
|
|
397
525
|
|
|
526
|
+
### Provisioning personas (`provisionPersonas`)
|
|
527
|
+
|
|
528
|
+
Anywhere but `pikku dev`, the accounts have to exist before anyone signs in.
|
|
529
|
+
The deployment creates them itself, from its own lifecycle:
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
import { provisionPersonas } from '@pikku/better-auth'
|
|
533
|
+
import {
|
|
534
|
+
personaConfigs,
|
|
535
|
+
personaEnvironments,
|
|
536
|
+
} from '#pikku/pikku-personas.gen.js'
|
|
537
|
+
|
|
538
|
+
await provisionPersonas(singletonServices, {
|
|
539
|
+
personas: personaConfigs,
|
|
540
|
+
environments: personaEnvironments,
|
|
541
|
+
})
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
It runs where the database already is, which is the point: the CLI has no
|
|
545
|
+
connection to a deployed environment's database — it resolves one from the local
|
|
546
|
+
project config — so a `pikku persona sync staging` that wrote rows would write
|
|
547
|
+
them to whatever database the checkout happened to point at. `pikku persona sync
|
|
548
|
+
<environment>` still exists, and reports who that environment will provision and
|
|
549
|
+
why anyone was skipped, which is what you run _before_ the deploy.
|
|
550
|
+
|
|
551
|
+
It creates missing accounts as `actor: true`, applies the roles each persona
|
|
552
|
+
declares, and is additive — it never revokes. `PIKKU_ENV` (or an explicit
|
|
553
|
+
`environment`) selects who is eligible, through the same rule that decides who
|
|
554
|
+
may run there; an address already held by a real, non-actor user throws rather
|
|
555
|
+
than being granted the persona's roles.
|
|
556
|
+
|
|
557
|
+
**Deleting a persona does not delete its account.** Being additive leaves a hole:
|
|
558
|
+
the account keeps every role it was granted, and the actor endpoint authenticates
|
|
559
|
+
on the `actor` column alone without consulting the declaration — so an `admin`
|
|
560
|
+
persona nobody declares any more is still a live way in wherever that endpoint is
|
|
561
|
+
open. By default provisioning warns about those accounts and changes nothing.
|
|
562
|
+
`orphans: 'ban'` shuts them:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
await provisionPersonas(singletonServices, {
|
|
566
|
+
personas: personaConfigs,
|
|
567
|
+
environments: personaEnvironments,
|
|
568
|
+
orphans: 'ban',
|
|
569
|
+
})
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
It writes the same `banned` column the console's ban RPC writes (so it needs the
|
|
573
|
+
`ban()` plugin wired, and says so if it isn't), revokes the account's sessions,
|
|
574
|
+
and leaves the row, its grants and its history intact — provisioning lifts the
|
|
575
|
+
ban again by itself if the persona comes back. Deleting is deliberately not
|
|
576
|
+
offered: an actor row is referenced by whatever those scenarios did while it
|
|
577
|
+
existed.
|
|
578
|
+
|
|
579
|
+
`report` is the default because a rolling deploy runs the new replica's
|
|
580
|
+
provisioning while the old replica is still serving, so for the length of that
|
|
581
|
+
overlap "no persona claims this" is a statement about the newer declaration only.
|
|
582
|
+
A persona pinned to another environment counts as unclaimed here — it has no
|
|
583
|
+
business holding a signable account in an environment its own rule refuses it.
|
|
584
|
+
|
|
398
585
|
---
|
|
399
586
|
|
|
400
587
|
## Secret Management
|
|
@@ -325,7 +325,9 @@ or not the note says `built`.
|
|
|
325
325
|
for working on an empty state the test data would hide). Because reset always
|
|
326
326
|
arrives at a database it just wiped, **the seed file is plain `INSERT`s** — no
|
|
327
327
|
`ON CONFLICT DO NOTHING`, no `INSERT OR IGNORE`. Nothing ever applies it
|
|
328
|
-
twice, so it never has to defend itself.
|
|
328
|
+
twice, so it never has to defend itself. It is also **local only** — no deploy
|
|
329
|
+
applies it, so anything the app would be broken without in production is
|
|
330
|
+
configuration and belongs in a migration, not here.
|
|
329
331
|
Do this generously and do it now: an empty app demos badly and critiques
|
|
330
332
|
badly, and you cannot judge a screen's hierarchy, overflow, or truncation
|
|
331
333
|
against zero rows. Seed rows each persona sees differently — with an ownership
|
|
@@ -115,7 +115,9 @@ Then, in this order — it is the order codegen depends on:
|
|
|
115
115
|
2. **Seed** — rows in `db/sqlite-dev-seed.sql`. There is no seed command:
|
|
116
116
|
`bunx --bun pikku db reset` wipes, migrates and seeds in one go, and is the
|
|
117
117
|
only thing that applies the file. It always starts from a wiped database, so
|
|
118
|
-
the file is plain `INSERT`s — no `ON CONFLICT DO NOTHING`.
|
|
118
|
+
the file is plain `INSERT`s — no `ON CONFLICT DO NOTHING`. It is local only:
|
|
119
|
+
no deploy applies it, so anything the app cannot run without belongs in a
|
|
120
|
+
migration instead. **Be generous, and
|
|
119
121
|
seed rows for both personas.** An empty app demos badly, and you cannot see a
|
|
120
122
|
layout break against zero rows.
|
|
121
123
|
3. **Functions** — one `pikkuFunc` per `*.function.ts`, `expose: true`. Pikku
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
options, parameters, custom renderers, and nested command groups. TRIGGER when: code uses
|
|
6
6
|
wireCLI/pikkuCLICommand, user asks about CLI commands, terminal tools, command-line interface,
|
|
7
7
|
or adding subcommands. DO NOT TRIGGER when: user asks about the pikku CLI tool itself (use
|
|
8
|
-
pikku-
|
|
8
|
+
pikku-meta) or HTTP endpoints (use pikku-http).
|
|
9
9
|
installGroups: [core]
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -10,6 +10,7 @@ description: >-
|
|
|
10
10
|
email (verification, password reset, invitation, receipt), wire email sending, or translate an
|
|
11
11
|
email. DO NOT TRIGGER when: user asks about i18n for the app UI (use pikku-i18n) or auth flows
|
|
12
12
|
in general (use pikku-better-auth).
|
|
13
|
+
installGroups: [fabric]
|
|
13
14
|
---
|
|
14
15
|
|
|
15
16
|
# Pikku Emails
|
|
@@ -109,11 +109,25 @@ plain `INSERT`s** — no `INSERT OR IGNORE`, no `ON CONFLICT DO NOTHING`, no
|
|
|
109
109
|
you find yourself reaching for an idempotent form, that's a sign the data wants
|
|
110
110
|
to be a migration instead.
|
|
111
111
|
|
|
112
|
-
This is **
|
|
113
|
-
app.
|
|
114
|
-
and
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
This is **local dev data only**: enough rows that a fresh dev database isn't an
|
|
113
|
+
empty app. Nothing else ever runs it. A deployed stage applies `db/<engine>/*.sql`
|
|
114
|
+
and stops there — reset refuses `NODE_ENV=production` and refuses a database
|
|
115
|
+
outside the runtime directory, and no deploy step reaches for the seed file.
|
|
116
|
+
|
|
117
|
+
So the test is not "is this row realistic?", it is **"would the app be broken
|
|
118
|
+
without it in production?"** If yes, it is configuration and belongs in a
|
|
119
|
+
migration, however much it looks like sample data. A venue and its rooms, a
|
|
120
|
+
product catalogue, a tenant, a country list, the organization the whole
|
|
121
|
+
deployment hangs off — all configuration. Accounts and role grants are
|
|
122
|
+
provisioning: `provisionPersonas` at deployment, or a migration. What is left
|
|
123
|
+
over is the seed's job — the bookings, orders and messages a demo needs and a real
|
|
124
|
+
environment starts without.
|
|
125
|
+
|
|
126
|
+
Get this wrong and it hides: the app is perfect locally, where reset has just
|
|
127
|
+
run, and every deployed environment comes up with empty tables. The signature is
|
|
128
|
+
a stage whose pages return 200 — the shell renders fine — while its first data
|
|
129
|
+
read throws `no result` or a foreign-key violation on a row the seed was
|
|
130
|
+
silently supplying.
|
|
117
131
|
|
|
118
132
|
A Better Auth app has a second constraint: the plugins you enable (`ban()`,
|
|
119
133
|
`actor()`, …) each declare columns, and `pikku db migrate` refuses to run while
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-i18n
|
|
3
3
|
description: 'Wire i18n into a Pikku frontend with Paraglide JS (inlang). English by default, every user-facing string is a typed message function (`m.some__key()`) compiled from `messages/<locale>.json`, and additional languages are served under `/fr` `/de` URL prefixes. TRIGGER when: scaffolding or editing a frontend and writing user-facing text, adding a second language, or asked to "make this translatable / use tokens / add i18n". DO NOT TRIGGER for backend functions, error messages thrown from functions, or log output.'
|
|
4
|
-
installGroups: [client]
|
|
4
|
+
installGroups: [client, fabric]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Pikku i18n (Paraglide JS)
|
|
@@ -6,6 +6,7 @@ description: >-
|
|
|
6
6
|
code uses JoseJWTService, user asks about JWT setup, token signing, token verification, or
|
|
7
7
|
@pikku/jose. DO NOT TRIGGER when: user asks about session middleware (use pikku-security) or
|
|
8
8
|
general service setup (use pikku-services).
|
|
9
|
+
installGroups: [fabric]
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# Pikku Jose (JWT Service)
|
|
@@ -11,6 +11,7 @@ description: >-
|
|
|
11
11
|
PikkuKysely, KyselyChannelStore, KyselyWorkflowService, KyselySecretService, or the user asks
|
|
12
12
|
about SQL setup with Pikku. DO NOT TRIGGER when: user asks about MongoDB (use pikku-mongodb) or
|
|
13
13
|
Redis (use pikku-redis).
|
|
14
|
+
installGroups: [fabric]
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
# Pikku Kysely (SQL Database Services)
|
|
@@ -9,6 +9,7 @@ description: >-
|
|
|
9
9
|
credentials, sandbox/worker tokens, or resolving a better-auth session in a Pikku function. DO
|
|
10
10
|
NOT TRIGGER when: user asks about end-user HTTP session/cookie auth only (use pikku-http + the
|
|
11
11
|
app betterAuth config) or about WebSocket channel mechanics (use pikku-websocket).
|
|
12
|
+
installGroups: [fabric]
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
# Pikku Machine Auth
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-meta
|
|
3
|
+
description: >-
|
|
4
|
+
Read and change a Pikku project's declarations without grepping or hand-editing — functions
|
|
5
|
+
(with their transport, middleware and permissions), schemas, workflows, wires, tags, middleware
|
|
6
|
+
and permission definitions, plus `pikku meta apply` to set config on them. TRIGGER when: user
|
|
7
|
+
asks "what functions exist?", "show me the project structure", "list routes/middleware/
|
|
8
|
+
permissions", needs a function's input/output shape, or wants to add a permission, retag a
|
|
9
|
+
function, or change a declaration's config. DO NOT TRIGGER when: user is writing a NEW function
|
|
10
|
+
or wiring (use the specific wiring skill) or asking about Pikku concepts (use pikku-concepts).
|
|
11
|
+
installGroups: [core]
|
|
12
|
+
allowed-tools: Bash(yarn pikku meta *), Bash(yarn pikku info *)
|
|
13
|
+
argument-hint: '[context|functions|schemas|workflows|middleware|permissions|wires|apply]'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Pikku Project Metadata
|
|
17
|
+
|
|
18
|
+
`pikku meta` is the machine-readable view of the project and the write path to it.
|
|
19
|
+
`pikku info` is the same ground as human-readable tables. Prefer `meta` when you are
|
|
20
|
+
going to act on the output; prefer `info` when a person is going to read it.
|
|
21
|
+
|
|
22
|
+
## Agent Operating Procedure
|
|
23
|
+
|
|
24
|
+
Use this skill as an execution checklist, not reference material.
|
|
25
|
+
|
|
26
|
+
1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
27
|
+
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.
|
|
28
|
+
3. Change a declaration's config with `pikku meta apply`, not by hand-editing the file.
|
|
29
|
+
4. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
30
|
+
5. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
31
|
+
6. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
32
|
+
|
|
33
|
+
## Reading
|
|
34
|
+
|
|
35
|
+
| Command | What it answers |
|
|
36
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `pikku meta context` | Everything a planner needs in one call — functions, wires, middleware, permissions, workflows, capabilities, layout. Start here. |
|
|
38
|
+
| `pikku meta functions get <id>` | One function's input/output schema names, source file, tags, expose/readonly |
|
|
39
|
+
| `pikku meta schemas get <name>` | One generated JSON schema |
|
|
40
|
+
| `pikku meta workflows get <id>` | One workflow's steps |
|
|
41
|
+
| `pikku meta permissions list` | What permissions exist and where they are defined |
|
|
42
|
+
| `pikku meta middleware list` | What middleware exists |
|
|
43
|
+
| `pikku meta wires list` | Wires by transport (http, channel, scheduler, queue, trigger) |
|
|
44
|
+
| `pikku meta clients` | Exposed RPCs/workflows/channels with their type names — what a frontend can call |
|
|
45
|
+
|
|
46
|
+
`list` is the default for each group, so `pikku meta functions` and `pikku meta functions list`
|
|
47
|
+
are the same call.
|
|
48
|
+
|
|
49
|
+
A function's input/output shape comes from here. Do not infer it by reading the
|
|
50
|
+
function body, and do not cast a call site to make it compile — the schema is the type.
|
|
51
|
+
|
|
52
|
+
## Changing
|
|
53
|
+
|
|
54
|
+
`pikku meta apply` applies a batch of edits to your own source. Pass JSON as a file
|
|
55
|
+
or on stdin:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pikku meta apply ops.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"operations": [
|
|
64
|
+
{
|
|
65
|
+
"kind": "functionConfig",
|
|
66
|
+
"sourceFile": "src/functions/todos.functions.ts",
|
|
67
|
+
"exportedName": "listTodos",
|
|
68
|
+
"changes": { "title": "List Todos", "tags": ["todos", "read"] }
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
"kind": "functionConfig",
|
|
73
|
+
"sourceFile": "src/functions/todos.functions.ts",
|
|
74
|
+
"exportedName": "listTodos",
|
|
75
|
+
"changes": {
|
|
76
|
+
"permissions": {
|
|
77
|
+
"functionLevel": {
|
|
78
|
+
"name": "isTodoOwner",
|
|
79
|
+
"from": "../permissions.js"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Three kinds: `functionConfig`, `agentConfig`, `functionBody`. Every operation names
|
|
89
|
+
a `sourceFile` and the `exportedName` declared in it.
|
|
90
|
+
|
|
91
|
+
`functionConfig` changes: `title`, `description`, `summary`, `tags`, `errors`,
|
|
92
|
+
`expose`, `remote`, `mcp`, `readonly`, `approvalRequired`, `permissions`.
|
|
93
|
+
`agentConfig` changes: `name`, `description`, `instructions`, `role`, `personality`,
|
|
94
|
+
`goal`, `model`, `maxSteps`, `temperature`, `toolChoice`, `tools`, `tags`.
|
|
95
|
+
|
|
96
|
+
`null` removes a property. Edits are spliced into the original text, so formatting,
|
|
97
|
+
comments and JSDoc survive.
|
|
98
|
+
|
|
99
|
+
`permissions` and `tools` are written as identifiers rather than literals, so each
|
|
100
|
+
one carries the module it comes from (`{"name": "isTodoOwner", "from": "../permissions.js"}`)
|
|
101
|
+
and the missing import is added for you — widening an existing import from that
|
|
102
|
+
module rather than adding a second one.
|
|
103
|
+
|
|
104
|
+
### Why batch
|
|
105
|
+
|
|
106
|
+
The whole batch either lands or it does not: every operation is resolved before
|
|
107
|
+
anything is written, so a failure leaves every file untouched and names the
|
|
108
|
+
operation that caused it. Batching is also what makes one codegen pass correct —
|
|
109
|
+
**run `pikku all` once after the batch**, not once per property. The response tells
|
|
110
|
+
you whether it is needed:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"schemaVersion": "meta-apply.v1",
|
|
115
|
+
"applied": 2,
|
|
116
|
+
"files": ["src/functions/todos.functions.ts"],
|
|
117
|
+
"generatedMetaIsStale": true
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Human-readable tables (`pikku info`)
|
|
122
|
+
|
|
123
|
+
Four subcommands only — `functions`, `tags`, `middleware`, `permissions`. Routes,
|
|
124
|
+
channels, schedulers and queues are not subcommands; they are the _transport_ column
|
|
125
|
+
of `info functions --verbose`.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
yarn pikku info functions --verbose --silent
|
|
129
|
+
yarn pikku info tags --silent
|
|
130
|
+
yarn pikku info middleware --verbose --silent
|
|
131
|
+
yarn pikku info permissions --verbose --silent
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`--silent` suppresses the banner and inspector diagnostics. It works, but it is not
|
|
135
|
+
declared as an option, so every run also prints `Warning: Unknown option: --silent
|
|
136
|
+
(ignored)` — the warning is wrong. Ignore that one line.
|
|
137
|
+
|
|
138
|
+
`--limit N` caps rows (default 50); the footer says how many were withheld.
|
|
139
|
+
On `tags`, `--verbose` swaps counts for names; elsewhere it adds columns.
|
|
@@ -3,6 +3,7 @@ name: pikku-n8n-import
|
|
|
3
3
|
description: 'Use to import an n8n workflow JSON export into a runnable Pikku workflow. Triggers when the user says "import this n8n workflow", "convert this n8n export to pikku", points at an n8n `.json` export or a directory of them, or picks up after `pikku import n8n` left throwing stub functions (`STUB — generated from n8n …`, `— implement me`) or a `<workflow>.integrations.json` manifest. Owns the whole flow: run the importer, triage what it could not map, fill each stub, report any missing `@pikku/addon-*` integrations, and verify the result compiles and runs with no surviving stubs. DO NOT TRIGGER for hand-written addon wiring unrelated to an n8n import (use pikku-addon), or for authoring workflows from scratch (use pikku-workflow).'
|
|
4
4
|
metadata:
|
|
5
5
|
version: 1.0.0
|
|
6
|
+
installGroups: [fabric]
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
# n8n → Pikku Import
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-react-query
|
|
3
3
|
description: 'Use the Pikku auto-generated React Query hooks (`usePikkuQuery`, `usePikkuMutation`, `usePikkuInfiniteQuery`) to call backend RPC functions from a React frontend with full type safety. TRIGGER when: writing React components that need to call a Pikku function, fetch data, mutate data, or paginate; user mentions React Query, useQuery, useMutation, or building a frontend that talks to a Pikku backend. DO NOT TRIGGER when: working on the backend (use pikku-rpc / pikku-feature) or wiring a non-React frontend.'
|
|
4
|
-
installGroups: [client]
|
|
4
|
+
installGroups: [client, fabric]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Pikku React Query Hooks
|
|
@@ -6,6 +6,7 @@ description: >-
|
|
|
6
6
|
TRIGGER when: code uses wire.rpc or expose: true, user asks about calling one Pikku function
|
|
7
7
|
from another, function composition, or RPC endpoints. DO NOT TRIGGER when: user asks about HTTP
|
|
8
8
|
routes (use pikku-http) or addon cross-package calls (use pikku-addon).
|
|
9
|
+
installGroups: [fabric]
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# Pikku RPC Wiring
|
package/skills/pikku-ws/SKILL.md
CHANGED
|
@@ -5,6 +5,7 @@ description: >-
|
|
|
5
5
|
adapter for Pikku channels. TRIGGER when: code uses @pikku/ws, user asks about ws library
|
|
6
6
|
WebSocket server, or Node.js WebSocket runtime. DO NOT TRIGGER when: user asks about WebSocket
|
|
7
7
|
wiring/channels (use pikku-websocket) or uWebSockets (use pikku-deploy-uws).
|
|
8
|
+
installGroups: [fabric]
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
# Pikku WS (WebSocket Server Runtime)
|