@pikku/skills 0.12.21 → 0.12.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.21",
3
+ "version": "0.12.22",
4
4
  "description": "The Pikku agent skills — the instruction set coding agents read to build, wire and deploy Pikku projects",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -146,7 +146,7 @@ second argument is always present — an addon never falls back to its own logge
146
146
  variables or secrets; the consuming app supplies them:
147
147
 
148
148
  ```typescript
149
- import { pikkuAddonServices } from '#pikku/addon/setup'
149
+ import { pikkuAddonServices } from '#pikku/setup'
150
150
 
151
151
  export const createSingletonServices = pikkuAddonServices(
152
152
  async (config, { secrets, logger }) => {
@@ -167,7 +167,7 @@ config object.
167
167
  Define per-request services for an addon package (created fresh per HTTP request, queue job, etc.):
168
168
 
169
169
  ```typescript
170
- import { pikkuAddonWireServices } from '#pikku/addon/setup'
170
+ import { pikkuAddonWireServices } from '#pikku/setup'
171
171
 
172
172
  export const createWireServices = pikkuAddonWireServices(
173
173
  async (singletonServices, wire) => {
@@ -195,7 +195,7 @@ This generates `package.json` (exports `.pikku/*` + `dist/`), `pikku.config.json
195
195
 
196
196
  ```typescript
197
197
  // src/services.ts
198
- import { pikkuAddonServices, pikkuAddonWireServices } from '#pikku/addon/setup'
198
+ import { pikkuAddonServices, pikkuAddonWireServices } from '#pikku/setup'
199
199
  import { TodoStore } from './todo-store.service.js'
200
200
 
201
201
  export const createSingletonServices = pikkuAddonServices(async () => {
@@ -213,14 +213,15 @@ export const createWireServices = pikkuAddonWireServices(
213
213
 
214
214
  ### Functions
215
215
 
216
- An addon generates its whole tree under `#pikku/addon/*`, so it authors against
217
- `#pikku/addon/function`, `#pikku/addon/http` and so on. An application's leaves
218
- stay flat, which is what stops a linked addon resolving against its host.
216
+ An addon's generated tree roots at `.pikku/addon/`, but its `imports` map points
217
+ `#pikku/*` there, so it authors against the same subpaths an application does —
218
+ `#pikku/function`, `#pikku/http`. The `addon` segment is the package's own
219
+ business, never part of a specifier.
219
220
 
220
221
  ```typescript
221
222
  // src/functions/addTodo.function.ts
222
223
  import { z } from 'zod'
223
- import { pikkuSessionlessFunc } from '#pikku/addon/function'
224
+ import { pikkuSessionlessFunc } from '#pikku/function'
224
225
 
225
226
  const AddTodoInput = z.object({ title: z.string() })
226
227
  const AddTodoOutput = z.object({ id: z.string(), title: z.string() })
@@ -516,9 +516,9 @@ an `actor: true` row only under `pikku dev`. With the opt-in set, a stage signs
516
516
  in as the personas the deployment provisioned when it started and refuses
517
517
  everything else (`No actor account exists for that address`), so holding the
518
518
  secret on such a stage does not let anyone invent identities. Those rows are
519
- written by `provisionPersonas` from `@pikku/better-auth`, called in the server's
520
- own lifecycle, so provisioning needs no actor secret and works on a stage whose
521
- endpoint is shut.
519
+ written by the fabric plugin when an operator asks to act as an address the
520
+ stage has no account for, so provisioning needs no actor secret and works on a
521
+ stage whose endpoint is shut.
522
522
 
523
523
  **`SCENARIO_ACTOR_SECRET` is a credential as powerful as the most privileged
524
524
  persona.** Provisioning grants declared roles to actor accounts, so an
@@ -544,30 +544,46 @@ This is the endpoint `pikku scenario` signs its actors in through, and the one
544
544
  the frontend dev switcher posts to — see `pikku-scenario` for declaring the
545
545
  actors and `pikku-react` for `useDevActors()`.
546
546
 
547
- ### Provisioning personas (`provisionPersonas`)
547
+ ### Provisioning personas
548
548
 
549
- Anywhere but `pikku dev`, the accounts have to exist before anyone signs in.
550
- The deployment creates them itself, from its own lifecycle:
549
+ Anywhere but `pikku dev`, the accounts have to exist before anyone signs in. The
550
+ stage creates them itself, from the personas you hand `pikkuFabric`:
551
551
 
552
552
  ```ts
553
- import { provisionPersonas } from '@pikku/better-auth'
553
+ import { pikkuFabric } from '@pikku/better-auth'
554
554
  import {
555
555
  personaConfigs,
556
556
  personaEnvironments,
557
557
  } from '#pikku/pikku-personas.gen.js'
558
558
 
559
- await provisionPersonas(singletonServices, {
560
- personas: personaConfigs,
561
- environments: personaEnvironments,
559
+ pikkuFabric({
560
+ publicKey,
561
+ audience,
562
+ scopeService,
563
+ personas: {
564
+ personas: personaConfigs,
565
+ environments: personaEnvironments,
566
+ },
562
567
  })
563
568
  ```
564
569
 
565
- It runs where the database already is, which is the point: the CLI has no
566
- connection to a deployed environment's database — it resolves one from the local
567
- project config — so a `pikku persona sync staging` that wrote rows would write
568
- them to whatever database the checkout happened to point at. `pikku persona sync
569
- <environment>` still exists, and reports who that environment will provision and
570
- why anyone was skipped, which is what you run _before_ the deploy.
570
+ There is nothing else to call and nothing to schedule. The plugin's operator
571
+ endpoint resolves the address the caller wants to act as; a miss provisions the
572
+ declaration and looks again. On a stage that already holds the persona that is
573
+ one query, and the pass only runs when there is genuinely something absent to
574
+ create.
575
+
576
+ **Do not reach for `pikkuServerLifecycle`'s `afterStart` for this.** That hook is
577
+ invoked by `pikku serve` and `pikku dev` and by nothing else — no deploy runtime
578
+ calls it — so a stage on Workers or a serverless target that provisioned from
579
+ `afterStart` provisioned nothing, and every persona signed in holding no roles.
580
+
581
+ Provisioning runs where the database already is, which is the point: the CLI has
582
+ no connection to a deployed environment's database — it resolves one from the
583
+ local project config — so a `pikku persona sync staging` that wrote rows would
584
+ write them to whatever database the checkout happened to point at. `pikku persona
585
+ sync <environment>` still exists, and reports who that environment will provision
586
+ and why anyone was skipped, which is what you run _before_ the deploy.
571
587
 
572
588
  It creates missing accounts as `actor: true`, applies the roles each persona
573
589
  declares, and is additive — it never revokes. `PIKKU_ENV` (or an explicit
@@ -583,10 +599,15 @@ open. By default provisioning warns about those accounts and changes nothing.
583
599
  `orphans: 'ban'` shuts them:
584
600
 
585
601
  ```ts
586
- await provisionPersonas(singletonServices, {
587
- personas: personaConfigs,
588
- environments: personaEnvironments,
589
- orphans: 'ban',
602
+ pikkuFabric({
603
+ publicKey,
604
+ audience,
605
+ scopeService,
606
+ personas: {
607
+ personas: personaConfigs,
608
+ environments: personaEnvironments,
609
+ orphans: 'ban',
610
+ },
590
611
  })
591
612
  ```
592
613
 
@@ -210,7 +210,7 @@ people the user named, and the roles they imply.
210
210
 
211
211
  ```typescript
212
212
  import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
213
- import { defineSystemRole } from '#pikku'
213
+ import { defineSystemRole } from '#pikku/scopes'
214
214
 
215
215
  defineSystemRole({
216
216
  owner: {
@@ -484,7 +484,7 @@ experiences it. Three ship in `packages/functions/test/scenarios/` — keep them
484
484
  green — and every milestone's gherkin block from §5 becomes one more.
485
485
 
486
486
  ```typescript
487
- import { pikkuScenario } from '#pikku/workflow/pikku-workflow-types.gen.js'
487
+ import { pikkuScenario } from '#pikku/scenarios'
488
488
 
489
489
  export const tenantReportsAFaultScenario = pikkuScenario<void, { id: string }>({
490
490
  title: 'A tenant reports a fault and the owner sees it',
@@ -85,7 +85,7 @@ named:
85
85
 
86
86
  ```typescript
87
87
  import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
88
- import { defineSystemRole } from '#pikku'
88
+ import { defineSystemRole } from '#pikku/scopes'
89
89
 
90
90
  defineSystemRole({
91
91
  owner: { displayName: 'Owner', description: 'Sees only their own rows', scopes: [] },
@@ -199,7 +199,7 @@ Not the full ladder — one journey, end to end, as a real persona, so the app h
199
199
  at least one thing that stays true.
200
200
 
201
201
  ```typescript
202
- import { pikkuScenario } from '#pikku/workflow/pikku-workflow-types.gen.js'
202
+ import { pikkuScenario } from '#pikku/scenarios'
203
203
 
204
204
  export const ownerCreatesAndSeesItScenario = pikkuScenario<void, { id: string }>({
205
205
  title: 'An owner creates a thing and sees it',
@@ -247,6 +247,8 @@ export const lifecycle = pikkuServerLifecycle<SingletonServices>({
247
247
 
248
248
  Export exactly one `pikkuServerLifecycle` from anywhere in `srcDirectories` — the inspector finds it by the wrapper call. Every hook is optional and receives the already-created singleton services. See pikku-services for the ordering and the `afterStop` caveat.
249
249
 
250
+ **Only `pikku dev` and `pikku serve` invoke these hooks.** No deploy runtime does, so anything a Workers or serverless stage needs done cannot live here — put it on the request path that needs it, guarded by a cheap check.
251
+
250
252
  **2. Bootstrap it yourself (required for a specific runtime)**
251
253
 
252
254
  Express, Fastify, uWS, Lambda, Cloudflare and Next.js need their own entrypoint, because Pikku is embedded in a server you own:
@@ -333,7 +335,7 @@ bottom.
333
335
  | Axis | What it covers | What decides it |
334
336
  | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
335
337
  | **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Branch names and commit messages. | Nothing. **Always English.** There is no setting. |
336
- | **Meta** | The prose authored _inside_ the code: `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role/persona descriptions. | `metaLocale` in `pikku.config.json`. Defaults to `en`. |
338
+ | **Meta** | The prose authored _inside_ the code: `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role/persona descriptions. | `metaLocale` in `pikku.config.json`. Defaults to `en`. |
337
339
  | **Product UI** | Every string the app shows a user. | `messages/<locale>.json`, with `active.json`'s `defaultLocale` choosing what a first-time visitor opens in. |
338
340
 
339
341
  ### Identifiers are English, and nothing changes that
@@ -119,7 +119,7 @@ without it in production?"** If yes, it is configuration and belongs in a
119
119
  migration, however much it looks like sample data. A venue and its rooms, a
120
120
  product catalogue, a tenant, a country list, the organization the whole
121
121
  deployment hangs off — all configuration. Accounts and role grants are
122
- provisioning: `provisionPersonas` at deployment, or a migration. What is left
122
+ provisioning: the fabric plugin's `personas`, or a migration. What is left
123
123
  over is the seed's job — the bookings, orders and messages a demo needs and a real
124
124
  environment starts without.
125
125
 
@@ -306,13 +306,13 @@ tells you nothing about whether it worked. `--sync` waits for a terminal state
306
306
  and exits non-zero unless the deployment went live, which is the only form worth
307
307
  running in CI:
308
308
 
309
- | exit | meaning |
310
- | ---- | ------- |
311
- | 0 | live (or queued, without `--sync`) |
312
- | 1 | the command could not run — not logged in, unsafe git state, bad flags |
313
- | 2 | the deployment failed, errored, timed out server-side, or was cancelled |
314
- | 3 | the deployment is blocked and nothing the CLI can do will unblock it |
315
- | 4 | the wait hit `--timeout` with the deployment still in flight |
309
+ | exit | meaning |
310
+ | ---- | ----------------------------------------------------------------------- |
311
+ | 0 | live (or queued, without `--sync`) |
312
+ | 1 | the command could not run — not logged in, unsafe git state, bad flags |
313
+ | 2 | the deployment failed, errored, timed out server-side, or was cancelled |
314
+ | 3 | the deployment is blocked and nothing the CLI can do will unblock it |
315
+ | 4 | the wait hit `--timeout` with the deployment still in flight |
316
316
 
317
317
  Fabric parks every deploy at a gate after the plan phase (`status: suspended`).
318
318
  Why it parked is the whole story, and it is `statusReason`, not `status`:
@@ -226,7 +226,7 @@ export const getBook = pikkuFunc({
226
226
  })
227
227
 
228
228
  // wirings/books.http.ts — same defineHTTPRoutes/wireHTTPRoutes shape as the Route Groups example above
229
- import { addHTTPMiddleware } from '#pikku/http'
229
+ import { addHTTPMiddleware } from '#pikku/middleware'
230
230
  import { cors, authBearer } from '@pikku/core/middleware'
231
231
 
232
232
  addHTTPMiddleware('*', [cors(), authBearer()])
@@ -23,7 +23,7 @@ installGroups: [core]
23
23
  ## The `pikkuMiddleware` Factory
24
24
 
25
25
  ```typescript
26
- import { pikkuMiddleware } from '#pikku/function'
26
+ import { pikkuMiddleware } from '#pikku/middleware'
27
27
 
28
28
  // Simple: just a function
29
29
  const myMiddleware = pikkuMiddleware(async (services, wire, next) => {
@@ -139,7 +139,7 @@ Tags from the function definition and the wire object are merged — middleware
139
139
  ### Registering Tag Middleware
140
140
 
141
141
  ```typescript
142
- import { addTagMiddleware } from '#pikku/function'
142
+ import { addTagMiddleware } from '#pikku/middleware'
143
143
 
144
144
  addTagMiddleware('machine-agent', [machineAgentBearerAuth])
145
145
  ```
@@ -277,7 +277,7 @@ export const getToken = () => _token
277
277
  ```typescript
278
278
  // wirings/http.wiring.ts
279
279
  import { timingSafeEqual } from 'node:crypto'
280
- import { addTagMiddleware, pikkuMiddleware } from '#pikku/function'
280
+ import { addTagMiddleware, pikkuMiddleware } from '#pikku/middleware'
281
281
  import { UnauthorizedError } from '#pikku/error'
282
282
  import { getToken } from '../lib/host-token.js'
283
283
 
@@ -58,7 +58,7 @@ Use for checks that read the session but need no request data — and that asser
58
58
  something **beyond** merely having a session (a flag, a tier, a claim).
59
59
 
60
60
  ```typescript
61
- import { pikkuAuth } from '#pikku/function'
61
+ import { pikkuAuth } from '#pikku/auth'
62
62
 
63
63
  // Good: a real gate on the session's contents, not just its existence.
64
64
  export const isVerified = pikkuAuth(
@@ -89,7 +89,7 @@ A permission answers "_may this user do this?_" (role, ownership, tier) — neve
89
89
  Use when authorization depends on the actual request data (e.g., resource ownership).
90
90
 
91
91
  ```typescript
92
- import { pikkuPermission } from '#pikku/function'
92
+ import { pikkuPermission } from '#pikku/auth'
93
93
 
94
94
  export const isBookOwner = pikkuPermission(
95
95
  async ({ db }, { bookId }, { session }) => {
@@ -139,7 +139,7 @@ export const deleteBook = pikkuFunc({
139
139
  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.
140
140
 
141
141
  ```typescript
142
- import { addGlobalPermission } from '#pikku/function'
142
+ import { addGlobalPermission } from '#pikku/auth'
143
143
 
144
144
  addGlobalPermission([isEmployee]) // every function now also requires an employee session
145
145
  ```
@@ -248,7 +248,7 @@ declared, inspectable, and reusable.
248
248
 
249
249
  ```typescript
250
250
  // src/permissions.ts
251
- import { pikkuAuth, pikkuPermission } from '#pikku/function'
251
+ import { pikkuAuth, pikkuPermission } from '#pikku/auth'
252
252
 
253
253
  export const isVerified = pikkuAuth(
254
254
  async (_services, session) => !!session?.emailVerified
@@ -46,7 +46,7 @@ Scenarios live in `srcDirectories` like any other function — by convention `*.
46
46
  `pikkuScenario` comes from the **generated** workflow types, not `@pikku/core`:
47
47
 
48
48
  ```typescript
49
- import { pikkuScenario } from '#pikku/scenario'
49
+ import { pikkuScenario } from '#pikku/scenarios'
50
50
 
51
51
  export const orderSupportScenario = pikkuScenario<
52
52
  { value?: number },
@@ -175,7 +175,7 @@ Hooks are scenario-only. A `before`/`after` on a `pikkuWorkflowFunc` never runs
175
175
  `pikkuFeature` groups scenarios the way gherkin's `Feature:` groups `Scenario:`. Scenarios are referenced by **imported identifier**, so a renamed or deleted scenario is a compile error rather than a silent skip:
176
176
 
177
177
  ```typescript
178
- import { pikkuFeature } from '#pikku/scenario'
178
+ import { pikkuFeature } from '#pikku/scenarios'
179
179
  import {
180
180
  credentialLazyLoadScenario,
181
181
  credentialRoundTripScenario,
@@ -240,7 +240,7 @@ Utilities are **not steps**. They are plain exported functions, they take the br
240
240
 
241
241
  ```typescript
242
242
  // shop.browser.ts — shared actions. Not steps: nothing here is an intent.
243
- import type { PikkuBrowserWire } from '#pikku/scenario'
243
+ import type { PikkuBrowserWire } from '#pikku/scenarios'
244
244
  import type {} from '@pikku/playwright'
245
245
 
246
246
  /** Arrive on the shop, from wherever the browser happens to be. */
@@ -462,7 +462,7 @@ Assertions with no possible browser witness are a different thing and should not
462
462
  `scenario.do` can only name an RPC. A **step** is a named, typed unit of scenario behaviour whose body is an ordinary pikku function — so it can call several RPCs as its actor, assert, or drive a browser.
463
463
 
464
464
  ```typescript
465
- import { pikkuScenarioStep } from '#pikku/scenario'
465
+ import { pikkuScenarioStep } from '#pikku/scenarios'
466
466
 
467
467
  export const buysAnApple = pikkuScenarioStep<
468
468
  { qty: number },
@@ -62,7 +62,7 @@ Apply these via `addHTTPMiddleware` in a wirings file:
62
62
 
63
63
  ```typescript
64
64
  import { authBearer, authCookie, authAPIKey } from '#pikku/middleware'
65
- import { addHTTPMiddleware } from '#pikku/http'
65
+ import { addHTTPMiddleware } from '#pikku/middleware'
66
66
 
67
67
  // JWT bearer token — reads Authorization header
68
68
  addHTTPMiddleware('*', [authBearer()])
@@ -118,7 +118,7 @@ response.
118
118
 
119
119
  ```typescript
120
120
  // permissions.ts
121
- import { pikkuAuth, pikkuPermission } from '#pikku/function'
121
+ import { pikkuAuth, pikkuPermission } from '#pikku/auth'
122
122
 
123
123
  export const isAuthenticated = pikkuAuth(
124
124
  async (_services, session) => !!session
@@ -129,7 +129,7 @@ export const isVerified = pikkuAuth(
129
129
 
130
130
  // wirings/auth.wiring.ts
131
131
  import { authCookie } from '#pikku/middleware'
132
- import { addHTTPMiddleware } from '#pikku/http'
132
+ import { addHTTPMiddleware } from '#pikku/middleware'
133
133
 
134
134
  addHTTPMiddleware('*', [
135
135
  authCookie({
@@ -38,7 +38,7 @@ pikku info tags --verbose # Understand project organization
38
38
  ### `pikkuServices(factory)` — singleton services (created once at startup)
39
39
 
40
40
  ```typescript
41
- import { pikkuServices } from '#pikku/function'
41
+ import { pikkuServices } from '#pikku/setup'
42
42
  import { ConsoleLogger } from '@pikku/core/services'
43
43
  import { JoseJWTService } from '@pikku/jose'
44
44
 
@@ -61,7 +61,7 @@ export const createSingletonServices = pikkuServices(
61
61
  ### `pikkuWireServices(factory)` — per-request services (fresh per HTTP request, queue job, CLI command, etc.)
62
62
 
63
63
  ```typescript
64
- import { pikkuWireServices } from '#pikku/function'
64
+ import { pikkuWireServices } from '#pikku/setup'
65
65
 
66
66
  export const createWireServices = pikkuWireServices(
67
67
  async (singletonServices, wire) => {
@@ -240,7 +240,7 @@ const createSingletonServices = pikkuServices(async (config) => {
240
240
 
241
241
  ```typescript
242
242
  // services.ts
243
- import { pikkuServices, pikkuWireServices } from '#pikku/function'
243
+ import { pikkuServices, pikkuWireServices } from '#pikku/setup'
244
244
  import { ConsoleLogger } from '@pikku/core/services'
245
245
  import { JoseJWTService } from '@pikku/jose'
246
246
 
@@ -65,7 +65,7 @@ import {
65
65
  } from '#pikku/workflow/pikku-workflow-types.gen.js'
66
66
 
67
67
  // WRONG — the function leaf does not re-export them (TS2305)
68
- import { pikkuWorkflowFunc } from '#pikku/function'
68
+ import { pikkuWorkflowFunc } from '#pikku/workflow'
69
69
  ```
70
70
 
71
71
  ## Defining a workflow