@pikku/skills 0.12.37 → 0.12.38
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/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +30 -0
- package/skills/pikku-addon/references/openapi.md +69 -38
- package/skills/pikku-auth/references/better-auth.md +17 -2
- package/skills/pikku-build/SKILL.md +80 -9
- package/skills/pikku-build/references/app.md +33 -11
- package/skills/pikku-build/references/design.md +16 -5
- package/skills/pikku-build/references/openapi.md +119 -0
- package/skills/pikku-build/references/platform.md +4 -0
- package/skills/pikku-build/references/quick.md +4 -3
- package/skills/pikku-scenario/SKILL.md +9 -8
- package/skills/pikku-scenario/references/personas.md +65 -3
|
@@ -35,13 +35,13 @@ Use this skill as an execution checklist, not reference material.
|
|
|
35
35
|
This skill covers writing and running scenarios end to end. Four topics are one level down, and
|
|
36
36
|
each says when to open it:
|
|
37
37
|
|
|
38
|
-
| Read | For
|
|
39
|
-
| --------------------------- |
|
|
40
|
-
| `references/steps.md` | Authoring a `pikkuScenarioStep` — intent, witnesses, what a step is given
|
|
41
|
-
| `references/browser.md` | Browser bindings, and locating by message key in a translated app
|
|
42
|
-
| `references/personas.md` | Personas vs actors, `definePersonas`, and the human "Sign in as …" switcher |
|
|
43
|
-
| `references/coverage.md` | Live coverage, filling it, and unit tests for pure logic
|
|
44
|
-
| `references/persona-run.md` | Running a persona as a model-driven virtual user against a stage
|
|
38
|
+
| Read | For |
|
|
39
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
40
|
+
| `references/steps.md` | Authoring a `pikkuScenarioStep` — intent, witnesses, what a step is given |
|
|
41
|
+
| `references/browser.md` | Browser bindings, and locating by message key in a translated app |
|
|
42
|
+
| `references/personas.md` | Personas vs actors, `definePersonas`, upstream credentials for actors, and the human "Sign in as …" switcher |
|
|
43
|
+
| `references/coverage.md` | Live coverage, filling it, and unit tests for pure logic |
|
|
44
|
+
| `references/persona-run.md` | Running a persona as a model-driven virtual user against a stage |
|
|
45
45
|
|
|
46
46
|
## What a scenario is
|
|
47
47
|
|
|
@@ -245,7 +245,7 @@ suite survives its first redesign:
|
|
|
245
245
|
through the actor.
|
|
246
246
|
- **Steps describe intent, not actions** — `buys the £5 strawberry milkshake`, never
|
|
247
247
|
`clicks [data-testid=add]`. The clicking lives in plain utilities the step composes.
|
|
248
|
-
- **`then` bindings are witnesses, not alternatives** — a `then` runs
|
|
248
|
+
- **`then` bindings are witnesses, not alternatives** — a `then` runs _every_ binding it declares
|
|
249
249
|
and fails when they disagree, because "database right, user still watching a spinner" is the bug
|
|
250
250
|
nobody catches.
|
|
251
251
|
- **What language the prose is in** — identifiers are English in every project; `description`,
|
|
@@ -265,6 +265,7 @@ whole project — codegen builds the `PersonaId` union from it, materialises one
|
|
|
265
265
|
scenario actor per person, and seeds a user row each:
|
|
266
266
|
|
|
267
267
|
```ts snippet:definePersonas
|
|
268
|
+
|
|
268
269
|
```
|
|
269
270
|
|
|
270
271
|
`pikku.config.json` carries the settings around them — nothing about a person:
|
|
@@ -5,7 +5,7 @@ A **persona** is a person your product is for; an **actor** is one body that sig
|
|
|
5
5
|
- Two people of the same kind are two entries, not one persona with two logins — "you see yours, not theirs" is only testable with two customers.
|
|
6
6
|
- Never write an email address: each is derived from the persona id and `scenarios.emailDomain`, and a hand-written one signs in as somebody who was never created.
|
|
7
7
|
- `roles` is typechecked against `defineSystemRole`; an undeclared role is a build error.
|
|
8
|
-
- A person who is only ever acted
|
|
8
|
+
- 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.
|
|
9
9
|
- A persona 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.
|
|
10
10
|
|
|
11
11
|
## Declaring personas in TypeScript
|
|
@@ -50,6 +50,32 @@ A project that never declares a persona keeps working: a scenario that names no
|
|
|
50
50
|
- `environments.<name>.apiUrl` is required. `signInPath` defaults to `/auth/sign-in/actor`, `rpcPath` to `/rpc`.
|
|
51
51
|
- **`SCENARIO_ACTOR_SECRET` is an environment variable and never goes in `pikku.config.json`.** It signs actors in. `pikku scenario run` throws without it; a server auto-building actors warns and runs without them.
|
|
52
52
|
|
|
53
|
+
## Actors that call a third-party API
|
|
54
|
+
|
|
55
|
+
An addon generated from an upstream API (Dolibarr, a CRM, a calendar) calls it
|
|
56
|
+
with the signed-in user's own credential. An actor has none until one is stored
|
|
57
|
+
for it, so every step that reaches the addon fails with `No <X> session`. Give
|
|
58
|
+
the `actor` plugin a `credentials` option, and each actor carries its upstream
|
|
59
|
+
credential into every session:
|
|
60
|
+
|
|
61
|
+
- `names: ['dolibarr']` — the credentials to carry
|
|
62
|
+
- `store: (name, value, userId) => credentialService.set(name, value, userId)`
|
|
63
|
+
- `remove: (name, userId) => credentialService.delete(name, userId)`
|
|
64
|
+
|
|
65
|
+
At each actor sign-in the plugin reads `ACTOR_CREDENTIAL_<PERSONA>_<NAME>` —
|
|
66
|
+
`dan` + `dolibarr` is `ACTOR_CREDENTIAL_DAN_DOLIBARR` — and stores it with
|
|
67
|
+
`credentialService.set` for that actor. A bare value is stored as `{ token }`
|
|
68
|
+
(what a delegated or bearer credential holds); a JSON object is stored as-is,
|
|
69
|
+
e.g. `{"apiKey":"…"}` for an API-key credential. Unset means the actor has no
|
|
70
|
+
upstream credential: `remove` drops one stored at an earlier sign-in.
|
|
71
|
+
|
|
72
|
+
- **Values live in `.env` (or CI secrets), never in `personas.ts` or code.**
|
|
73
|
+
Use a dedicated upstream test account per persona, not a real person's.
|
|
74
|
+
- `read` defaults to `process.env`; a Worker passes `(key) => variables.get(key)`.
|
|
75
|
+
- A malformed JSON value refuses the sign-in with a 500 that names the variable.
|
|
76
|
+
- A delegated token expires upstream like any other; re-signing the actor
|
|
77
|
+
in re-stores whatever the variable holds now.
|
|
78
|
+
|
|
53
79
|
## The same actors sign a human in
|
|
54
80
|
|
|
55
81
|
Declared actors are not only for automated runs. `signInPath` is Better Auth's
|
|
@@ -57,8 +83,8 @@ Declared actors are not only for automated runs. `signInPath` is Better Auth's
|
|
|
57
83
|
frontend gets a one-click "Sign in as …" switcher over the **same** list, and an
|
|
58
84
|
app can be reviewed as each kind of user without anyone knowing a seed password.
|
|
59
85
|
|
|
60
|
-
The
|
|
61
|
-
personas
|
|
86
|
+
The dev server bakes both halves into the frontend from the declared
|
|
87
|
+
personas — the sandbox's, or the template's `bun run dev`, never `pikku dev`: `VITE_DEV_ACTORS` (the JSON actor list) and `VITE_DEV_ACTOR_SECRETS`
|
|
62
88
|
(`{ email: credential }`, one per persona — `SCENARIO_ACTOR_SECRET` itself never
|
|
63
89
|
goes in a bundle; see **pikku-auth**). Neither var is set in a production
|
|
64
90
|
build, so the control renders nothing there — but gate the reads on your
|
|
@@ -85,3 +111,39 @@ When the switcher is there but signing in fails with `401 Invalid actor
|
|
|
85
111
|
secret`, check which server answered before checking the secret: a frontend
|
|
86
112
|
whose dev proxy (`VITE_API_PROXY`, default `http://localhost:3000`) points at
|
|
87
113
|
another project's API sends the sign-in there.
|
|
114
|
+
|
|
115
|
+
**A runner of your own that starts vite has to bake them itself**, from the
|
|
116
|
+
generated persona meta (`<outDir>/workflow/personas.gen.json`, which already
|
|
117
|
+
carries the derived `email`):
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const personas = Object.values(JSON.parse(readFileSync(personasPath, 'utf8')))
|
|
121
|
+
|
|
122
|
+
env.VITE_DEV_ACTORS = JSON.stringify(
|
|
123
|
+
personas.map(({ id, email, name, jobTitle }) => ({
|
|
124
|
+
key: id,
|
|
125
|
+
email,
|
|
126
|
+
name,
|
|
127
|
+
jobTitle: jobTitle ?? '',
|
|
128
|
+
}))
|
|
129
|
+
)
|
|
130
|
+
env.VITE_DEV_ACTOR_SECRETS = JSON.stringify(
|
|
131
|
+
Object.fromEntries(
|
|
132
|
+
await Promise.all(
|
|
133
|
+
personas.map(async ({ email }) => [
|
|
134
|
+
email,
|
|
135
|
+
await deriveActorSecret(env.SCENARIO_ACTOR_SECRET, email),
|
|
136
|
+
])
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Set `SCENARIO_ACTOR_SECRET` yourself**, at least 32 characters, in the
|
|
143
|
+
environment both processes read. Left unset, `pikku dev` mints an ephemeral root
|
|
144
|
+
for its own run that a separately spawned vite cannot see, so the two derive
|
|
145
|
+
from different roots: the switcher renders every persona and each click is
|
|
146
|
+
refused, which reads as a broken login rather than missing configuration. On a
|
|
147
|
+
brand-new project the persona file does not exist until the first `pikku dev`
|
|
148
|
+
codegen, after vite has baked an empty list — watch it and restart the frontend
|
|
149
|
+
when it changes.
|