@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.
- package/README.md +1 -1
- package/dist/skills.gen.js +2 -2
- package/dist/snippets.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-a11y/SKILL.md +22 -15
- package/skills/pikku-addon/SKILL.md +5 -5
- package/skills/pikku-addon/references/addon-package-manifest.md +7 -0
- package/skills/pikku-addon/references/openapi.md +11 -0
- package/skills/pikku-agent/SKILL.md +6 -6
- package/skills/pikku-agent/references/agents.md +8 -1
- package/skills/pikku-agent/references/runner-vercel.md +2 -2
- package/skills/pikku-agent/references/voice.md +7 -1
- package/skills/pikku-architect/SKILL.md +2 -2
- package/skills/pikku-architect/references/plan-defects.md +9 -7
- package/skills/pikku-auth/SKILL.md +15 -36
- package/skills/pikku-auth/references/better-auth.md +106 -133
- package/skills/pikku-auth/references/jose.md +6 -0
- package/skills/pikku-auth/references/machine-auth.md +6 -0
- package/skills/pikku-auth/references/sessions.md +6 -0
- package/skills/pikku-build/SKILL.md +37 -34
- package/skills/pikku-build/references/app.md +113 -99
- package/skills/pikku-build/references/design.md +43 -31
- package/skills/pikku-build/references/feature.md +17 -3
- package/skills/pikku-build/references/mocks.md +29 -0
- package/skills/pikku-build/references/multi-app.md +25 -18
- package/skills/pikku-build/references/native-app.md +9 -0
- package/skills/pikku-build/references/openapi.md +41 -34
- package/skills/pikku-build/references/platform.md +23 -14
- package/skills/pikku-build/references/post-clone.md +1 -1
- package/skills/pikku-build/references/quick.md +76 -39
- package/skills/pikku-build/references/scenarios.md +28 -16
- package/skills/pikku-build/references/ship.md +19 -16
- package/skills/pikku-build/references/theming.md +76 -127
- package/skills/pikku-changes/SKILL.md +7 -10
- package/skills/pikku-concepts/SKILL.md +34 -44
- package/skills/pikku-concepts/references/concept-mapping.md +18 -0
- package/skills/pikku-concepts/references/language.md +6 -6
- package/skills/pikku-convert-screen/SKILL.md +45 -0
- package/skills/pikku-deploy/references/cloudflare.md +8 -0
- package/skills/pikku-deploy/references/lambda.md +8 -0
- package/skills/pikku-deploy/references/nextjs.md +1 -1
- package/skills/pikku-deploy/references/uws.md +1 -1
- package/skills/pikku-deploy/references/ws.md +3 -3
- package/skills/pikku-emails/SKILL.md +3 -9
- package/skills/pikku-guide/SKILL.md +28 -27
- package/skills/pikku-i18n/SKILL.md +7 -7
- package/skills/pikku-i18n/references/enum-labels.md +9 -0
- package/skills/pikku-i18n/references/messages.md +21 -9
- package/skills/pikku-i18n/references/rtl.md +18 -38
- package/skills/pikku-knowledge/SKILL.md +12 -13
- package/skills/pikku-kysely/SKILL.md +3 -21
- package/skills/pikku-list-query/SKILL.md +2 -2
- package/skills/pikku-meta/references/audit.md +10 -1
- package/skills/pikku-meta/references/meta.md +6 -0
- package/skills/pikku-meta/references/versioning.md +10 -0
- package/skills/pikku-middleware/SKILL.md +7 -16
- package/skills/pikku-n8n-import/SKILL.md +9 -1
- package/skills/pikku-n8n-import/references/addon-mapping.md +6 -0
- package/skills/pikku-n8n-import/references/code-translation.md +7 -0
- package/skills/pikku-permissions/SKILL.md +41 -10
- package/skills/pikku-permissions/references/scopes.md +59 -0
- package/skills/pikku-react/SKILL.md +20 -22
- package/skills/pikku-react/references/client.md +2 -70
- package/skills/pikku-react/references/react-query.md +11 -0
- package/skills/pikku-react/references/workflows.md +11 -0
- package/skills/pikku-realtime/SKILL.md +9 -3
- package/skills/pikku-report/SKILL.md +17 -16
- package/skills/pikku-scenario/SKILL.md +72 -81
- package/skills/pikku-scenario/references/browser.md +5 -5
- package/skills/pikku-scenario/references/coverage.md +4 -4
- package/skills/pikku-scenario/references/steps.md +31 -30
- package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +47 -42
- package/skills/pikku-scenario/references/virtual-users.md +114 -0
- package/skills/pikku-seo/SKILL.md +12 -16
- package/skills/pikku-service-backends/SKILL.md +0 -10
- package/skills/pikku-service-backends/references/aws.md +6 -0
- package/skills/pikku-service-backends/references/mongodb.md +7 -0
- package/skills/pikku-service-backends/references/redis.md +6 -0
- package/skills/pikku-services/references/audit-wire-service.md +2 -2
- package/skills/pikku-services/references/audit.md +10 -1
- package/skills/pikku-services/references/config.md +6 -0
- package/skills/pikku-services/references/services.md +9 -2
- package/skills/pikku-software-archaeology/SKILL.md +21 -27
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +16 -16
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/pikku-software-archaeology/references/second-opinion.md +13 -1
- package/skills/pikku-tailwind/SKILL.md +111 -0
- package/skills/pikku-theme/SKILL.md +187 -0
- package/skills/pikku-theme/references/from-a-reference.md +44 -0
- package/skills/pikku-theme/references/presets.md +40 -0
- package/skills/pikku-ui/SKILL.md +41 -0
- package/skills/pikku-wiring/SKILL.md +13 -12
- package/skills/pikku-wiring/references/channel.md +7 -1
- package/skills/pikku-wiring/references/cli.md +7 -1
- package/skills/pikku-wiring/references/gateway-slack.md +6 -0
- package/skills/pikku-wiring/references/http.md +10 -3
- package/skills/pikku-wiring/references/mcp.md +8 -0
- package/skills/pikku-wiring/references/queue.md +6 -0
- package/skills/pikku-wiring/references/realtime.md +34 -4
- package/skills/pikku-wiring/references/rpc.md +6 -0
- package/skills/pikku-wiring/references/scheduler.md +6 -0
- package/skills/pikku-wiring/references/trigger.md +8 -0
- package/skills/pikku-workflow/SKILL.md +9 -7
- package/skills/pikku-admin-to-fabric/SKILL.md +0 -212
- package/skills/pikku-auth/references/permissions.md +0 -261
- package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -377
- package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
- package/skills/pikku-fabric/SKILL.md +0 -619
- package/skills/pikku-fabric/references/debugging.md +0 -110
- package/skills/pikku-mantine/SKILL.md +0 -80
- package/skills/pikku-scenario/references/personas.md +0 -117
- package/skills/pikku-wiring/references/realtime-other-routes.md +0 -23
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
# Running a
|
|
1
|
+
# Running a virtual user against a stage
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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
|
|
18
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
39
|
-
| `--goals` | Comma-separated, in your words — run _alongside_ the
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
112
|
-
from a
|
|
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
|
|
121
|
-
|
|
|
122
|
-
| `pikku
|
|
123
|
-
| `pikku
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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__`
|
|
46
|
-
|
|
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
|
|
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
|
|
68
|
+
## Headings, landmarks, alt text
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
-
|
|
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,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
|
|
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
|
-
> **
|
|
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
|
|
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,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
|
|
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
|
|