@pikku/skills 0.12.52 → 0.12.55

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 (112) hide show
  1. package/README.md +1 -1
  2. package/dist/skills.gen.js +2 -2
  3. package/dist/snippets.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/pikku-a11y/SKILL.md +22 -15
  6. package/skills/pikku-addon/SKILL.md +5 -5
  7. package/skills/pikku-addon/references/addon-package-manifest.md +7 -0
  8. package/skills/pikku-addon/references/openapi.md +11 -0
  9. package/skills/pikku-agent/SKILL.md +6 -6
  10. package/skills/pikku-agent/references/agents.md +8 -1
  11. package/skills/pikku-agent/references/runner-vercel.md +2 -2
  12. package/skills/pikku-agent/references/voice.md +7 -1
  13. package/skills/pikku-architect/SKILL.md +2 -2
  14. package/skills/pikku-architect/references/plan-defects.md +9 -7
  15. package/skills/pikku-auth/SKILL.md +15 -36
  16. package/skills/pikku-auth/references/better-auth.md +106 -133
  17. package/skills/pikku-auth/references/jose.md +6 -0
  18. package/skills/pikku-auth/references/machine-auth.md +6 -0
  19. package/skills/pikku-auth/references/sessions.md +6 -0
  20. package/skills/pikku-build/SKILL.md +37 -34
  21. package/skills/pikku-build/references/app.md +113 -99
  22. package/skills/pikku-build/references/design.md +43 -31
  23. package/skills/pikku-build/references/feature.md +17 -3
  24. package/skills/pikku-build/references/mocks.md +29 -0
  25. package/skills/pikku-build/references/multi-app.md +25 -18
  26. package/skills/pikku-build/references/native-app.md +9 -0
  27. package/skills/pikku-build/references/openapi.md +41 -34
  28. package/skills/pikku-build/references/platform.md +23 -14
  29. package/skills/pikku-build/references/post-clone.md +1 -1
  30. package/skills/pikku-build/references/quick.md +76 -39
  31. package/skills/pikku-build/references/scenarios.md +28 -16
  32. package/skills/pikku-build/references/ship.md +19 -16
  33. package/skills/pikku-build/references/theming.md +76 -127
  34. package/skills/pikku-changes/SKILL.md +7 -10
  35. package/skills/pikku-concepts/SKILL.md +34 -44
  36. package/skills/pikku-concepts/references/concept-mapping.md +18 -0
  37. package/skills/pikku-concepts/references/language.md +6 -6
  38. package/skills/pikku-convert-screen/SKILL.md +45 -0
  39. package/skills/pikku-deploy/references/cloudflare.md +8 -0
  40. package/skills/pikku-deploy/references/lambda.md +8 -0
  41. package/skills/pikku-deploy/references/nextjs.md +1 -1
  42. package/skills/pikku-deploy/references/uws.md +1 -1
  43. package/skills/pikku-deploy/references/ws.md +3 -3
  44. package/skills/pikku-emails/SKILL.md +3 -9
  45. package/skills/pikku-guide/SKILL.md +28 -27
  46. package/skills/pikku-i18n/SKILL.md +7 -7
  47. package/skills/pikku-i18n/references/enum-labels.md +9 -0
  48. package/skills/pikku-i18n/references/messages.md +21 -9
  49. package/skills/pikku-i18n/references/rtl.md +18 -38
  50. package/skills/pikku-knowledge/SKILL.md +12 -13
  51. package/skills/pikku-kysely/SKILL.md +3 -21
  52. package/skills/pikku-list-query/SKILL.md +2 -2
  53. package/skills/pikku-meta/references/audit.md +10 -1
  54. package/skills/pikku-meta/references/meta.md +6 -0
  55. package/skills/pikku-meta/references/versioning.md +10 -0
  56. package/skills/pikku-middleware/SKILL.md +7 -16
  57. package/skills/pikku-n8n-import/SKILL.md +9 -1
  58. package/skills/pikku-n8n-import/references/addon-mapping.md +6 -0
  59. package/skills/pikku-n8n-import/references/code-translation.md +7 -0
  60. package/skills/pikku-permissions/SKILL.md +41 -10
  61. package/skills/pikku-permissions/references/scopes.md +59 -0
  62. package/skills/pikku-react/SKILL.md +20 -22
  63. package/skills/pikku-react/references/client.md +2 -70
  64. package/skills/pikku-react/references/react-query.md +11 -0
  65. package/skills/pikku-react/references/workflows.md +11 -0
  66. package/skills/pikku-realtime/SKILL.md +9 -3
  67. package/skills/pikku-report/SKILL.md +17 -16
  68. package/skills/pikku-scenario/SKILL.md +72 -81
  69. package/skills/pikku-scenario/references/browser.md +5 -5
  70. package/skills/pikku-scenario/references/coverage.md +4 -4
  71. package/skills/pikku-scenario/references/steps.md +31 -30
  72. package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +47 -42
  73. package/skills/pikku-scenario/references/virtual-users.md +114 -0
  74. package/skills/pikku-seo/SKILL.md +12 -16
  75. package/skills/pikku-service-backends/SKILL.md +0 -10
  76. package/skills/pikku-service-backends/references/aws.md +6 -0
  77. package/skills/pikku-service-backends/references/mongodb.md +7 -0
  78. package/skills/pikku-service-backends/references/redis.md +6 -0
  79. package/skills/pikku-services/references/audit-wire-service.md +2 -2
  80. package/skills/pikku-services/references/audit.md +10 -1
  81. package/skills/pikku-services/references/config.md +6 -0
  82. package/skills/pikku-services/references/services.md +9 -2
  83. package/skills/pikku-software-archaeology/SKILL.md +21 -27
  84. package/skills/pikku-software-archaeology/references/blueprint.schema.json +16 -16
  85. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  86. package/skills/pikku-software-archaeology/references/second-opinion.md +13 -1
  87. package/skills/pikku-tailwind/SKILL.md +111 -0
  88. package/skills/pikku-theme/SKILL.md +187 -0
  89. package/skills/pikku-theme/references/from-a-reference.md +44 -0
  90. package/skills/pikku-theme/references/presets.md +40 -0
  91. package/skills/pikku-ui/SKILL.md +41 -0
  92. package/skills/pikku-wiring/SKILL.md +13 -12
  93. package/skills/pikku-wiring/references/channel.md +7 -1
  94. package/skills/pikku-wiring/references/cli.md +7 -1
  95. package/skills/pikku-wiring/references/gateway-slack.md +6 -0
  96. package/skills/pikku-wiring/references/http.md +10 -3
  97. package/skills/pikku-wiring/references/mcp.md +8 -0
  98. package/skills/pikku-wiring/references/queue.md +6 -0
  99. package/skills/pikku-wiring/references/realtime.md +34 -4
  100. package/skills/pikku-wiring/references/rpc.md +6 -0
  101. package/skills/pikku-wiring/references/scheduler.md +6 -0
  102. package/skills/pikku-wiring/references/trigger.md +8 -0
  103. package/skills/pikku-workflow/SKILL.md +9 -7
  104. package/skills/pikku-admin-to-fabric/SKILL.md +0 -212
  105. package/skills/pikku-auth/references/permissions.md +0 -261
  106. package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -377
  107. package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
  108. package/skills/pikku-fabric/SKILL.md +0 -619
  109. package/skills/pikku-fabric/references/debugging.md +0 -110
  110. package/skills/pikku-mantine/SKILL.md +0 -80
  111. package/skills/pikku-scenario/references/personas.md +0 -117
  112. package/skills/pikku-wiring/references/realtime-other-routes.md +0 -23
@@ -1,7 +1,17 @@
1
- # Running a persona as a virtual user
1
+ # Running a virtual user against a stage
2
2
 
3
- `pikku persona run <environment> <persona>` signs a declared persona in over the
4
- app's real auth and works the API in character, driven by a model. A persona
3
+ ## Contents
4
+
5
+ - [The shape of a run](#the-shape-of-a-run)
6
+ - [The dispositions](#the-dispositions)
7
+ - [The key](#the-key)
8
+ - [Production is opt-in, twice](#production-is-opt-in-twice)
9
+ - [The role check happens before the first step](#the-role-check-happens-before-the-first-step)
10
+ - [The other subcommands](#the-other-subcommands)
11
+ - [What NOT to do](#what-not-to-do)
12
+
13
+ `pikku virtual-user run <environment> <virtualUserId>` signs a declared virtual user in over the
14
+ app's real auth and works the API in character, driven by a model. A virtual user
5
15
  while running **is** the virtual user — there is no second declaration for it.
6
16
 
7
17
  **It is not a test runner.** It asserts nothing, and a green run proves nothing:
@@ -10,33 +20,33 @@ not with this seed". Findings set exit code 1, so a run can gate a pipeline;
10
20
  giving up on a goal does not, because that is a user being a user.
11
21
 
12
22
  Everything it needs is already in the project — the catalogue is the function
13
- meta, the intents are the scenarios' own prose, the identity is the persona
23
+ meta, the intents are the scenarios' own prose, the identity is the virtual user
14
24
  signing in, the scopes come from their declared roles. The only new input is
15
25
  which person to be.
16
26
 
17
- Declaring personas — persona versus actor, `definePersonas`, materialised
18
- actors — is in the skill itself, under **Personas and actors**. This is the
27
+ Declaring virtual users — virtual user versus virtual user, `defineVirtualUsers`, materialised
28
+ virtual users — is in the skill itself, under **Virtual users and virtual users**. This is the
19
29
  running half.
20
30
 
21
31
  ## The shape of a run
22
32
 
23
33
  ```bash
24
- SCENARIO_ACTOR_SECRET=… pikku persona run local shopper
25
- SCENARIO_ACTOR_SECRET=… pikku persona run local shopper -d careless --seed 42
26
- SCENARIO_ACTOR_SECRET=… pikku persona run staging auditor \
34
+ pikku virtual-user run local shopper
35
+ pikku virtual-user run local shopper -d careless --seed 42
36
+ pikku virtual-user run staging auditor \
27
37
  --goals "reconcile the order totals" --steps 80 --out runs/auditor.json
28
38
  ```
29
39
 
30
40
  Both arguments are required positionals: the environment key from
31
- `environments`, then the persona id. A run needs a model — `--model`, or
41
+ `environments`, then the virtual user id. A run needs a model — `--model`, or
32
42
  `scenarios.model` in `pikku.config.json` — and an AI provider in the
33
43
  environment (`OPENAI_BASE_URL` + `OPENAI_API_KEY`, or `LITELLM_PROXY_URL` +
34
44
  `LITELLM_API_KEY`).
35
45
 
36
46
  | Flag | Effect |
37
47
  | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
38
- | `--disposition` / `-d` | How they behave. Overrides the persona's own |
39
- | `--goals` | Comma-separated, in your words — run _alongside_ the persona's own and the ones derived from scenarios |
48
+ | `--disposition` / `-d` | How they behave. Overrides the virtual user's own |
49
+ | `--goals` | Comma-separated, in your words — run _alongside_ the virtual user's own and the ones derived from scenarios |
40
50
  | `--steps` | Model turns before it stops (default 40) |
41
51
  | `--mutations` | Non-read calls before it stops |
42
52
  | `--duration` | Wall clock before it stops, e.g. `30m` |
@@ -50,9 +60,9 @@ environment (`OPENAI_BASE_URL` + `OPENAI_API_KEY`, or `LITELLM_PROXY_URL` +
50
60
  ## The dispositions
51
61
 
52
62
  A disposition is a bundle of instructions and mechanical dials (move weights,
53
- temperature, repeat and re-read rates). `tuning` on the persona adjusts those
63
+ temperature, repeat and re-read rates). `tuning` on the virtual user adjusts those
54
64
  dials without replacing the character — a tuned `careless` user is still
55
- careless. Passing `--disposition` drops the persona's `tuning`, because you
65
+ careless. Passing `--disposition` drops the virtual user's `tuning`, because you
56
66
  asked to run them differently rather than to bend their dials into another
57
67
  shape.
58
68
 
@@ -66,33 +76,29 @@ shape.
66
76
  | `adversarial` | Probing whether the boundaries are enforced. Inverted oracle — a 2xx from something it should not reach is the finding |
67
77
  | `accountable` | Doing the job for real. The **only** disposition production accepts |
68
78
 
69
- ## Credentials, and which one wins
79
+ ## The key
80
+
81
+ A run signs one short-lived statement per virtual user and presents it to the
82
+ app. The key comes from, in this order:
70
83
 
71
- Three variables, checked in this order. None of them belongs in
72
- `pikku.config.json`.
84
+ 1. **`VIRTUAL_USER_SIGNING_KEY`** in the environment — a deployed stage's
85
+ private key, held by whoever deploys it.
86
+ 2. **The file `pikku dev` wrote**, `.pikku-runtime/virtual-user/signing.key` —
87
+ so a local run needs nothing set.
73
88
 
74
- 1. **`FABRIC_OPERATOR_TOKEN`** — what a deployed stage accepts. Asymmetric, and
75
- it needs no account the target would not otherwise have, so it wins over the
76
- other two when both are present.
77
- 2. **`PIKKU_PERSONA_SECRETS`** — `id=secret,id=secret`, already-derived
78
- per-persona credentials. Hand a run only the personas it should be able to
79
- be; asking for one outside the list is refused by name rather than falling
80
- through to the root. Mint them with `pikku persona secret [personas...]` —
81
- naming none mints all.
82
- 3. **`SCENARIO_ACTOR_SECRET`** — the root secret, which derives every persona's
83
- credential and is therefore entitled to all of them. Only `pikku dev` serves
84
- the endpoint it opens.
89
+ Neither belongs in `pikku.config.json`. The app only ever holds the public half,
90
+ `VIRTUAL_USER_VERIFY_KEY`.
85
91
 
86
92
  ## Production is opt-in, twice
87
93
 
88
- A persona's `environments` omitted means every configured environment **except**
94
+ A virtual user's `environments` omitted means every configured environment **except**
89
95
  those flagged `production: true` — nothing reaches production by being
90
96
  forgotten. Naming one requires `disposition: 'accountable'`.
91
97
 
92
98
  That rule is checked twice on purpose: the inspector checks the declaration at
93
- build time, and sign-in re-checks the **effective** disposition — the persona's
99
+ build time, and sign-in re-checks the **effective** disposition — the virtual user's
94
100
  own, or whatever `--disposition` replaced it with — before the run starts. So
95
- `--disposition adversarial` cannot point an accountable persona at production.
101
+ `--disposition adversarial` cannot point an accountable virtual user at production.
96
102
  The build check trusts the file; the run check does not trust which artifact got
97
103
  deployed.
98
104
 
@@ -100,7 +106,7 @@ deployed.
100
106
  true` is a label a person wrote in `pikku.config.json`; nothing can tell from a
101
107
  url whether real customers are behind it. `--api-url` replaces the url and keeps
102
108
  the classification, so a non-production environment repointed at a production
103
- host is still treated as non-production, and an adversarial persona will happily
109
+ host is still treated as non-production, and an adversarial virtual user will happily
104
110
  run against it. The flag is for a target that only exists at run time — a
105
111
  freshly provisioned sandbox. Point it anywhere else and the guard above is not
106
112
  protecting you.
@@ -108,8 +114,8 @@ protecting you.
108
114
  ## The role check happens before the first step
109
115
 
110
116
  A run reads its own roles back from the stage and compares them to what the
111
- persona declared. It refuses on a mismatch, before anything runs — findings
112
- from a persona whose roles drifted are about the seed, and reading them as
117
+ virtual user declared. It refuses on a mismatch, before anything runs — findings
118
+ from a virtual user whose roles drifted are about the seed, and reading them as
113
119
  product bugs is how a whole run gets thrown away. A stage that reports no roles
114
120
  warns and runs unverified. `--skip-role-check` is for a target whose auth
115
121
  reports roles somewhere pikku cannot read; findings from such a run may be seed
@@ -117,21 +123,20 @@ drift.
117
123
 
118
124
  ## The other subcommands
119
125
 
120
- | Command | What it answers |
121
- | ------------------------------------ | ------------------------------------------------------------------------------------------ |
122
- | `pikku persona list` | Who is declared — who each one is, what they may do, what they want |
123
- | `pikku persona sync <environment>` | Which personas that environment will provision, with which roles, and why any were skipped |
124
- | `pikku persona secret [personas...]` | Mint per-persona credentials from the root secret |
126
+ | Command | What it answers |
127
+ | --------------------------------------- | ----------------------------------------------------------------------------------------------- |
128
+ | `pikku virtual-user list` | Who is declared — who each one is, what they may do, what they want |
129
+ | `pikku virtual-user sync <environment>` | Which virtual users that environment will provision, with which roles, and why any were skipped |
125
130
 
126
131
  `sync` **reports**; it does not provision. The CLI has no connection to a
127
132
  deployed environment's database, so the provisioning happens in the deployment —
128
- pass the generated personas to `pikkuFabric` from `@pikku/better-auth`.
133
+ pass the generated virtual users to `provisionVirtualUsers` from `@pikku/better-auth`.
129
134
 
130
135
  ## What NOT to do
131
136
 
132
137
  - **Do not treat a clean run as a pass.** Nothing was asserted. Use scenarios
133
138
  for the things that must hold.
134
- - **Do not run a persona declared `runnable: false`**, or one whose `account`
139
+ - **Do not run a virtual user declared `runnable: false`**, or one whose `account`
135
140
  names a provider. The first is someone who exists to be acted upon — running
136
141
  her races the scenario that bans her — and the second needs a human at a
137
142
  consent screen. Both are refused before sign-in rather than partway through.
@@ -144,5 +149,5 @@ pass the generated personas to `pikkuFabric` from `@pikku/better-auth`.
144
149
  ones the app marked as needing a human because they spend money.
145
150
  - **Do not read a finding from a run started with `--skip-role-check` as a
146
151
  product bug** until the roles are confirmed some other way.
147
- - **Do not expect `--goals` to replace the persona's goals.** They are appended;
152
+ - **Do not expect `--goals` to replace the virtual user's goals.** They are appended;
148
153
  a run that replaces Susan's goals is not Susan.
@@ -0,0 +1,114 @@
1
+ # Virtual users and virtual users
2
+
3
+ ## Contents
4
+
5
+ - [Declaring virtual users in TypeScript](#declaring-virtual-users-in-typescript)
6
+ - [Virtual users that call a third-party API](#virtual-users-that-call-a-third-party-api)
7
+ - [The same virtual users sign a human in](#the-same-virtual-users-sign-a-human-in)
8
+
9
+ A **virtual user** is a person your product is for; an **virtual user** is one body that signs in as them. Every entry materialises exactly one virtual user, so `virtualUsers.<id>` exists for each declared virtual user and there is no second way to declare a login.
10
+
11
+ - Two people of the same kind are two entries, not one virtual user with two logins — "you see yours, not theirs" is only testable with two customers.
12
+ - Never write an email address: each is derived from the virtual user id and `scenarios.emailDomain`, and a hand-written one signs in as somebody who was never created.
13
+ - `roles` is typechecked against `defineSystemRole`; an undeclared role is a build error.
14
+ - A person who is only ever acted _upon_ — the account an admin bans — sets `runnable: false`: declared and seeded, never signed in, because a run as them would race the scenario that acts on them.
15
+ - A virtual user holds only what is true of that kind of person for the app's whole lifetime (`name`, `jobTitle`, `description`, `personality`, `roles`, `goals`, `disposition`). What someone is trying to get done, and the circumstances they are doing it in, belong to the **scenario**, not to them.
16
+
17
+ ## Declaring virtual users in TypeScript
18
+
19
+ There may be **one `defineVirtualUsers` call in the whole codebase** — one place to
20
+ read the set from, one place to add to it. A second anywhere, including in the
21
+ same file, is a critical. Generated files are exempt and never claim the slot.
22
+
23
+ > [!WARNING]
24
+ > The declaration is **read from source, never evaluated** — the CLI writes it
25
+ > to JSON that a deployed stage carries without the app. So every value has to
26
+ > be statically knowable, and a value that is not comes out as `undefined`
27
+ > rather than as an error. Only `name` is checked, so a computed `personality`,
28
+ > `jobTitle` or `description` is dropped in silence and the virtual user runs with a
29
+ > blank temperament.
30
+
31
+ What that admits and what it does not:
32
+
33
+ ```typescript
34
+ personality: 'Wound up and short with it.' // read
35
+ personality: `Wound up and short with it.
36
+ Says what she wants in a few blunt words.` // read — no ${} in it
37
+ personality: 'Wound up. ' + 'Short with it.' // dropped, silently
38
+ personality: TEMPERAMENTS.impatient // dropped, silently
39
+ ```
40
+
41
+ A no-substitution template literal is a string literal as far as the reader is
42
+ concerned, so it is the way to write a long personality across several lines —
43
+ not a concatenation, and not a `prettier-ignore`d single line. Its newlines and
44
+ leading indentation are kept verbatim and reach the model that way, which is
45
+ harmless but worth knowing before you align it to the surrounding code.
46
+
47
+ One more thing worth knowing before writing a rich virtual user: **`virtualUser.converse`
48
+ builds its prompt from `name`, `jobTitle`, `personality` and the scenario's
49
+ `task` only.** Fields like `disposition`, `goals` and `roles` are read and
50
+ stored, and the console shows them, but they do not reach the conversing
51
+ virtual user's instructions. Anything that must shape how someone talks belongs in
52
+ `personality` or in the task.
53
+
54
+ A project that never declares a virtual user keeps working: a scenario that names no virtual user needs none.
55
+
56
+ - `environments.<name>.apiUrl` is required. `signInPath` defaults to `/auth/sign-in/virtual-user`, `rpcPath` to `/rpc`.
57
+ - **The signing key never goes in `pikku.config.json`.** `pikku dev` creates one under `.pikku-runtime/virtual-user/`; a deployed stage's deployer holds `VIRTUAL_USER_SIGNING_KEY`. `pikku scenario run` throws without a key.
58
+
59
+ ## Virtual users that call a third-party API
60
+
61
+ An addon generated from an upstream API (Dolibarr, a CRM, a calendar) calls it
62
+ with the signed-in user's own credential. A virtual user has none until one is stored
63
+ for it, so every step that reaches the addon fails with `No <X> session`. Give
64
+ the `virtualUser` plugin a `credentials` option, and each virtual user carries its upstream
65
+ credential into every session:
66
+
67
+ - `names: ['dolibarr']` — the credentials to carry
68
+ - `store: (name, value, userId) => credentialService.set(name, value, userId)`
69
+ - `remove: (name, userId) => credentialService.delete(name, userId)`
70
+
71
+ At each virtual user sign-in the plugin reads `VIRTUAL_USER_CREDENTIAL_<VIRTUAL_USER>_<NAME>` —
72
+ `dan` + `dolibarr` is `VIRTUAL_USER_CREDENTIAL_DAN_DOLIBARR` — and stores it with
73
+ `credentialService.set` for that virtual user. A bare value is stored as `{ token }`
74
+ (what a delegated or bearer credential holds); a JSON object is stored as-is,
75
+ e.g. `{"apiKey":"…"}` for an API-key credential. Unset means the virtual user has no
76
+ upstream credential: `remove` drops one stored at an earlier sign-in.
77
+
78
+ - **Values live in `.env` (or CI secrets), never in `virtual-users.ts` or code.**
79
+ Use a dedicated upstream test account per virtual user, not a real person's.
80
+ - `read` defaults to `process.env`; a Worker passes `(key) => variables.get(key)`.
81
+ - A malformed JSON value refuses the sign-in with a 500 that names the variable.
82
+ - A delegated token expires upstream like any other; re-signing the virtual user
83
+ in re-stores whatever the variable holds now.
84
+
85
+ ## The same virtual users sign a human in
86
+
87
+ Declared virtual users are not only for automated runs. `signInPath` is Better Auth's
88
+ `virtualUser` plugin (see `pikku-auth`, a separate install), which any caller can post to — so the
89
+ frontend can sign in as the **same** list, and an
90
+ app can be reviewed as each kind of user without anyone knowing a seed password.
91
+
92
+ It holds no credential. A client app that wants one-click sign-in posts only a
93
+ virtual user id to `/auth/sign-in/switch-virtual-user` itself; the server
94
+ resolves the address. One server piece serves both, in the auth config:
95
+
96
+ ```ts snippet:virtualUserSignIn
97
+
98
+ ```
99
+
100
+ It is always open under `pikku dev`. A deployed stage needs virtual user
101
+ sign-in opted in and an explicit `virtualUserSignIn.allowed`; production never
102
+ has the opt-in, so it lists nobody and refuses every virtual user sign-in.
103
+
104
+ If the list is empty, it is one of two things, and neither errors:
105
+
106
+ - **The gate is closed.** On a deployed stage that is the gate doing its job —
107
+ check the opt-in. Locally, check the virtual users declare an `email` (via
108
+ `scenarios.emailDomain`) and are not `runnable: false`.
109
+ - **`virtualUserSignIn` is missing, or the frontend calls another API.** A dev
110
+ proxy (`VITE_API_PROXY`, default `http://localhost:3000`) that points at
111
+ another project's API lists that project's virtual users, or none.
112
+
113
+ When the list has virtual users but a sign-in 404s, that virtual user has no `email`
114
+ or is `runnable: false`.
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: pikku-seo
3
3
  description: >-
4
- On-page SEO rules for the app's PUBLIC pages: per-route head() titles and meta descriptions, Open Graph tags, one-h1 heading hierarchy, semantic/crawlable markup, JSON-LD on the landing page, and noindex for the logged-in area.
4
+ On-page SEO rules for the app's PUBLIC pages: per-route head() titles and meta descriptions, Open Graph tags, crawlable links and URLs, JSON-LD on the landing page, and noindex for the logged-in area.
5
5
  TRIGGER when: building or reworking any public page (landing, pricing, about, blog/content pages), writing page titles or meta tags, or the user asks about SEO / Google / discoverability / social sharing previews.
6
- DO NOT TRIGGER when: working on logged-in /app screens (they are noindexed — only the one robots rule below applies), backend functions, database, or deployment.
6
+ DO NOT TRIGGER when: the task is accessibility, including heading order, landmarks or alt text (use pikku-a11y), or working on logged-in /app screens (they are noindexed — only the one robots rule below applies), backend functions, database, or deployment.
7
7
  installGroups: [client]
8
8
  ---
9
9
 
@@ -38,19 +38,20 @@ export const Route = createFileRoute('/pricing')({
38
38
  })
39
39
  ```
40
40
 
41
- `head()` strings are plain strings (they do not go through the Mantine i18n
41
+ `head()` strings are plain strings (they do not go through the component i18n
42
42
  gate) — write real copy for THIS app, in the app's voice.
43
43
 
44
44
  - **Title**: unique per page, 50–60 characters, the page's primary topic first,
45
- brand at the end (`Topic — AppName`). The template's `__APP_TITLE__` default
46
- must never survive the rebrand, on any page.
45
+ brand at the end (`Topic — AppName`). The template's `__APP_TITLE__` placeholder
46
+ (in `src/app-meta.ts` and `messages/en.json`) is filled with the app title at scaffold time
47
+ by `cloud-new-app`; it must never survive in any page's title.
47
48
  - **Description**: unique per page, 150–160 characters, states the concrete
48
49
  value of the page in plain language — a reason to click, not a keyword list.
49
50
  - **Dynamic public pages** (e.g. a public detail page) build both from loader
50
51
  data: `head: ({ loaderData }) => ({ meta: [{ title: `${loaderData.name} — AppName` }, ...] })`.
51
52
  - **Never invent URLs**: the deployed domain is unknown at build time, so do
52
53
  NOT emit `canonical`, `og:url`, or `og:image` pointing at a made-up domain —
53
- omit them (same principle as the `/api` serverUrl rule). `og:image` only if a
54
+ omit them. `og:image` only if a
54
55
  real asset exists in the app.
55
56
 
56
57
  ## Logged-in area = noindex
@@ -64,24 +65,19 @@ head: () => ({ meta: [{ name: 'robots', content: 'noindex' }] })
64
65
  Never noindex a public page, and never put per-page SEO effort into `/app`
65
66
  screens — they are invisible to crawlers by design.
66
67
 
67
- ## Headings — exactly one h1 per page
68
+ ## Headings, landmarks, alt text
68
69
 
69
- - Every page has EXACTLY ONE h1 (`<Title order={1}>` in Mantine, `<h1>` in
70
- Tailwind) and it names the page's primary topic — aligned with the title tag,
71
- not identical boilerplate.
72
- - Logical hierarchy below it: h1 → h2 → h3, no skipped levels, headings
73
- describe the content under them. Never pick a heading level for its font
74
- size — set the size on the correct level (`<Title order={2} fz="xs">`).
70
+ Owned by `pikku-a11y` and apply app-wide; follow it. SEO addition only: the
71
+ public page's h1 names its primary topic, aligned with the title tag (not
72
+ identical boilerplate).
75
73
 
76
74
  ## Crawlable, semantic markup
77
75
 
78
- - Landmarks on public pages: `<nav>`, `<main>`, `<footer>` (Mantine: `component="nav"` etc.).
79
76
  - Navigation between public pages uses real links (`<Link>`/`<a href>`) with
80
77
  descriptive anchor text — crawlers follow hrefs; a `div onClick` navigation
81
78
  is invisible to them. No public page may be orphaned: every public page is
82
79
  reachable by link from the landing page (directly or via nav/footer).
83
- - Every meaningful `<img>` has alt text describing the image; decorative images
84
- get `alt=""`. Prefer descriptive file names for real assets.
80
+ - Prefer descriptive file names for real assets (alt text: see `pikku-a11y`).
85
81
  - Readable URLs: public routes are lowercase, hyphen-separated, and named for
86
82
  their content (`/pricing`, `/how-it-works`) — never `/page2` or query-param
87
83
  navigation.
@@ -16,16 +16,6 @@ installGroups: [core]
16
16
 
17
17
  # Pikku Service Backends
18
18
 
19
- ## Agent Operating Procedure
20
-
21
- Use this skill as an execution checklist, not reference material.
22
-
23
- 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
24
- 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.
25
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
26
- 4. Validate with the narrowest relevant command first, then run `pikku all` when functions, wirings, schemas, or generated clients may have changed.
27
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
28
-
29
19
  Constructor shapes and method signatures come from `pikku doc` — run
30
20
  `pikku doc --ai` for the installed surface. This skill is the part the compiler
31
21
  cannot tell you: which backend implements which interface, and what changes when
@@ -1,5 +1,11 @@
1
1
  # AWS (`@pikku/aws-services`)
2
2
 
3
+ ## Contents
4
+
5
+ - [`S3Content` — ContentService](#s3content-contentservice)
6
+ - [`SQSQueueService` — QueueService](#sqsqueueservice-queueservice)
7
+ - [`AWSSecrets` — SecretService](#awssecrets-secretservice)
8
+
3
9
  ```bash
4
10
  yarn add @pikku/aws-services
5
11
  ```
@@ -1,5 +1,12 @@
1
1
  # MongoDB (`@pikku/mongodb`)
2
2
 
3
+ ## Contents
4
+
5
+ - [`PikkuMongoDB` — connection wrapper](#pikkumongodb-connection-wrapper)
6
+ - [Available services](#available-services)
7
+ - [`MongoDBSecretService`](#mongodbsecretservice)
8
+ - [Full setup](#full-setup)
9
+
3
10
  ```bash
4
11
  yarn add @pikku/mongodb
5
12
  ```
@@ -1,5 +1,11 @@
1
1
  # Redis (`@pikku/redis`)
2
2
 
3
+ ## Contents
4
+
5
+ - [`RedisSecretService`](#redissecretservice)
6
+ - [`RedisLeaseService`](#redisleaseservice)
7
+ - [Full setup](#full-setup)
8
+
3
9
  ```bash
4
10
  yarn add @pikku/redis
5
11
  ```
@@ -1,6 +1,6 @@
1
1
  # Audit Wire Service
2
2
 
3
- `createInvocationAudit` creates a per-request `InvocationAuditLog` that buffers audit events in memory and flushes them as a batch when the function-runner calls `closeWireServices` at the end of the request. If `singletonServices.audit` is not configured (local dev without Fabric), it returns a no-op `DisabledInvocationAudit` — no crash, events are silently dropped.
3
+ `createInvocationAudit` creates a per-request `InvocationAuditLog` that buffers audit events in memory and flushes them as a batch when the function-runner calls `closeWireServices` at the end of the request. If `singletonServices.audit` is not configured (local dev without a hosting platform), it returns a no-op `DisabledInvocationAudit` — no crash, events are silently dropped.
4
4
 
5
5
  Pair with `createAuditedKysely` to auto-capture every Kysely query as an audit event.
6
6
 
@@ -38,4 +38,4 @@ const deleteUser = pikkuFunc({
38
38
 
39
39
  `closeWireServices` (called automatically by the function-runner) invokes `audit.close()` → `singletonServices.audit.write(batch)` → platform-specific flush (e.g. CF Queue, libsql INSERT). No manual flushing needed.
40
40
 
41
- > **Fabric note:** Fabric provisions the audit queue and consumer worker automatically. The audit table schema is in `db/sqlite/0003-audit.sql` (starter-template). Run `pikku fabric validate` to confirm the migration is in place.
41
+ > **Note:** a hosting platform may provision the audit queue and consumer worker for you. The audit table schema is in `db/sqlite/0003-audit.sql` (starter-template). Run `pikku validate` to confirm the migration is in place.
@@ -1,5 +1,14 @@
1
1
  # Pikku Audit
2
2
 
3
+ ## Contents
4
+
5
+ - [Mental model — two layers](#mental-model-two-layers)
6
+ - [Wiring (services.ts)](#wiring-servicests)
7
+ - [Recording events — explicit domain events (default)](#recording-events-explicit-domain-events-default)
8
+ - [Recording events — automatic query capture (optional)](#recording-events-automatic-query-capture-optional)
9
+ - [Sinks](#sinks)
10
+ - [AuditEvent shape](#auditevent-shape)
11
+ - [Do / Don't](#do-dont)
3
12
 
4
13
  ## Mental model — two layers
5
14
 
@@ -178,7 +187,7 @@ type AuditEvent = {
178
187
 
179
188
  `auditLog.write()` takes `Omit<AuditEvent, 'occurredAt'>` — you only supply `type`, `source`, and `metadata` (and `userIdentity` if overriding the session default).
180
189
 
181
- `userIdentity` is filled from the wire's session plus its `pikkuUserId`, and is left off entirely when all three are absent — which is what makes a cron or system invocation land with a null actor rather than an empty object.
190
+ `userIdentity` is filled from the wire's session plus its `pikkuUserId`, and is left off entirely when all three are absent — which is what makes a cron or system invocation land with a null virtual user rather than an empty object.
182
191
 
183
192
  ## Do / Don't
184
193
 
@@ -1,5 +1,11 @@
1
1
  # Pikku Config, Secrets & OAuth2
2
2
 
3
+ ## Contents
4
+
5
+ - [Before You Start](#before-you-start)
6
+ - [Secrets & Variables](#secrets-variables)
7
+ - [Credentials](#credentials)
8
+
3
9
 
4
10
  ## Before You Start
5
11
 
@@ -1,5 +1,12 @@
1
1
  # Pikku Services (Dependency Injection)
2
2
 
3
+ ## Contents
4
+
5
+ - [Before You Start](#before-you-start)
6
+ - [API Reference](#api-reference)
7
+ - [Usage Patterns](#usage-patterns)
8
+ - [Complete Example](#complete-example)
9
+
3
10
  ## Before You Start
4
11
 
5
12
  ```bash
@@ -75,7 +82,7 @@ export const lifecycle = pikkuServerLifecycle<SingletonServices>({
75
82
  })
76
83
  ```
77
84
 
78
- Every hook is optional. Order is `beforeStart` → server starts → `afterStart`, then on SIGINT `beforeStop` → services stopped → server stopped → `afterStop`.
85
+ Every hook is optional. Order is `beforeStart` → server starts → `afterStart`, then on SIGINT or SIGTERM `beforeStop` → services stopped → server stopped → `afterStop`. A repeat signal while this runs is ignored, so the sequence always finishes.
79
86
 
80
87
  **`afterStop` runs after the singleton services have been stopped.** It still receives the services object, but the services inside it are shut down — using one there is a use-after-close bug. Anything that needs a live service goes in `beforeStop`.
81
88
 
@@ -203,7 +210,7 @@ what makes the split real.
203
210
 
204
211
  ### Audit Wire Service
205
212
 
206
- `createInvocationAudit` + `createAuditedKysely` add per-request audit buffering that flushes on request close (no-op if `audit` is unconfigured). For the full pattern, no-op behavior, custom-event usage, and Fabric notes, read `references/audit-wire-service.md`.
213
+ `createInvocationAudit` + `createAuditedKysely` add per-request audit buffering that flushes on request close (no-op if `audit` is unconfigured). For the full pattern, no-op behavior, custom-event usage, and hosting notes, read `references/audit-wire-service.md`.
207
214
 
208
215
  ### Built-in Services
209
216