@pikku/skills 0.12.58 → 0.12.60
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 +2 -2
- package/package.json +10 -7
- package/skills/pikku-a11y/SKILL.md +5 -3
- package/skills/pikku-addon/SKILL.md +8 -9
- package/skills/pikku-addon/references/addon-package-manifest.md +53 -0
- package/skills/pikku-agent/SKILL.md +16 -13
- package/skills/pikku-agent/references/runner-vercel.md +1 -1
- package/skills/pikku-architect/SKILL.md +7 -10
- package/skills/pikku-auth/SKILL.md +16 -20
- package/skills/pikku-auth/references/better-auth.md +74 -37
- package/skills/pikku-auth/references/sessions.md +8 -0
- package/skills/pikku-build/SKILL.md +36 -68
- package/skills/pikku-build/references/app.md +27 -828
- package/skills/pikku-build/references/feature.md +3 -5
- package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
- package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
- package/skills/pikku-build/references/phase-3-plan.md +62 -0
- package/skills/pikku-build/references/phase-4-build.md +253 -0
- package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
- package/skills/pikku-build/references/platform.md +5 -6
- package/skills/pikku-build/references/quick.md +2 -2
- package/skills/pikku-build/references/ship.md +11 -5
- package/skills/pikku-changes/SKILL.md +13 -7
- package/skills/pikku-concepts/SKILL.md +26 -258
- package/skills/pikku-concepts/references/cli.md +63 -0
- package/skills/pikku-concepts/references/functions.md +117 -0
- package/skills/pikku-concepts/references/language.md +5 -5
- package/skills/pikku-concepts/references/packages.md +6 -4
- package/skills/pikku-concepts/references/testing.md +14 -0
- package/skills/pikku-convert-screen/SKILL.md +7 -1
- package/skills/pikku-deploy/SKILL.md +29 -30
- package/skills/pikku-deploy/references/ws.md +1 -1
- package/skills/pikku-emails/SKILL.md +7 -3
- package/skills/pikku-guide/SKILL.md +7 -10
- package/skills/pikku-i18n/SKILL.md +18 -19
- package/skills/pikku-i18n/references/messages.md +6 -8
- package/skills/pikku-knowledge/SKILL.md +42 -45
- package/skills/pikku-kysely/SKILL.md +50 -13
- package/skills/pikku-list-query/SKILL.md +27 -14
- package/skills/pikku-meta/SKILL.md +13 -16
- package/skills/pikku-meta/references/audit.md +1 -45
- package/skills/pikku-meta/references/versioning.md +1 -1
- package/skills/pikku-middleware/SKILL.md +23 -13
- package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
- package/skills/pikku-n8n-import/SKILL.md +7 -8
- package/skills/pikku-permissions/SKILL.md +14 -8
- package/skills/pikku-react/SKILL.md +7 -6
- package/skills/pikku-realtime/SKILL.md +16 -11
- package/skills/pikku-report/SKILL.md +8 -10
- package/skills/pikku-scenario/SKILL.md +8 -12
- package/skills/pikku-scenario/references/steps.md +3 -4
- package/skills/pikku-scenario/references/virtual-users.md +1 -1
- package/skills/pikku-seo/SKILL.md +16 -8
- package/skills/pikku-service-backends/SKILL.md +26 -28
- package/skills/pikku-services/SKILL.md +8 -9
- package/skills/pikku-services/references/services.md +1 -1
- package/skills/pikku-software-archaeology/README.md +12 -1
- package/skills/pikku-software-archaeology/SKILL.md +9 -2
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
- package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
- package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
- package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
- package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
- package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
- package/skills/pikku-tailwind/SKILL.md +15 -16
- package/skills/pikku-theme/SKILL.md +15 -17
- package/skills/pikku-ui/SKILL.md +4 -3
- package/skills/pikku-webhook/SKILL.md +10 -11
- package/skills/pikku-wiring/SKILL.md +45 -47
- package/skills/pikku-wiring/references/channel.md +1 -1
- package/skills/pikku-wiring/references/scheduler.md +1 -1
- package/skills/pikku-wiring/references/trigger.md +24 -1
- 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.
|
|
3
|
+
"version": "0.12.60",
|
|
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": "^
|
|
35
|
-
"yaml": "^2.9.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
cross-project function sharing
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- Package Structure
|
|
6
6
|
- pikku.config.json
|
|
7
7
|
- package.json (key fields)
|
|
8
|
+
- Platform service contributors (`pikku.contributors`)
|
|
8
9
|
- tsconfig.json (key fields)
|
|
9
10
|
|
|
10
11
|
`npx pikku new addon` scaffolds these files. You rarely hand-edit them — consult this when wiring exports or config by hand.
|
|
@@ -98,6 +99,58 @@ live in a file that never imports `#pikku` — see "Functions" in the skill.
|
|
|
98
99
|
writes `@my-org/addon-todos/.pikku/rpc/...`, exactly as it would in an
|
|
99
100
|
application, and the leaf stays the package's own business.
|
|
100
101
|
|
|
102
|
+
## Platform service contributors (`pikku.contributors`)
|
|
103
|
+
|
|
104
|
+
An addon can ship code that runs in the **host's** generated
|
|
105
|
+
`createPlatformServices` — a log source over the host's database, a client
|
|
106
|
+
built from a host secret. `wireAddon` has no field for it; the package declares
|
|
107
|
+
it in its own `package.json`, the same shape as the host's `dev.contributors`:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"exports": {
|
|
112
|
+
"./contributors": {
|
|
113
|
+
"types": "./dist/src/contributors.d.ts",
|
|
114
|
+
"import": "./dist/src/contributors.js"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"pikku": {
|
|
118
|
+
"contributors": [
|
|
119
|
+
{ "module": "@my-org/addon-logs/contributors", "export": "logSources" }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`module` resolves from the addon's package directory. Every app that wires the
|
|
126
|
+
addon (not `wireRemoteAddon`) picks the contributors up: `pikku all` emits them
|
|
127
|
+
into `.pikku/pikku-dev-platform-services.gen.ts` for `pikku dev`, and
|
|
128
|
+
`pikku deploy plan` / `apply` hands them to the provider adapter, after the
|
|
129
|
+
app's own `deploy.contributors`.
|
|
130
|
+
|
|
131
|
+
Declare what a contributor reads and sets, rather than relying on list order:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
export const logSources: PlatformServiceContributor = {
|
|
135
|
+
name: 'log-sources',
|
|
136
|
+
needs: ['kysely'], // runtime.kysely, or a contributor's services.kysely
|
|
137
|
+
provides: ['logSources', 'logSources.workspace'],
|
|
138
|
+
imports: [`import { kyselyLogSource } from '@my-org/addon-logs/log-source'`],
|
|
139
|
+
emit: () => [
|
|
140
|
+
` services.logSources = { workspace: kyselyLogSource(runtime.kysely) }`,
|
|
141
|
+
],
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Contributors are sorted so each runs after every contributor providing one of
|
|
146
|
+
its `needs`; a dotted need (`logSources.stage`) is met by a provider of the key
|
|
147
|
+
or of its prefix. Emitted lines read services the entry built before them as
|
|
148
|
+
`runtime.<name>` — `pikku dev` has logger, variables, secrets, schema and (with
|
|
149
|
+
`db`) kysely; the standalone entry has logger, kysely (with a database) and its
|
|
150
|
+
scheduler/queue/workflow/trigger services and event hub. Codegen **fails** on a
|
|
151
|
+
need nothing meets, on two contributors providing the same key, and on a cycle,
|
|
152
|
+
naming the contributors involved.
|
|
153
|
+
|
|
101
154
|
## tsconfig.json (key fields)
|
|
102
155
|
|
|
103
156
|
```json
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-agent
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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…
|
|
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
|
|
28
|
-
| Adding speech in or out of an agent
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
`pikku
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
pikku-permissions
|
|
9
|
-
|
|
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 `
|
|
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…
|
|
43
|
-
|
|
|
44
|
-
| Restricting who may call a function — ownership, roles, scopes
|
|
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
|
|
47
|
-
| Authenticating a CLI, agent, sandbox or worker — API keys, `pikku login`
|
|
48
|
-
| Configuring the JWT service, or rotating a signing secret
|
|
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
|
|
54
|
-
will
|
|
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
|
|
130
|
-
|
|
|
131
|
-
| `impersonation` (
|
|
132
|
-
| `credentialOAuth`'s `canLinkSingleton`
|
|
133
|
-
| the
|
|
134
|
-
| create a user out of band
|
|
135
|
-
| ban / unban
|
|
136
|
-
| delete a user and their data
|
|
137
|
-
| revoke a user's sessions
|
|
138
|
-
| set a user's password
|
|
139
|
-
| read credential values and who holds them
|
|
140
|
-
| set and delete credentials
|
|
141
|
-
| view declared scopes, roles, and who holds them
|
|
142
|
-
| create roles, change their scopes, grant them
|
|
143
|
-
| read the audit trail
|
|
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).
|
|
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
|
|
254
|
-
`credentialOAuth`, `delegatedAuth`
|
|
255
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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({
|