@pikku/skills 0.12.57 → 0.12.59

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 (72) hide show
  1. package/dist/skills.gen.js +2 -2
  2. package/package.json +10 -7
  3. package/skills/pikku-a11y/SKILL.md +5 -3
  4. package/skills/pikku-addon/SKILL.md +8 -9
  5. package/skills/pikku-agent/SKILL.md +16 -13
  6. package/skills/pikku-agent/references/runner-vercel.md +1 -1
  7. package/skills/pikku-architect/SKILL.md +7 -10
  8. package/skills/pikku-auth/SKILL.md +16 -20
  9. package/skills/pikku-auth/references/better-auth.md +74 -37
  10. package/skills/pikku-auth/references/sessions.md +8 -0
  11. package/skills/pikku-build/SKILL.md +36 -68
  12. package/skills/pikku-build/references/app.md +27 -828
  13. package/skills/pikku-build/references/feature.md +3 -5
  14. package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
  15. package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
  16. package/skills/pikku-build/references/phase-3-plan.md +62 -0
  17. package/skills/pikku-build/references/phase-4-build.md +249 -0
  18. package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
  19. package/skills/pikku-build/references/platform.md +5 -6
  20. package/skills/pikku-build/references/quick.md +2 -2
  21. package/skills/pikku-build/references/ship.md +4 -5
  22. package/skills/pikku-changes/SKILL.md +13 -7
  23. package/skills/pikku-concepts/SKILL.md +19 -258
  24. package/skills/pikku-concepts/references/cli.md +63 -0
  25. package/skills/pikku-concepts/references/functions.md +117 -0
  26. package/skills/pikku-concepts/references/language.md +5 -5
  27. package/skills/pikku-concepts/references/packages.md +6 -4
  28. package/skills/pikku-concepts/references/testing.md +14 -0
  29. package/skills/pikku-convert-screen/SKILL.md +7 -1
  30. package/skills/pikku-deploy/SKILL.md +29 -30
  31. package/skills/pikku-deploy/references/ws.md +1 -1
  32. package/skills/pikku-emails/SKILL.md +7 -3
  33. package/skills/pikku-guide/SKILL.md +7 -10
  34. package/skills/pikku-i18n/SKILL.md +18 -19
  35. package/skills/pikku-i18n/references/messages.md +6 -8
  36. package/skills/pikku-knowledge/SKILL.md +42 -45
  37. package/skills/pikku-kysely/SKILL.md +50 -13
  38. package/skills/pikku-list-query/SKILL.md +27 -14
  39. package/skills/pikku-meta/SKILL.md +13 -16
  40. package/skills/pikku-meta/references/audit.md +1 -45
  41. package/skills/pikku-meta/references/versioning.md +1 -1
  42. package/skills/pikku-middleware/SKILL.md +23 -13
  43. package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
  44. package/skills/pikku-n8n-import/SKILL.md +7 -8
  45. package/skills/pikku-permissions/SKILL.md +14 -8
  46. package/skills/pikku-react/SKILL.md +7 -6
  47. package/skills/pikku-realtime/SKILL.md +16 -11
  48. package/skills/pikku-report/SKILL.md +8 -10
  49. package/skills/pikku-scenario/SKILL.md +15 -18
  50. package/skills/pikku-scenario/references/steps.md +3 -4
  51. package/skills/pikku-scenario/references/virtual-users.md +1 -1
  52. package/skills/pikku-seo/SKILL.md +16 -8
  53. package/skills/pikku-service-backends/SKILL.md +26 -28
  54. package/skills/pikku-services/SKILL.md +8 -9
  55. package/skills/pikku-services/references/services.md +1 -1
  56. package/skills/pikku-software-archaeology/README.md +12 -1
  57. package/skills/pikku-software-archaeology/SKILL.md +9 -2
  58. package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
  59. package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
  60. package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
  61. package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
  62. package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
  63. package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
  64. package/skills/pikku-tailwind/SKILL.md +15 -16
  65. package/skills/pikku-theme/SKILL.md +15 -17
  66. package/skills/pikku-ui/SKILL.md +4 -3
  67. package/skills/pikku-webhook/SKILL.md +10 -11
  68. package/skills/pikku-wiring/SKILL.md +45 -47
  69. package/skills/pikku-wiring/references/channel.md +1 -1
  70. package/skills/pikku-wiring/references/scheduler.md +1 -1
  71. package/skills/pikku-wiring/references/trigger.md +24 -1
  72. package/skills/pikku-workflow/SKILL.md +7 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.57",
3
+ "version": "0.12.59",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -16,23 +16,26 @@
16
16
  "scripts": {
17
17
  "embed": "bun run scripts/embed.ts",
18
18
  "tsc": "bun run embed && tsc",
19
- "build": "bun run embed && tsc -b",
19
+ "build": "bun run clean && bun run embed && tsc -b",
20
20
  "ncu": "npx npm-check-updates",
21
21
  "test": "bash run-tests.sh",
22
22
  "test:watch": "bash run-tests.sh --watch",
23
23
  "test:coverage": "bash run-tests.sh --coverage",
24
- "prepublishOnly": "bun run build"
24
+ "prepublishOnly": "bun run build",
25
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
25
26
  },
26
27
  "exports": {
27
- ".": "./dist/index.js"
28
+ ".": "./dist/index.js",
29
+ "./package.json": "./package.json"
28
30
  },
29
31
  "files": [
30
32
  "dist",
31
- "skills"
33
+ "skills",
34
+ "!dist/**/*.tsbuildinfo"
32
35
  ],
33
36
  "devDependencies": {
34
- "@types/node": "^24.13.3",
35
- "yaml": "^2.9.0"
37
+ "@types/node": "^26.6.5",
38
+ "yaml": "^2.9.1"
36
39
  },
37
40
  "engines": {
38
41
  "node": ">=24"
@@ -1,9 +1,11 @@
1
1
  ---
2
2
  name: pikku-a11y
3
3
  description: >-
4
- Accessibility rules (WCAG 2.2) for shadcn/Tailwind app UI: labeled inputs, real buttons/links, keyboard and focus, contrast and not-color-alone, modals, reduced motion.
5
- TRIGGER when: building forms or any interactive UI, icon-only buttons, modals/drawers, tables/lists with actions, keyboard/focus work, or the user mentions accessibility / screen readers / WCAG.
6
- DO NOT TRIGGER when: working on backend functions, database, or deployment with no UI.
4
+ WCAG 2.2 accessibility rules for shadcn/Tailwind app UI: labeled inputs, real buttons/links,
5
+ keyboard and focus, contrast, modals, reduced motion. TRIGGER when: building forms or interactive
6
+ UI, icon-only buttons, modals/drawers, tables/lists with actions, keyboard/focus work, or the user
7
+ mentions accessibility, screen readers or WCAG. DO NOT TRIGGER when: backend, database or
8
+ deployment work with no UI.
7
9
  installGroups: [client]
8
10
  ---
9
11
 
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: pikku-addon
3
3
  description: >-
4
- Use when creating or consuming reusable function packages (addons) in Pikku. Covers wireAddon,
5
- ref(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, addons that ship
6
- database tables (pikku db export), generating an addon from an OpenAPI/Swagger spec, and
7
- cross-project function sharing. TRIGGER when: code uses wireAddon/ref()/pikkuAddonServices, the
8
- user asks about addons, reusable function packages, cross-project sharing or addon package
9
- structure, or hands over an OpenAPI/Swagger spec (file or URL) to build on. DO NOT TRIGGER when:
10
- the user asks about internal function composition (use pikku-wiring), general function
11
- definitions (use pikku-concepts), or importing an n8n export (use pikku-n8n-import).
4
+ Creating or consuming reusable Pikku function packages (addons): wireAddon, ref(),
5
+ pikkuAddonServices/pikkuAddonWireServices, package structure, shipping DB tables (pikku db
6
+ export), generating an addon from an OpenAPI/Swagger spec. TRIGGER when: code uses those APIs, or
7
+ the user asks about addons or cross-project function sharing, or hands over an OpenAPI/Swagger
8
+ spec (file or URL) to build on. DO NOT TRIGGER for internal function composition (use
9
+ pikku-wiring), general function definitions (use pikku-concepts) or n8n imports (use
10
+ pikku-n8n-import).
12
11
  installGroups: [core]
13
12
  ---
14
13
 
@@ -393,7 +392,7 @@ calls, one field out of fifty), or when the app adds a permission of its own.
393
392
  carries middleware, not permissions, so a rule on one addon function lives in
394
393
  the `permissions` of an app function that calls it. An addon called with the
395
394
  user's own credential is already limited upstream to what that user may do, so
396
- a data-aware `pikkuPermission` repeating that check (may they read *this*
395
+ a data-aware `pikkuPermission` repeating that check (may they read _this_
397
396
  invoice?) adds nothing. A session-only `pikkuAuth` (a role, a tier) is still
398
397
  worth it: it can be checked before any input exists, so the functions a user
399
398
  can't call drop out of the tools an MCP client, an agent or a workflow is
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: pikku-agent
3
3
  description: >-
4
- Use when building AI agents, chatbots or LLM-powered assistants with Pikku — pikkuAgent, ref()
5
- tool registration, memory, streaming, tool approval, thread ownership, invocation via rpc.agent,
6
- the VercelAgentRunner and its provider map, and the voiceInput/voiceOutput middlewares. TRIGGER
7
- when: code uses pikkuAgent/rpc.agent/runAgent/streamAgent/VercelAgentRunner/voiceInput, or the
8
- user asks about AI agents, chatbots, tool-calling, agent memory or streaming, model providers,
9
- speech in or out, or `pikku enable agent`. DO NOT TRIGGER when: the user asks about MCP tool
10
- exposure (use pikku-wiring), workflows (use pikku-workflow), React agent hooks (use pikku-react),
11
- or general function definitions (use pikku-concepts).
4
+ Building AI agents, chatbots and LLM assistants with Pikku: pikkuAgent, ref() tools, memory,
5
+ streaming, tool approval, thread ownership, rpc.agent, VercelAgentRunner and its provider map,
6
+ voiceInput/voiceOutput. TRIGGER when: code uses
7
+ pikkuAgent/rpc.agent/runAgent/streamAgent/VercelAgentRunner/voiceInput, or the user asks about
8
+ agents, tool-calling, agent memory, model providers, speech in/out or `pikku enable agent`. DO NOT
9
+ TRIGGER for MCP exposure (use pikku-wiring), workflows (use pikku-workflow), React agent hooks
10
+ (use pikku-react) or general functions (use pikku-concepts).
12
11
  installGroups: [core]
13
12
  ---
14
13
 
@@ -21,11 +20,11 @@ than what they look like.
21
20
 
22
21
  ## Pick the reference
23
22
 
24
- | You are… | Read |
25
- | --- | --- |
26
- | Defining or invoking an agent — tools, memory, streaming, approval, threads, images | `references/agents.md` |
27
- | Wiring the runner, or pointing model strings at a provider or gateway | `references/runner-vercel.md` |
28
- | Adding speech in or out of an agent | `references/voice.md` |
23
+ | You are… | Read |
24
+ | ----------------------------------------------------------------------------------- | ----------------------------- |
25
+ | Defining or invoking an agent — tools, memory, streaming, approval, threads, images | `references/agents.md` |
26
+ | Wiring the runner, or pointing model strings at a provider or gateway | `references/runner-vercel.md` |
27
+ | Adding speech in or out of an agent | `references/voice.md` |
29
28
 
30
29
  ## An agent is a function that reaches other functions
31
30
 
@@ -47,6 +46,10 @@ session, the credentials and the RPC depth for you.
47
46
  schema on a tool-calling agent is silently inert. The pairing that wants it is
48
47
  reading an image or a document into typed data — one tool-free agent, an
49
48
  `output` schema, and the picture passed as an `attachments` entry.
49
+ - **An `output` the model returns is validated.** The run fails with
50
+ `AgentOutputValidationError` (502, `issues` lists what did not match) rather
51
+ than handing back a malformed object; there is no retry. A `null` the model
52
+ sends for an optional field comes back absent, so `.optional()` holds.
50
53
  - **`auth` defaults to `false`**, because agents are normally invoked from an
51
54
  already-authenticated `pikkuFunc`. `scopes` and `permissions` are enforced
52
55
  either way — see `pikku-permissions`.
@@ -24,7 +24,7 @@ const runner = new VercelAgentRunner(
24
24
  **Methods:**
25
25
 
26
26
  - `stream(params: AgentRunnerParams, channel: AgentStreamChannel): Promise<AgentStepResult>` — Stream AI responses with tool calls
27
- - `run(params: AgentRunnerParams): Promise<AgentStepResult>` — Execute a single AI step (non-streaming)
27
+ - `run(params: AgentRunnerParams): Promise<AgentStepResult>` — Execute a single AI step (non-streaming). Its `object` is `unknown` and unchecked: the runner has no agent, so there is no declared `output` to validate it against
28
28
  - `transcribe({ model, audio, … })` / `generateSpeech({ model, text, voice, … })` — what `voiceInput`/`voiceOutput` call; see the voice reference
29
29
  - `generateImage`, `embed`, `embedMany`, `rerank` — the remaining AI SDK surfaces
30
30
  - `withApiKey(apiKey)` — returns a **new** runner built from `providerFactory`; returns `this` unchanged when no factory was supplied or the key is blank. This is the per-user-credential path
@@ -1,15 +1,13 @@
1
1
  ---
2
2
  name: pikku-architect
3
3
  description: >-
4
- Use to turn one claimed changeset into the technical plan its build is measured against —
5
- the tables, functions, wires, roles, scopes, screens and scenarios it owes, split into passes and
6
- written through `pikku knowledge plan set`. The plan is the denominator
7
- `pikku knowledge plan progress` divides by, so it is written BEFORE any of the changeset's code
8
- exists and never edited afterwards to match what got built. TRIGGER when: `pikku changes
9
- claim` says a changeset needs a plan, `changes done` or `pikku changes next` refuses one for having no
10
- plan, or the user asks to plan or architect a changeset. DO NOT TRIGGER when: the knowledge notes
11
- themselves are still being written (use pikku-knowledge), the plan already exists and the job is
12
- to build it (use pikku-changes), or the changeset was claimed with no plan needed.
4
+ Turn one claimed changeset into its technical plan (tables, functions, wires, roles, scopes,
5
+ screens, scenarios, split into passes) via `pikku knowledge plan set`. The plan is the denominator
6
+ `pikku knowledge plan progress` divides by, so write it BEFORE any code and never edit it to match
7
+ the build. TRIGGER when: `pikku changes claim` says a plan is needed, `changes done` or `pikku
8
+ changes next` refuses for lack of one, or the user asks to plan/architect a changeset. DO NOT
9
+ TRIGGER while knowledge notes are still being written (use pikku-knowledge), when the plan exists
10
+ and the job is building it (use pikku-changes), or when no plan was needed.
13
11
  installGroups: [core]
14
12
  agent:
15
13
  tools: read, write, edit, bash, grep
@@ -20,7 +18,6 @@ agent:
20
18
  verify:
21
19
  - id: knowledge-consistent
22
20
  command: pikku knowledge validate
23
-
24
21
  ---
25
22
 
26
23
  # Plan one changeset
@@ -1,16 +1,12 @@
1
1
  ---
2
2
  name: pikku-auth
3
3
  description: >-
4
- Use for identity in a Pikku app: authenticating a caller (login, logout, sessions, cookies,
5
- bearer tokens, API keys, JWT, OAuth/social providers, MFA, Better Auth, machine callers,
6
- `pikku login`). Covers telling authentication from authorization, the built-in strategies and
7
- the JWT service. Authorization rules (ownership, roles, scopes, global permissions) live in
8
- pikku-permissions. TRIGGER when: user asks about login, logout, session, cookie auth, bearer
9
- tokens, API keys, JWT, Better Auth, social providers, how a CLI/agent/sandbox authenticates,
10
- or hits InvalidSessionError. DO NOT TRIGGER when: the question is who may call a function,
11
- such as ownership, roles, per-row checks, scopes or MissingScopeError (use pikku-permissions),
12
- middleware mechanics with no identity involved (use pikku-middleware), or secrets and env
13
- vars (use pikku-services).
4
+ Identity in a Pikku app: authenticating a caller via login, logout, sessions, cookies, bearer
5
+ tokens, API keys, JWT, OAuth/social, MFA, Better Auth, machine callers, `pikku login`. TRIGGER
6
+ when: the user asks about those, how a CLI/agent/sandbox authenticates, or hits
7
+ InvalidSessionError. DO NOT TRIGGER for who may call a function: ownership, roles, scopes,
8
+ MissingScopeError (use pikku-permissions); identity-free middleware mechanics (use
9
+ pikku-middleware); secrets and env vars (use pikku-services).
14
10
  installGroups: [core]
15
11
  ---
16
12
 
@@ -26,7 +22,7 @@ They are separate gates, and confusing them is the most common mistake in a
26
22
  Pikku app.
27
23
 
28
24
  **Authentication** answers _who is calling_. It happens in middleware, before the
29
- function runs, and ends in a call to `setSession`.
25
+ function runs, and ends in a call to `setInitialSession`.
30
26
 
31
27
  **Authorization** answers _may this caller do this_. It is declared on the
32
28
  function — `scopes` and `permissions` — never checked inside its body. All of it is
@@ -39,19 +35,19 @@ middleware instead.
39
35
 
40
36
  ## Pick the reference
41
37
 
42
- | You are… | Read |
43
- | --- | --- |
44
- | Restricting who may call a function — ownership, roles, scopes | the pikku-permissions skill |
45
- | Reading or setting the session, or wiring `authBearer`/`authCookie`/`authAPIKey` | `references/sessions.md` |
46
- | Standing up user sign-in — OAuth, email+password, MFA, organizations | `references/better-auth.md` |
47
- | Authenticating a CLI, agent, sandbox or worker — API keys, `pikku login` | `references/machine-auth.md` |
48
- | Configuring the JWT service, or rotating a signing secret | `references/jose.md` |
38
+ | You are… | Read |
39
+ | -------------------------------------------------------------------------------- | ---------------------------- |
40
+ | Restricting who may call a function — ownership, roles, scopes | the pikku-permissions skill |
41
+ | Reading or setting the session, or wiring `authBearer`/`authCookie`/`authAPIKey` | `references/sessions.md` |
42
+ | Standing up user sign-in — OAuth, email+password, MFA, organizations | `references/better-auth.md` |
43
+ | Authenticating a CLI, agent, sandbox or worker — API keys, `pikku login` | `references/machine-auth.md` |
44
+ | Configuring the JWT service, or rotating a signing secret | `references/jose.md` |
49
45
 
50
46
  ## All authentication goes through `@pikku/better-auth`
51
47
 
52
48
  There is no second auth story. A hand-rolled user table, a bespoke password
53
- hash, a custom OAuth dance — all of them are the wrong answer, and the console
54
- will not work against them. `references/better-auth.md` has the setup; the
49
+ hash, a custom OAuth dance — all of them are the wrong answer, and nothing else
50
+ in Pikku will work against them. `references/better-auth.md` has the setup; the
55
51
  built-in strategies in `references/sessions.md` are how a resolved credential
56
52
  becomes a Pikku session, not a replacement for it.
57
53
 
@@ -48,10 +48,6 @@ Better Auth owns its own HTTP surface, database tables, and session cookie. The
48
48
 
49
49
  You do NOT hand-write routes, the session middleware, or the secret wiring — `pikkuBetterAuth` + the CLI generate all of it. Re-run `pikku all` to regenerate.
50
50
 
51
- ### The console requires Better Auth
52
-
53
- The Pikku console (`@pikku/addon-console`, enabled via `scaffold.console` in `pikku.config.json`) is an admin surface: **every console RPC now requires an authenticated session** (the functions are `pikkuFunc`; unauthenticated calls return `403`). So `scaffold.console` alone is **no longer the minimum** — you also need an auth strategy, and Better Auth is the supported one. `pikku all` **throws** if `scaffold.console` is set but no `pikkuBetterAuth(...)` is found in the project. Baseline is "must be logged in"; finer policy (admin-only, org scoping) is layered host-side via tag/HTTP middleware. See `pikku-meta` for the console's Security screen.
54
-
55
51
  ---
56
52
 
57
53
  ## Standard Setup
@@ -107,6 +103,38 @@ Enabling `session: { cookieCache: { enabled: true } }` makes the CLI split out a
107
103
 
108
104
  **Tradeoff:** server-side session revocation isn't seen until the cookie cache expires (sign-out is still immediate — it deletes the cookie).
109
105
 
106
+ **Read the cookie the way the app writes it.** The stateless middleware never builds the auth instance, so it cannot see your `betterAuth({ ... })` options. The generated one is given `advanced.cookiePrefix`, `cookieCache.strategy` and `cookieCache.version` when they are **string literals** in the `betterAuth({ ... })` call — keep them literal and there is nothing to do. When one is not (an env var, a constant from elsewhere), the CLI warns and you register your own, which runs before the generated one:
107
+
108
+ - A `jwe` or `jwt` `cookieCache.strategy` is recognised from the cookie's shape. Pin it with `cookieCache: { strategy }` if you prefer.
109
+ - A custom `advanced.cookiePrefix` must be passed as `betterAuthStatelessSession({ cookiePrefix })`.
110
+ - A `cookieCache.version` must be passed as `cookieCache: { version }`, or cookies of an older version still verify.
111
+ - `cookieCache.jwt` (e.g. a JWKS) is never passed through — register your own for it.
112
+
113
+ `pikkuBetterAuth` logs an error at startup when a constructed stateless middleware disagrees with the auth config.
114
+
115
+ ```typescript
116
+ addGlobalMiddleware([
117
+ betterAuthStatelessSession({
118
+ cookiePrefix: 'my-app',
119
+ cookieCache: { strategy: 'jwe', version: '2' },
120
+ }),
121
+ ])
122
+ ```
123
+
124
+ ### Store-backed sessions (`secondaryStorage`)
125
+
126
+ With better-auth's `secondaryStorage`, a session token _is_ the store key, so `betterAuthStoreSession` resolves a session with one `get` and no server bundled — and, unlike the stateless path, revocation is immediate. The token arrives on `Authorization: Bearer …` (enable `bearer()`) or on the session cookie; `transports` picks which.
127
+
128
+ ```typescript
129
+ const storage = inMemorySessionStore() // or your own AuthSessionStorage over Redis/KV
130
+
131
+ betterAuth({ secondaryStorage: storage, plugins: [bearer()] /* … */ })
132
+
133
+ addGlobalMiddleware([betterAuthStoreSession({ store: () => storage })])
134
+ ```
135
+
136
+ `AuthSessionStorage` is better-auth 1.7's `SecondaryStorage` contract: `get`, `set`, `delete`, `getAndDelete` and `increment`. It was called `SessionStore` before 0.13, which clashed with `@pikku/core`'s unrelated `SessionStore`. `prefixedSessionStore(storage, 'app_1/')` namespaces one backing store for several apps.
137
+
110
138
  **Don't add a redundant default `addHTTPMiddleware('*', [betterAuthSession()])`** — with cookieCache on, that re-drags the stateful server into every unit and defeats the split (validate flags it as `better-auth-stateful-session-global`). If you don't need to customize the session, the generated middleware is enough.
111
139
 
112
140
  **Customizing the session bridge (`mapSession`, `impersonation`, `apiKey`, …):** you do NOT chain a second middleware on top of the generated one — register your OWN global session middleware and the CLI steps aside (it stops generating its default). This works on both paths and is detected the same way:
@@ -126,21 +154,21 @@ resolves the caller's scopes through the registered `ScopeService` and checks th
126
154
  `admin:*` tree (`ADMIN_SCOPES` exports the ids so you never spell them as bare
127
155
  strings):
128
156
 
129
- | Gate | Scope required |
130
- | -------------------------------------------------------------------- | -------------------------- |
131
- | `impersonation` (`betterAuthSession` / `betterAuthStatelessSession`) | `admin:impersonate` |
132
- | `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link` |
133
- | the console's user directory | `admin:users:list` |
134
- | create a user out of band | `admin:users:create` |
135
- | ban / unban | `admin:users:ban` |
136
- | delete a user and their data | `admin:users:remove` |
137
- | revoke a user's sessions | `admin:users:sessions` |
138
- | set a user's password | `admin:users:password` |
139
- | read credential values and who holds them | `admin:credentials:read` |
140
- | set and delete credentials | `admin:credentials:manage` |
141
- | view declared scopes, roles, and who holds them | `admin:scopes:read` |
142
- | create roles, change their scopes, grant them | `admin:scopes:manage` |
143
- | read the audit trail | `admin:audit:read` |
157
+ | Gate | Scope required |
158
+ | ------------------------------------------------------ | -------------------------- |
159
+ | `impersonation` (any of the three session middlewares) | `admin:impersonate` |
160
+ | `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link` |
161
+ | the admin user directory | `admin:users:list` |
162
+ | create a user out of band | `admin:users:create` |
163
+ | ban / unban | `admin:users:ban` |
164
+ | delete a user and their data | `admin:users:remove` |
165
+ | revoke a user's sessions | `admin:users:sessions` |
166
+ | set a user's password | `admin:users:password` |
167
+ | read credential values and who holds them | `admin:credentials:read` |
168
+ | set and delete credentials | `admin:credentials:manage` |
169
+ | view declared scopes, roles, and who holds them | `admin:scopes:read` |
170
+ | create roles, change their scopes, grant them | `admin:scopes:manage` |
171
+ | read the audit trail | `admin:audit:read` |
144
172
 
145
173
  Holding the bare `admin` scope satisfies all of them — a parent grant covers
146
174
  everything nested beneath it — so `admin` is the direct replacement for the old
@@ -148,7 +176,7 @@ everything nested beneath it — so `admin` is the direct replacement for the ol
148
176
 
149
177
  Declare the tree in your own `defineScope` (the CLI extracts it by AST, so it must
150
178
  be an inline literal; `ADMIN_SCOPE_TREE` is exported from `@pikku/better-auth`
151
- as the reference shape). Apps wiring `@pikku/addon-console` inherit it already.
179
+ as the reference shape).
152
180
 
153
181
  ```typescript
154
182
  defineScope({
@@ -244,15 +272,15 @@ like any other. None is automatic — an app wires the ones it needs.
244
272
  | Plugin | Plugin `id` | Adds | Use it when |
245
273
  | ------------------------ | ------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------- |
246
274
  | `pikkuBan()` | `pikku-ban` | `user.banned/banReason/banExpires` | You ban users (the schema + enforcement half of the above) |
247
- | `pikkuVirtualUser()` | `virtualUser` | `POST /sign-in/virtual-user`, `user.virtualUser` | Scenarios sign in as a virtual user |
275
+ | `pikkuVirtualUser()` | `virtualUser` | `POST /sign-in/virtual-user`, `user.virtualUser` | Scenarios sign in as a virtual user |
248
276
  | `pikkuCredentialOAuth()` | `credential-oauth` | `POST /credential-oauth/link`, `/credential-oauth/callback/:providerId` | An app links OAuth2 **API credentials** for a user |
249
277
  | `pikkuDelegatedAuth()` | `delegated-auth` | `POST /sign-in/delegated` | An imported upstream API is the system of record for identity |
250
278
 
251
279
  Every one carries a `pikku` prefix, because a `plugins: [...]` array mixes these
252
280
  with better-auth's own and a bare `virtualUser()` next to `organization()` says
253
- nothing about where it came from. The unprefixed names — `ban`, `virtualUser`,
254
- `credentialOAuth`, `delegatedAuth` — are still exported as deprecated
255
- aliases, so existing apps keep working.
281
+ nothing about where it came from. The unprefixed names (`ban`, `virtualUser`,
282
+ `credentialOAuth`, `delegatedAuth`) were removed; import the `pikku`-prefixed
283
+ ones.
256
284
 
257
285
  The plugin's `id` is what better-auth stores; the **export name** is what the
258
286
  inspector reads off your `plugins` array and what generated metadata is keyed
@@ -339,6 +367,23 @@ An upstream user with no email gets one made up from the login, and the
339
367
  identity says so with `syntheticEmail: true`. A made-up address never links to
340
368
  an existing user row, so it cannot take over someone else's account.
341
369
 
370
+ Even a real upstream email does not link to an existing local user unless you
371
+ opt in, because it is still the upstream's word for that address:
372
+
373
+ ```typescript
374
+ pikkuDelegatedAuth({
375
+ // false (default): a first sign-in whose email is taken is refused.
376
+ // 'non-admins': link unless the local user holds an admin
377
+ // scope; needs scopeService, and links nobody without it.
378
+ // 'all': link anyone, administrators included.
379
+ linkExistingUsersByEmail: 'non-admins',
380
+ /* … */
381
+ })
382
+ ```
383
+
384
+ Roles are only ever granted: a role the upstream stops returning is not
385
+ revoked.
386
+
342
387
  For an addon generated from an OpenAPI spec, none of this is written by hand:
343
388
  `pikku new addon --openapi … --auth-config <file>` generates
344
389
  `authenticate<Name>Upstream` in the addon and wires this plugin, the stored
@@ -446,19 +491,9 @@ Better Auth serves everything under `basePath` (default `/api/auth`). Call these
446
491
 
447
492
  The session cookie is `better-auth.session_token` (dev) / `__Secure-better-auth.session_token` (prod).
448
493
 
449
- ### Dev quick login
450
-
451
- Set `PIKKU_DEV_QUICK_LOGIN=true` and `${basePath}/dev/quick-login` signs in a
452
- fixed dev admin (`admin@pikku.dev`), creating the user idempotently and granting
453
- it the bare `admin` scope. It is guarded twice — the env var _and_ a localhost
454
- hostname check — because a one-request path to an admin session is exactly the
455
- thing that must not survive a deploy. An app that has not declared the `admin`
456
- scope still gets a session, with a warning, since a scopeless dev user is useful.
457
-
458
494
  ### Virtual user sign-in (`virtualUser` plugin)
459
495
 
460
- A different thing from dev quick login, and the one to reach for when "sign in as
461
- someone" means **a particular kind of user** rather than one fixed admin.
496
+ Signs in as **a particular kind of user**, for development and scenario runs.
462
497
  Register it explicitly — it is not automatic:
463
498
 
464
499
  ```typescript
@@ -604,7 +639,7 @@ await provisionVirtualUsers(
604
639
  )
605
640
  ```
606
641
 
607
- It writes the same `banned` column the console's ban RPC writes (so it needs the
642
+ It writes the same `banned` column the ban RPC writes (so it needs the
608
643
  `pikkuBan()` plugin wired, and says so if it isn't), revokes the account's sessions,
609
644
  and leaves the row, its grants and its history intact — provisioning lifts the
610
645
  ban again by itself if the virtual user comes back. Deleting is deliberately not
@@ -641,7 +676,9 @@ export const auth = pikkuBetterAuth(async ({ secrets, variables, kysely }) => be
641
676
  ```
642
677
 
643
678
  - Export exactly ONE `pikkuBetterAuth` per project; the CLI generates a single catch-all worker for all auth routes.
644
- - `betterAuthSession({ auth })` (generated) bridges the better-auth session into the Pikku session on every request — you never add it by hand.
679
+ - The generated session middleware (`betterAuthSession()` or `betterAuthStatelessSession()`) bridges the better-auth session into the Pikku session on every request — you only register one yourself to customise it (`mapSession`, `impersonation`, `apiKey`; see "Stateless session" above).
680
+ - `createAuthHandler()` takes no arguments: it reads `services.auth`. The CLI generates the catch-all route with it.
681
+ - Outside a pikku function (Next.js route, TanStack loader), `getAuthSession(auth, request, createConfig, createSingletonServices)` reads the session. With a factory it builds the services and the instance once and reuses them across calls.
645
682
  - MFA, organizations, passkeys, etc. are better-auth plugins: add them to `betterAuth({ plugins: [...] })`. The catch-all route already forwards their endpoints.
646
683
 
647
684
  ---
@@ -13,6 +13,14 @@
13
13
  third argument — not on services. `setSession`/`clearSession` may be async
14
14
  (cookie and session-store backends write on the way out), so await them.
15
15
 
16
+ `setSession` means the same in middleware and in a function body: it signs the
17
+ caller in — the session is marked changed, so `authCookie` re-issues the cookie,
18
+ and it is written to the session store under its `userId`. Middleware that only
19
+ works out who the caller is from a credential it was handed (a cookie, a bearer
20
+ token, an API key) calls `setInitialSession` instead: the function sees that
21
+ session, but nothing is persisted and no cookie is re-issued. Calling
22
+ `setSession` there would rewrite the cookie and the store on every request.
23
+
16
24
  ```typescript
17
25
  // Read session in pikkuFunc (session guaranteed to exist)
18
26
  const getProfile = pikkuFunc({
@@ -1,17 +1,14 @@
1
1
  ---
2
2
  name: pikku-build
3
3
  description: >-
4
- Use to build on Pikku — turning a fresh scaffold into a working app (quick spike, real product,
5
- or a showcase that exercises every surface), adding a feature to an app that already exists, and
6
- the one-off cleanup right after a template is cloned. Covers the knowledge base, virtual users and
7
- roles, filing the work as changes, the scenario that proves each one, theming, multi-app
8
- layouts and deploying. TRIGGER when: the user asks for an app to be built on
9
- Pikku, a freshly scaffolded project needs turning into a product, the user asks to add a feature
10
- or wire up a new endpoint in a working app, or a template was just cloned or scaffolded. DO NOT
11
- TRIGGER when: the user asks for a one-off edit to an existing function, asks about Pikku concepts
12
- (use pikku-concepts), wants one specific surface explained rather than built (use that surface's
13
- skill), only needs the technical plan for one claimed changeset (use pikku-architect), or is
14
- working items off a deployed stage's changes queue (use pikku-changes).
4
+ Owns app-level work on Pikku (new app, feature, conversion); load pikku-concepts first. Turn a fresh scaffold into a working app (spike, real product or showcase), add a
5
+ feature or endpoint to an existing app, and the cleanup after a template is cloned. Covers
6
+ knowledge base, virtual users, changes, proving scenarios, theming, multi-app layouts, deploying.
7
+ TRIGGER when: asked to build an app on Pikku, a scaffold/template was just created or cloned, or
8
+ asked to add a feature or wire a new endpoint in a working app. DO NOT TRIGGER for one-off
9
+ function edits, Pikku concepts (use pikku-concepts), explaining one surface (use that surface's
10
+ skill), planning one claimed changeset (use pikku-architect) or working a stage's changes queue
11
+ (use pikku-changes).
15
12
  allowed-tools: Bash(yarn pikku meta *), Bash(yarn pikku all *), Bash(yarn tsc), Bash(git status *), Bash(git diff *), Bash(git switch *), Bash(git checkout *), Bash(git checkout -b *), Bash(git add *), Bash(git commit *), Bash(git rm *), Bash(git mv *), Bash(git log *), Bash(git branch *), Bash(yarn pikku cloud report *), Bash(npx --no pikku cloud report *)
16
13
  argument-hint: '[feature description]'
17
14
  installGroups: [core]
@@ -32,29 +29,30 @@ agent:
32
29
 
33
30
  ## Which mode
34
31
 
35
- | The situation | Read |
36
- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
37
- | A template was just cloned or scaffolded, and the tree still looks like one | `references/post-clone.md` first, then come back |
38
- | A real product, meant to be picked up by someone else | `references/app.md` — the default |
39
- | A spike, a throwaway demo, an idea nobody has committed to | `references/quick.md` |
40
- | A showcase meant to exercise every Pikku surface | `references/platform.md`, read together with `references/app.md` |
41
- | A feature added to an app that already has its knowledge base | `references/feature.md` |
42
- | Writing or fixing the scenarios that prove a changeset (app §7, §7a) | `references/scenarios.md` |
43
- | Before the first screen is built: a picture of the screens, a design direction | `references/design.md` |
44
- | Authoring or changing the theme (app §8a) | `references/theming.md` |
45
- | A screen needs data no function returns yet | `references/mocks.md` |
46
- | Adding a second frontend (app §4), at the changeset that needs it | `references/multi-app.md` |
47
- | Shipping an app as a desktop or Android app | `references/native-app.md` |
48
- | Deploying, and the deploy-readiness contract (app §9) | `references/ship.md` |
49
- | The request is built on an OpenAPI spec (auth mode, sign-in or connect screen) | `references/openapi.md` |
32
+ | The situation | Read |
33
+ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
34
+ | A template was just cloned or scaffolded, and the tree still looks like one | `references/post-clone.md` first, then come back |
35
+ | A real product, meant to be picked up by someone else | `references/app.md` (index), then the phase file it names for the phase you are in — the default |
36
+ | A spike, a throwaway demo, an idea nobody has committed to | `references/quick.md` |
37
+ | A showcase meant to exercise every Pikku surface | `references/platform.md`, read together with `references/app.md` |
38
+ | A feature added to an app that already has its knowledge base | `references/feature.md` |
39
+ | Writing or fixing the scenarios that prove a changeset (app §7, §7a) | `references/scenarios.md` |
40
+ | Before the first screen is built: a picture of the screens, a design direction | `references/design.md` |
41
+ | Authoring or changing the theme (app §8a) | `references/theming.md` |
42
+ | A screen needs data no function returns yet | `references/mocks.md` |
43
+ | Adding a second frontend (app §4), at the changeset that needs it | `references/multi-app.md` |
44
+ | Shipping an app as a desktop or Android app | `references/native-app.md` |
45
+ | Deploying, and the deploy-readiness contract (app §9) | `references/ship.md` |
46
+ | The request is built on an OpenAPI spec (auth mode, sign-in or connect screen) | `references/openapi.md` |
50
47
 
51
48
  **App is the default.** A small or toy-sounding app does not make it Quick;
52
49
  only an explicit signal of speed or throwaway-ness does. Platform is not "App
53
50
  plus more effort" — it is App plus a deliberate surface checklist: read both
54
51
  files, follow the base in full, and do not blend the two into one plan.
55
52
 
56
- Each reference names the topics it leans on but never tells you to open
57
- another file; this table is the only routing. Open a reference when its row
53
+ Apart from `app.md`, which indexes its own phase files, each reference names
54
+ the topics it leans on but never tells you to open another file; this table is
55
+ the only routing. Open a reference when its row
58
56
  matches, not up front.
59
57
 
60
58
  ## Bootstrap before anything else
@@ -109,7 +107,7 @@ generated functions through `ref()`.
109
107
  calls for a scheduled task and `capabilities.scheduler` is `false`, check
110
108
  `pikku doc` for the door before concluding you cannot build it.
111
109
  - **`metaLocale` in `pikku.config.json` is the language of authored meta** —
112
- every `description`, `title` and step `template` the console renders.
110
+ every `description`, `title` and step `template` authored in meta.
113
111
  Identifiers stay English whatever it says, and the product's own language
114
112
  lives in `messages/*.json`.
115
113
  - **`pikku all` is the gate.** Run it after touching functions, wirings or
@@ -146,8 +144,6 @@ generated functions through `ref()`.
146
144
  - **App** — the frontend's URL (`http://localhost:7104` in the template; each
147
145
  frontend in `frontends` in `pikku.config.json` has its own port)
148
146
  - **API** — `http://localhost:3000`
149
- - **Console** — `http://localhost:3000/console`, plus a deep link to each
150
- page that shows what this turn produced (the paths are below)
151
147
 
152
148
  A person who has to go hunting for the port assumes the app did not start.
153
149
 
@@ -166,46 +162,18 @@ again with `--consent <answer>`. If it says reporting is off, do not ask.
166
162
  ## Who you are talking to
167
163
 
168
164
  The prompt asks first how technical the person is: **not technical**,
169
- **technical, no code**, or **developer** (the default when unsaid). Whenever the
170
- build makes or changes something the console can show, give the
171
- `http://localhost:<port>/console/...` link instead of describing it.
165
+ **technical, no code**, or **developer** (the default when unsaid). Report in
166
+ plain words at that level, and give the links `bun run dev` printed (the app
167
+ and the API). No per-feature deep links.
172
168
 
173
- Until the app is deployed that is the local open-source console, on the port
174
- `pikku dev` printed. Once it is deployed, link the hosted console for the stage
175
- you are talking about instead.
176
-
177
- | Level | Links | Code in the conversation |
178
- | ------------------ | --------------------------- | ------------------------ |
179
- | Not technical | Product pages only | Never |
180
- | Technical, no code | Product and technical pages | Never |
181
- | Developer | Product and technical pages | As normal |
169
+ | Level | Code in the conversation |
170
+ | ------------------ | ------------------------ |
171
+ | Not technical | Never |
172
+ | Technical, no code | Never |
173
+ | Developer | As normal |
182
174
 
183
175
  "Never" includes snippets and command lines; say what changed in the person's
184
- words and link to where they can see it.
185
-
186
- **Product pages** — the only ones a non-technical person gets:
187
-
188
- | Shows | Path |
189
- | --------------------- | -------------------------------------------------------------------------- |
190
- | Knowledge, plans | `/console/knowledge`, `/console/knowledge?id=<note path>` |
191
- | Virtual users | `/console/virtual-users`, `/console/virtual-users?virtual user=<id>` |
192
- | Roles and permissions | `/console/roles`, `/console/scopes` (only with `@pikku/addon-admin` wired) |
193
- | Scenarios and runs | `/console/scenarios?id=<id>`, `/console/scenarios?view=runs&run=<run id>` |
194
- | Workflows | `/console/workflow?id=<id>` |
195
- | Agents | `/console/agents`, `/console/agents/playground?id=<agent id>` |
196
-
197
- **Technical pages** — never for a non-technical person: `/console/overview`,
198
- `/console/functions`, `/console/surface`, `/console/database`,
199
- `/console/changes`, `/console/wires/http`, `/console/wires/channel`,
200
- `/console/wires/mcp`, `/console/wires/cli`, `/console/wires/gateway`,
201
- `/console/async/scheduler`, `/console/async/queue`, `/console/async/trigger`,
202
- `/console/runtime`, `/console/emails`, `/console/webhooks`, `/console/secrets`,
203
- `/console/variables`, `/console/security`, `/console/auth-providers`,
204
- `/console/addons`, `/console/analytics`, `/console/credentials`,
205
- `/console/users`, `/console/audit`, `/console/flags`, `/console/scorers`.
206
-
207
- These come from `packages/console/src/App.tsx`; do not link a path that is not
208
- listed here.
176
+ words and point at the running app.
209
177
 
210
178
  ## What NOT to do
211
179