@pikku/skills 0.12.21 → 0.12.25
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/CHANGELOG.md +125 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +10 -9
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +264 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +42 -47
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/{pikku-permissions/SKILL.md → pikku-auth/references/permissions.md} +5 -24
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +3 -23
- package/skills/pikku-build/SKILL.md +87 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +77 -25
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +1 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +6 -22
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +7 -1
- package/skills/pikku-concepts/SKILL.md +75 -8
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +20 -10
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +60 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +14 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +8 -8
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +293 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-scenario/SKILL.md +64 -49
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +15 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +199 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +4 -40
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/{pikku-realtime/SKILL.md → pikku-wiring/references/realtime.md} +2 -25
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +3 -3
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Pikku Project Metadata
|
|
2
|
+
|
|
3
|
+
`pikku meta` is the machine-readable view of the project and the write path to it.
|
|
4
|
+
`pikku info` is the same ground as human-readable tables. Prefer `meta` when you are
|
|
5
|
+
going to act on the output; prefer `info` when a person is going to read it.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Reading
|
|
9
|
+
|
|
10
|
+
| Command | What it answers |
|
|
11
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| `pikku meta context` | Everything a planner needs in one call — functions, wires, middleware, permissions, workflows, capabilities, layout. Start here. |
|
|
13
|
+
| `pikku meta functions get <id>` | One function's input/output schema names, source file, tags, expose/readonly |
|
|
14
|
+
| `pikku meta schemas get <name>` | One generated JSON schema |
|
|
15
|
+
| `pikku meta workflows get <id>` | One workflow's steps |
|
|
16
|
+
| `pikku meta permissions list` | What permissions exist and where they are defined |
|
|
17
|
+
| `pikku meta middleware list` | What middleware exists |
|
|
18
|
+
| `pikku meta wires list` | Wires by transport (http, channel, scheduler, queue, trigger) |
|
|
19
|
+
| `pikku meta clients` | Exposed RPCs/workflows/channels with their type names — what a frontend can call |
|
|
20
|
+
|
|
21
|
+
`list` is the default for each group, so `pikku meta functions` and `pikku meta functions list`
|
|
22
|
+
are the same call.
|
|
23
|
+
|
|
24
|
+
A function's input/output shape comes from here. Do not infer it by reading the
|
|
25
|
+
function body, and do not cast a call site to make it compile — the schema is the type.
|
|
26
|
+
|
|
27
|
+
## Changing
|
|
28
|
+
|
|
29
|
+
`pikku meta apply` applies a batch of edits to your own source. Pass JSON as a file
|
|
30
|
+
or on stdin:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pikku meta apply ops.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"operations": [
|
|
39
|
+
{
|
|
40
|
+
"kind": "functionConfig",
|
|
41
|
+
"sourceFile": "src/functions/todos.functions.ts",
|
|
42
|
+
"exportedName": "listTodos",
|
|
43
|
+
"changes": { "title": "List Todos", "tags": ["todos", "read"] }
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
"kind": "functionConfig",
|
|
48
|
+
"sourceFile": "src/functions/todos.functions.ts",
|
|
49
|
+
"exportedName": "listTodos",
|
|
50
|
+
"changes": {
|
|
51
|
+
"permissions": {
|
|
52
|
+
"functionLevel": {
|
|
53
|
+
"name": "isTodoOwner",
|
|
54
|
+
"from": "../permissions.js"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Three kinds: `functionConfig`, `agentConfig`, `functionBody`. Every operation names
|
|
64
|
+
a `sourceFile` and the `exportedName` declared in it.
|
|
65
|
+
|
|
66
|
+
`functionConfig` changes: `title`, `description`, `summary`, `tags`, `errors`,
|
|
67
|
+
`expose`, `remote`, `mcp`, `readonly`, `approvalRequired`, `permissions`.
|
|
68
|
+
`agentConfig` changes: `name`, `description`, `instructions`, `role`, `personality`,
|
|
69
|
+
`goal`, `model`, `maxSteps`, `temperature`, `toolChoice`, `tools`, `tags`.
|
|
70
|
+
|
|
71
|
+
`null` removes a property. Edits are spliced into the original text, so formatting,
|
|
72
|
+
comments and JSDoc survive.
|
|
73
|
+
|
|
74
|
+
`permissions` and `tools` are written as identifiers rather than literals, so each
|
|
75
|
+
one carries the module it comes from (`{"name": "isTodoOwner", "from": "../permissions.js"}`)
|
|
76
|
+
and the missing import is added for you — widening an existing import from that
|
|
77
|
+
module rather than adding a second one.
|
|
78
|
+
|
|
79
|
+
### Why batch
|
|
80
|
+
|
|
81
|
+
The whole batch either lands or it does not: every operation is resolved before
|
|
82
|
+
anything is written, so a failure leaves every file untouched and names the
|
|
83
|
+
operation that caused it. Batching is also what makes one codegen pass correct —
|
|
84
|
+
**run `pikku all` once after the batch**, not once per property. The response tells
|
|
85
|
+
you whether it is needed:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"schemaVersion": "meta-apply.v1",
|
|
90
|
+
"applied": 2,
|
|
91
|
+
"files": ["src/functions/todos.functions.ts"],
|
|
92
|
+
"generatedMetaIsStale": true
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Human-readable tables (`pikku info`)
|
|
97
|
+
|
|
98
|
+
Four subcommands only — `functions`, `tags`, `middleware`, `permissions`. Routes,
|
|
99
|
+
channels, schedulers and queues are not subcommands; they are the _transport_ column
|
|
100
|
+
of `info functions --verbose`.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
yarn pikku info functions --verbose --silent
|
|
104
|
+
yarn pikku info tags --silent
|
|
105
|
+
yarn pikku info middleware --verbose --silent
|
|
106
|
+
yarn pikku info permissions --verbose --silent
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`--silent` suppresses the banner and inspector diagnostics. It works, but it is not
|
|
110
|
+
declared as an option, so every run also prints `Warning: Unknown option: --silent
|
|
111
|
+
(ignored)` — the warning is wrong. Ignore that one line.
|
|
112
|
+
|
|
113
|
+
`--limit N` caps rows (default 50); the footer says how many were withheld.
|
|
114
|
+
On `tags`, `--verbose` swaps counts for names; elsewhere it adds columns.
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-versioning
|
|
3
|
-
description: >-
|
|
4
|
-
Use when versioning Pikku function contracts, detecting breaking changes, or managing API
|
|
5
|
-
backward compatibility. Covers the version property, versions.pikku.json manifest, contract
|
|
6
|
-
hashing, and CI integration. Also covers `pikku semver`, which derives a release's semver by
|
|
7
|
-
diffing this build's surface against a deployed one and writes .pikku/changes.gen.json.
|
|
8
|
-
TRIGGER when: code uses version: on a pikkuFunc, user asks about
|
|
9
|
-
API versioning, breaking changes, contract hashes, backward compatibility, what semver a
|
|
10
|
-
release should get, comparing against production/staging, or "pikku versions" / "pikku semver"
|
|
11
|
-
CLI commands. DO NOT TRIGGER when: user asks about secrets/variables/OAuth2 (use pikku-config)
|
|
12
|
-
or general function definitions (use pikku-concepts), or about updating dependency versions
|
|
13
|
-
(use pikku-deps).
|
|
14
|
-
---
|
|
15
|
-
|
|
16
1
|
# Pikku Function Versioning
|
|
17
2
|
|
|
18
|
-
## Agent Operating Procedure
|
|
19
|
-
|
|
20
|
-
Use this skill as an execution checklist, not reference material.
|
|
21
|
-
|
|
22
|
-
1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
23
|
-
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.
|
|
24
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
25
|
-
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
26
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
27
|
-
|
|
28
|
-
Track and protect function contracts across releases. Pikku hashes each function's input/output schema into a manifest so you can detect breaking changes before they ship.
|
|
29
3
|
|
|
30
4
|
## Before You Start
|
|
31
5
|
|
|
@@ -6,8 +6,8 @@ description: >-
|
|
|
6
6
|
understanding middleware execution order and priority. TRIGGER when: user wants middleware on
|
|
7
7
|
some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global
|
|
8
8
|
interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about
|
|
9
|
-
permissions
|
|
10
|
-
|
|
9
|
+
permissions, sessions or auth strategies like authBearer/authCookie (use
|
|
10
|
+
pikku-auth), or deployment.
|
|
11
11
|
installGroups: [core]
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ installGroups: [core]
|
|
|
23
23
|
## The `pikkuMiddleware` Factory
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
|
-
import { pikkuMiddleware } from '#pikku/
|
|
26
|
+
import { pikkuMiddleware } from '#pikku/middleware'
|
|
27
27
|
|
|
28
28
|
// Simple: just a function
|
|
29
29
|
const myMiddleware = pikkuMiddleware(async (services, wire, next) => {
|
|
@@ -139,7 +139,7 @@ Tags from the function definition and the wire object are merged — middleware
|
|
|
139
139
|
### Registering Tag Middleware
|
|
140
140
|
|
|
141
141
|
```typescript
|
|
142
|
-
import { addTagMiddleware } from '#pikku/
|
|
142
|
+
import { addTagMiddleware } from '#pikku/middleware'
|
|
143
143
|
|
|
144
144
|
addTagMiddleware('machine-agent', [machineAgentBearerAuth])
|
|
145
145
|
```
|
|
@@ -223,7 +223,7 @@ export const reportSomething = pikkuFunc({
|
|
|
223
223
|
})
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
An unresolved token leaves the session unset and the function throws `MissingSessionError` — 401, for free. Declare the scope tree once with `defineScope` (see `pikku-
|
|
226
|
+
An unresolved token leaves the session unset and the function throws `MissingSessionError` — 401, for free. Declare the scope tree once with `defineScope` (see `pikku-auth`).
|
|
227
227
|
|
|
228
228
|
### It MUST be `addHTTPMiddleware`, never `addTagMiddleware`
|
|
229
229
|
|
|
@@ -255,13 +255,13 @@ Unlike tag middleware over `/rpc`, this works: `runScheduledTask` builds its wir
|
|
|
255
255
|
|
|
256
256
|
### The one sessionless exception: bootstrap
|
|
257
257
|
|
|
258
|
-
An endpoint that runs BEFORE the caller has an identity — registering a new host with a shared bootstrap key, a login, a device-code request — has no session to set. That one stays `pikkuSessionlessFunc` and declares its gate in `permissions` (see `pikku-
|
|
258
|
+
An endpoint that runs BEFORE the caller has an identity — registering a new host with a shared bootstrap key, a login, a device-code request — has no session to set. That one stays `pikkuSessionlessFunc` and declares its gate in `permissions` (see `pikku-auth`).
|
|
259
259
|
|
|
260
260
|
## Service-to-Service Bearer Auth (gate-only pattern)
|
|
261
261
|
|
|
262
262
|
Use this when the callee needs to know only THAT the caller is trusted, not WHICH caller it is. If it needs to know which, use the session pattern above.
|
|
263
263
|
|
|
264
|
-
A server that exposes RPCs only to a trusted caller (e.g. an API calling a machine-agent). Auth lives in a tag middleware — NOT in the function body. Authorization/permission checks belong in the `permissions` field (see `pikku-
|
|
264
|
+
A server that exposes RPCs only to a trusted caller (e.g. an API calling a machine-agent). Auth lives in a tag middleware — NOT in the function body. Authorization/permission checks belong in the `permissions` field (see `pikku-auth`), never inside `func`.
|
|
265
265
|
|
|
266
266
|
**On the server (the service being called):** tag the function, register a `pikkuMiddleware` that reads the `Authorization` header on that tag.
|
|
267
267
|
|
|
@@ -277,7 +277,7 @@ export const getToken = () => _token
|
|
|
277
277
|
```typescript
|
|
278
278
|
// wirings/http.wiring.ts
|
|
279
279
|
import { timingSafeEqual } from 'node:crypto'
|
|
280
|
-
import { addTagMiddleware, pikkuMiddleware } from '#pikku/
|
|
280
|
+
import { addTagMiddleware, pikkuMiddleware } from '#pikku/middleware'
|
|
281
281
|
import { UnauthorizedError } from '#pikku/error'
|
|
282
282
|
import { getToken } from '../lib/host-token.js'
|
|
283
283
|
|
|
@@ -3,7 +3,6 @@ name: pikku-n8n-import
|
|
|
3
3
|
description: 'Use to import an n8n workflow JSON export into a runnable Pikku workflow. Triggers when the user says "import this n8n workflow", "convert this n8n export to pikku", points at an n8n `.json` export or a directory of them, or picks up after `pikku import n8n` left throwing stub functions (`STUB — generated from n8n …`, `— implement me`) or a `<workflow>.integrations.json` manifest. Owns the whole flow: run the importer, triage what it could not map, fill each stub, report any missing `@pikku/addon-*` integrations, and verify the result compiles and runs with no surviving stubs. DO NOT TRIGGER for hand-written addon wiring unrelated to an n8n import (use pikku-addon), or for authoring workflows from scratch (use pikku-workflow).'
|
|
4
4
|
metadata:
|
|
5
5
|
version: 1.0.0
|
|
6
|
-
installGroups: [fabric]
|
|
7
6
|
---
|
|
8
7
|
|
|
9
8
|
# n8n → Pikku Import
|
|
@@ -1,313 +1,65 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-react
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
Use when a React frontend talks to a Pikku backend — PikkuProvider and createPikku at the app
|
|
5
|
+
root, the generated React Query hooks (usePikkuQuery, usePikkuMutation, usePikkuInfiniteQuery),
|
|
6
|
+
direct usePikkuRPC / usePikkuFetch calls, realtime subscriptions, agent and workflow hooks, and
|
|
7
|
+
the dev actor switcher. TRIGGER when: writing a React component that fetches or mutates backend
|
|
8
|
+
data, wiring PikkuProvider, paginating, running or tracking a workflow from the client, or
|
|
9
|
+
asking about useDevActors / VITE_DEV_ACTORS / quick login. DO NOT TRIGGER when: working on the
|
|
10
|
+
backend (use pikku-wiring), defining the workflow itself (use pikku-workflow), or writing
|
|
11
|
+
user-facing copy (use pikku-i18n).
|
|
4
12
|
installGroups: [client]
|
|
5
13
|
---
|
|
6
14
|
|
|
7
15
|
# Pikku React
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
The hook names and their argument types come from your generated `api.gen.ts` —
|
|
18
|
+
read it for what this app actually exposes. This skill is the part it cannot
|
|
19
|
+
tell you: which hook a given need calls for, and where the generated client
|
|
20
|
+
stops.
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
## Pick the reference
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
| You are… | Read |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| Wiring the app root, resolving the server URL, authenticating, or subscribing to realtime | `references/client.md` |
|
|
27
|
+
| Fetching, mutating or paginating data | `references/react-query.md` |
|
|
28
|
+
| Starting a workflow and showing its progress | `references/workflows.md` |
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
two hooks. It does **not** depend on React Query — that's a separate
|
|
21
|
-
opt-in via the generated `api.gen.ts`. Use this skill when setting up the
|
|
22
|
-
provider or making direct RPC calls.
|
|
30
|
+
## Reach for what
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
| Need | Use |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| Render data, dedupe and cache | `usePikkuQuery` |
|
|
35
|
+
| Trigger a write and wait for the result | `usePikkuMutation` |
|
|
36
|
+
| Paginate | `usePikkuInfiniteQuery` |
|
|
37
|
+
| One-off call from an event handler | `usePikkuRPC()` |
|
|
38
|
+
| Hit a REST endpoint rather than an RPC | `usePikkuFetch()` |
|
|
39
|
+
| Talk to one named AI agent | `usePikkuAgent(name)` → `.run` / `.stream` / `.approve` |
|
|
40
|
+
| Run one named workflow | `usePikkuWorkflow(name)` → `.start` / `.run` / `.status` |
|
|
41
|
+
| A workflow long enough to need progress UI | `references/workflows.md` |
|
|
42
|
+
| Subscribe to events, SSE or a channel | `usePikkuRealtime()` |
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
PikkuProvider,
|
|
29
|
-
createPikku,
|
|
30
|
-
usePikkuFetch,
|
|
31
|
-
usePikkuRPC,
|
|
32
|
-
usePikkuRealtime,
|
|
33
|
-
usePikkuAgent,
|
|
34
|
-
usePikkuWorkflow,
|
|
35
|
-
asI18n,
|
|
36
|
-
} from '@pikku/react'
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`usePikkuRealtime` is only valid when you wired a `PikkuRealtime` class via
|
|
40
|
-
`createPikku` — see the setup section. `usePikkuAgent` and `usePikkuWorkflow`
|
|
41
|
-
are thin bindings over the RPC client that pin one agent/workflow name, so a
|
|
42
|
-
component never repeats it. `asI18n` is the i18n brand (see **pikku-i18n**).
|
|
43
|
-
|
|
44
|
-
## Resolving the server URL
|
|
45
|
-
|
|
46
|
-
Every client (`createPikku`, realtime, the auth client) resolves its base
|
|
47
|
-
through one shared helper in `src/lib/env.ts`. Write this once:
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
// Endpoints come from env, never hardcoded.
|
|
51
|
-
export function apiUrl(): string {
|
|
52
|
-
// SSR: the client hooks only run in the browser, so a placeholder is fine.
|
|
53
|
-
if (import.meta.env.SSR) {
|
|
54
|
-
return import.meta.env.VITE_API_URL ?? '/__api'
|
|
55
|
-
}
|
|
56
|
-
return import.meta.env.VITE_API_URL ?? `${window.location.origin}/api`
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**Never fall back to `http://localhost:3000`.** `import.meta.env.VITE_API_URL`
|
|
61
|
-
is substituted by Vite at _build_ time, so any deploy that supplies the URL as
|
|
62
|
-
a _runtime_ env var or platform binding leaves it `undefined` in the shipped
|
|
63
|
-
bundle — the fallback is then the only branch that ever runs in the browser. A
|
|
64
|
-
localhost fallback means every request from a deployed app goes to the user's
|
|
65
|
-
own machine. `origin + '/api'` is same-origin, needs no build-time knowledge of
|
|
66
|
-
the domain, and is correct wherever the app is served from.
|
|
67
|
-
|
|
68
|
-
For local dev, set `VITE_API_URL`, or proxy `/api` → your backend in
|
|
69
|
-
`vite.config.ts` under `server.proxy`. One `/api` entry also covers
|
|
70
|
-
`/api/auth/*`; only add more entries for root-level routes outside `/api`.
|
|
71
|
-
|
|
72
|
-
## Setup at the app root
|
|
73
|
-
|
|
74
|
-
```tsx
|
|
75
|
-
import { createPikku, PikkuProvider } from '@pikku/react'
|
|
76
|
-
import { PikkuFetch } from './pikku/pikku-fetch.gen'
|
|
77
|
-
import { PikkuRPC } from './pikku/pikku-rpc.gen'
|
|
78
|
-
import { apiUrl } from './lib/env'
|
|
79
|
-
|
|
80
|
-
const pikku = createPikku(PikkuFetch, PikkuRPC, {
|
|
81
|
-
serverUrl: apiUrl(),
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
createRoot(document.getElementById('root')!).render(
|
|
85
|
-
<PikkuProvider pikku={pikku}>
|
|
86
|
-
<App />
|
|
87
|
-
</PikkuProvider>
|
|
88
|
-
)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
If the project also exposes realtime events (see **pikku-realtime**), pass
|
|
92
|
-
the `PikkuRealtime` class as the third argument and the instance gets a
|
|
93
|
-
`realtime` field too:
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
import { PikkuRealtime } from './pikku/realtime.gen'
|
|
97
|
-
|
|
98
|
-
const pikku = createPikku(PikkuFetch, PikkuRPC, PikkuRealtime, {
|
|
99
|
-
serverUrl: apiUrl(),
|
|
100
|
-
})
|
|
101
|
-
// pikku.fetch / pikku.rpc / pikku.realtime — all share the same fetch
|
|
102
|
-
// (server URL + auth configured once).
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The generated classes come from your `pikku.config.json`:
|
|
106
|
-
|
|
107
|
-
| config field | generated file |
|
|
108
|
-
| ---------------------------- | ----------------------------------------------------- |
|
|
109
|
-
| `clientFiles.fetchFile` | typed HTTP client (`PikkuFetch` class) |
|
|
110
|
-
| `clientFiles.rpcWiringsFile` | RPC client (`PikkuRPC` class) calling all exposed fns |
|
|
111
|
-
| `clientFiles.realtimeFile` | `PikkuRealtime` (websocket events + SSE + channels) |
|
|
112
|
-
|
|
113
|
-
If a file isn't being generated, that field is missing from the config —
|
|
114
|
-
add it and re-run `pikku all`.
|
|
115
|
-
|
|
116
|
-
`createPikku(...)` accepts the same `CorePikkuFetchOptions` as `PikkuFetch`
|
|
117
|
-
plus `serverUrl`. Auth headers, request interceptors, etc. are configured
|
|
118
|
-
on the fetch instance — RPC and realtime inherit them automatically.
|
|
119
|
-
|
|
120
|
-
## Calling an RPC directly (no React Query)
|
|
121
|
-
|
|
122
|
-
Inside a component:
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
import { usePikkuRPC } from '@pikku/react'
|
|
126
|
-
|
|
127
|
-
function Logout() {
|
|
128
|
-
const rpc = usePikkuRPC()
|
|
129
|
-
return <button onClick={() => rpc.invoke('logoutUser', {})}>Sign out</button>
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
`rpc.invoke(name, data)` is typed against `FlattenedRPCMap` — `name` must
|
|
134
|
-
be an exposed function id, `data` matches the input schema, return value
|
|
135
|
-
matches the output schema.
|
|
136
|
-
|
|
137
|
-
You also have `rpc.<funcName>(data)` if the generated RPC client builds
|
|
138
|
-
direct methods (project-dependent).
|
|
139
|
-
|
|
140
|
-
## Calling fetch directly
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
const fetch = usePikkuFetch()
|
|
144
|
-
const data = await fetch.get('/some-rest-route', { searchParams: {...} })
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Use this only when the function is wired via HTTP (REST shape) and you
|
|
148
|
-
need a path-style call. For RPC calls, `usePikkuRPC()` is cleaner.
|
|
149
|
-
|
|
150
|
-
## Realtime subscriptions
|
|
151
|
-
|
|
152
|
-
If you wired a `PikkuRealtime` class into `createPikku`, use
|
|
153
|
-
`usePikkuRealtime()` to grab the shared instance:
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
import { usePikkuRealtime } from '@pikku/react'
|
|
157
|
-
import type { PikkuRealtime } from './pikku/realtime.gen'
|
|
158
|
-
|
|
159
|
-
function TodoList() {
|
|
160
|
-
const realtime = usePikkuRealtime<PikkuRealtime>()
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
return realtime.subscribe('todo-created', ({ todo }) => {
|
|
163
|
-
/* ... */
|
|
164
|
-
})
|
|
165
|
-
}, [realtime])
|
|
166
|
-
// ...
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
The hook throws if no `PikkuRealtime` was wired — that's how you know to
|
|
171
|
-
add it to `createPikku(...)`. Full event-hub setup, publishing, and SSE
|
|
172
|
-
helpers live in **pikku-realtime**.
|
|
173
|
-
|
|
174
|
-
## When to reach for what
|
|
175
|
-
|
|
176
|
-
| Need | Use |
|
|
177
|
-
| ----------------------------------- | -------------------------------------------------- |
|
|
178
|
-
| Render data, dedupe + cache | **usePikkuQuery** (react-query) |
|
|
179
|
-
| Trigger a write, wait for result | **usePikkuMutation** (react-query) |
|
|
180
|
-
| Paginate | **usePikkuInfiniteQuery** (react-query) |
|
|
181
|
-
| One-off call from an event handler | `usePikkuRPC()` direct |
|
|
182
|
-
| Hit a REST endpoint (not RPC) | `usePikkuFetch()` |
|
|
183
|
-
| Run one named workflow | `usePikkuWorkflow('name')` → `.start/.run/.status` |
|
|
184
|
-
| Talk to one named AI agent | `usePikkuAgent('name')` → `.run/.stream/.approve` |
|
|
185
|
-
| Longer-running workflow UX | **pikku-workflows-client** |
|
|
186
|
-
| Subscribe to events / SSE / channel | `usePikkuRealtime()` (see **pikku-realtime**) |
|
|
187
|
-
|
|
188
|
-
The first three live in your generated `api.gen.ts` (see the
|
|
189
|
-
**pikku-react-query** skill). This skill covers the rest.
|
|
190
|
-
|
|
191
|
-
`usePikkuAgent` and `usePikkuWorkflow` bind the name once and hand back the
|
|
192
|
-
call methods with it already applied:
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
const agent = usePikkuAgent('todo-agent')
|
|
196
|
-
const { text } = await agent.run({ message, threadId })
|
|
197
|
-
|
|
198
|
-
const workflow = usePikkuWorkflow('onboardUser')
|
|
199
|
-
const { runId } = await workflow.start({ email })
|
|
200
|
-
const state = await workflow.status(runId)
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Authentication
|
|
204
|
-
|
|
205
|
-
Auth is handled at the `PikkuFetch` layer, and `createPikku`'s options object
|
|
206
|
-
_is_ `CorePikkuFetchOptions` plus `serverUrl` — flat, not nested under a
|
|
207
|
-
`fetchOptions` key:
|
|
208
|
-
|
|
209
|
-
```tsx
|
|
210
|
-
const pikku = createPikku(PikkuFetch, PikkuRPC, {
|
|
211
|
-
serverUrl: apiUrl(),
|
|
212
|
-
credentials: 'include', // cookie sessions
|
|
213
|
-
authHeaders: { jwt: token }, // or { apiKey }
|
|
214
|
-
transformDate: true,
|
|
215
|
-
})
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
There is no request-interceptor hook. For a token that changes after startup,
|
|
219
|
-
call the setter on the shared instance — RPC and realtime pick it up because
|
|
220
|
-
they hold the same fetch:
|
|
221
|
-
|
|
222
|
-
```tsx
|
|
223
|
-
pikku.fetch.setAuthorizationJWT(token) // null clears it
|
|
224
|
-
pikku.fetch.setAPIKey(key)
|
|
225
|
-
pikku.fetch.setHeader('x-tenant', tenantId)
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
`authHeaders.jwt` becomes `Authorization: Bearer …` and `authHeaders.apiKey`
|
|
229
|
-
becomes `X-API-KEY`; setting a JWT takes precedence over an API key.
|
|
230
|
-
|
|
231
|
-
### Dev actor sign-in (`useDevActors`)
|
|
232
|
-
|
|
233
|
-
The dev-only "Sign in as …" control: one click signs in as a declared scenario
|
|
234
|
-
persona with no password, so the app can be reviewed as each kind of user.
|
|
235
|
-
`pikku fabric validate` **requires** any frontend with a login screen to ship one
|
|
236
|
-
(`app-missing-actor-quick-login-<app>`) — without it a reviewer is locked out of
|
|
237
|
-
their own sandbox.
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
import { useDevActors } from '@pikku/react'
|
|
241
|
-
|
|
242
|
-
const { actors, signInAs, pendingEmail, isPending, error } = useDevActors({
|
|
243
|
-
// Gate both reads on the bundler's dev flag so no credential can reach a
|
|
244
|
-
// production bundle. The sandbox dev server bakes them from your personas.
|
|
245
|
-
actors: import.meta.env.DEV ? import.meta.env.VITE_DEV_ACTORS : undefined,
|
|
246
|
-
secrets: import.meta.env.DEV
|
|
247
|
-
? import.meta.env.VITE_DEV_ACTOR_SECRETS
|
|
248
|
-
: undefined,
|
|
249
|
-
apiUrl: apiUrl(),
|
|
250
|
-
onSignedIn: () => navigate({ to: '/' }),
|
|
251
|
-
})
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
- **It is UI-free**, so render it however you like. For the default rendering use
|
|
255
|
-
`<DevActorSwitcher />` from `@pikku/mantine/dev` — a separate entry point from
|
|
256
|
-
`@pikku/mantine/core`, whose contract is "drop-in alias for `@mantine/core`"
|
|
257
|
-
and so must not export components Mantine has no counterpart for.
|
|
258
|
-
- **`secrets` is `{ address: credential }`, not one shared value** — a
|
|
259
|
-
credential opens the one persona it was minted for (see
|
|
260
|
-
**pikku-better-auth**). `actors` is empty unless the host supplied both a list
|
|
261
|
-
and the credentials for it, and an actor with no credential is not offered, so
|
|
262
|
-
a production build renders nothing without you testing for it.
|
|
263
|
-
- **It takes `onSignedIn` rather than a router**, and takes the env values rather
|
|
264
|
-
than reading them, because how env is spelled is a bundler fact
|
|
265
|
-
(`import.meta.env.VITE_*` vs `process.env.NEXT_PUBLIC_*`).
|
|
266
|
-
- The underlying `signInAsActor()` and `parseDevActors()` are exported too, for a
|
|
267
|
-
non-React caller. The endpoint only accepts rows flagged `actor: true`, so it
|
|
268
|
-
can never impersonate a real user — see **pikku-better-auth**.
|
|
269
|
-
|
|
270
|
-
Do not hand-write the `devActors()` / `signInAsActor()` pair per app; that
|
|
271
|
-
copy-paste, including the `import.meta.env.DEV` gate, is exactly what this
|
|
272
|
-
replaced.
|
|
273
|
-
|
|
274
|
-
### Linking from a Mantine element: `renderRoot`, not `component`
|
|
275
|
-
|
|
276
|
-
Handing TanStack's `Link` to a Mantine element as `component={Link}` compiles,
|
|
277
|
-
renders, and navigates — and silently unties the type. Mantine's polymorphic
|
|
278
|
-
`component` prop widens the router generic to `AnyRouter`, so `to` and
|
|
279
|
-
`params` stop being checked against your actual routes. Renaming a route then
|
|
280
|
-
breaks the running app instead of the build, which is the one thing the typed
|
|
281
|
-
router exists to prevent.
|
|
282
|
-
|
|
283
|
-
Wrap the typed `Link` once and reach it through `renderRoot`, which passes the
|
|
284
|
-
props through without re-typing the element:
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
// components/links.tsx — one wrapper the whole app links through
|
|
288
|
-
import { Link } from '@tanstack/react-router'
|
|
289
|
-
|
|
290
|
-
export const AssessmentLink = (props: { assessmentId: string; children: React.ReactNode }) => (
|
|
291
|
-
<Link to="/assessments/$assessmentId" params={{ assessmentId: props.assessmentId }}>
|
|
292
|
-
{props.children}
|
|
293
|
-
</Link>
|
|
294
|
-
)
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
```tsx
|
|
298
|
-
<Button renderRoot={(p) => <AssessmentLink assessmentId={id} {...p} />}>
|
|
299
|
-
Open
|
|
300
|
-
</Button>
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
The wrapper is where `to` and `params` are checked, and it is checked once.
|
|
44
|
+
A workflow that finishes in a moment can be awaited; one that does not needs
|
|
45
|
+
start-plus-observe, or the component holds a pending state with nothing to show.
|
|
304
46
|
|
|
305
47
|
## What NOT to do
|
|
306
48
|
|
|
307
|
-
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
49
|
+
- **Do not write a client.** The generated one covers every exposed function
|
|
50
|
+
with full types; a hand-rolled RPC client or a hand-written
|
|
51
|
+
`useQuery({ queryKey, queryFn })` reimplements it worse.
|
|
52
|
+
- **Do not instantiate `PikkuFetch`/`PikkuRPC` in a component.** `createPikku`
|
|
53
|
+
runs once at the app root and the instance flows through context — and
|
|
54
|
+
`usePikkuRPC()` outside `<PikkuProvider>` throws.
|
|
55
|
+
- **Do not call the RPC client inside a `useEffect`.** The hooks handle
|
|
56
|
+
deduplication, caching and unmounting; a manual effect handles none of them.
|
|
57
|
+
- **Do not construct a hook name at runtime.** Hook names are the RPC names known
|
|
58
|
+
at generation time, and a computed one is not type-checked.
|
|
59
|
+
- **Do not poll a workflow with `setInterval`.** `useWorkflowStatus` with a
|
|
60
|
+
`refetchInterval` callback dedupes across components and stops on a terminal
|
|
61
|
+
state in one place.
|
|
62
|
+
- **Do not reach for `as any` when a hook's types disagree with you.** The
|
|
63
|
+
mismatch is the backend's input/output schema; fix it there.
|
|
64
|
+
- **Do not hardcode a user-facing string.** Every display string goes through an
|
|
65
|
+
i18n message — see `pikku-i18n`.
|