@pikku/skills 0.12.8 → 0.12.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "description": "The Pikku agent skills — the instruction set coding agents read to build, wire and deploy Pikku projects",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -50,9 +50,13 @@ wireAddon({
50
50
  mcp?: boolean,
51
51
  tags?: string[], // Tags applied to all addon functions
52
52
  scopes?: string[], // Required of every function, on top of its own
53
- secretOverrides?: Record<string, string>, // Remap secret names
53
+ secretOverrides?: Record<string, string>, // Remap secret names (and grant them)
54
54
  variableOverrides?: Record<string, string>, // Remap variable names
55
- credentialOverrides?: Record<string, string>, // Remap credential names
55
+ credentialOverrides?: Record<string, string>, // Remap credential names (and grant them)
56
+ secretGrants?: string[], // Secrets the app lends this addon
57
+ credentialGrants?: string[], // Credentials the app lends this addon
58
+ globalSecrets?: string, // Reason for handing over the whole SecretService
59
+ globalCredentials?: string, // Reason for handing over the whole CredentialService
56
60
  })
57
61
  ```
58
62
 
@@ -61,6 +65,58 @@ it would weaken the wiring's own gate — so the addon-level setting can require
61
65
  session but never waive one. The same package wired twice under two namespaces is
62
66
  governed by the union of both instances' scopes and tags.
63
67
 
68
+ ### An addon reads only the secrets it declared
69
+
70
+ An addon's `SecretService` and `CredentialService` are **scoped**: it may read
71
+ the secrets its own source declares (literal `getSecret('X')` calls and
72
+ `wireSecret` definitions, which the CLI collects into `declaredSecrets`) and
73
+ nothing else. Anything undeclared throws `Access denied to secret key: X` at
74
+ runtime. The same holds for credentials, and a scoped addon can never call
75
+ `getAllUsers()`.
76
+
77
+ That works for an addon naming its own secrets. It does not work for a _generic_
78
+ addon whose secret names arrive as data — `@pikku/addon-graph` reads
79
+ `getSecret(auth.credential)`, where the name comes off the workflow node — so
80
+ such an addon declares nothing and is scoped to nothing. Only the consuming app
81
+ can widen it, with one of three fields:
82
+
83
+ ```typescript
84
+ wireAddon({
85
+ name: 'graph',
86
+ package: '@pikku/addon-graph',
87
+
88
+ secretGrants: ['STRIPE_KEY'], // lend these, unrenamed
89
+ secretOverrides: { MAILGUN_KEY: 'PROD_EMAIL_KEY' }, // lend + rename
90
+ // globalSecrets: 'why no static list can cover it' // lend everything
91
+ })
92
+ ```
93
+
94
+ | field | meaning |
95
+ | ----------------- | --------------------------------------- |
96
+ | `secretOverrides` | grant **and** rename |
97
+ | `secretGrants` | grant as-is |
98
+ | `globalSecrets` | grant everything, with a written reason |
99
+
100
+ **Grants name the secret as the addon reads it**, not as your project stores it.
101
+ Scoping is checked _before_ the override map renames anything, so an overridden
102
+ secret is granted by its addon-side key — which is also why an override's key
103
+ grants and its value does not. With no rename in play the two names coincide.
104
+
105
+ `globalSecrets` / `globalCredentials` take the _reason_ for the grant, not a
106
+ boolean, because every grant is enumerated in the deploy manifest
107
+ (`unscopedSecretAddons`, `grantedSecretAddons`). Prefer `secretGrants` — reach
108
+ for `globalSecrets` only when no static list can exist, and never for an addon
109
+ that performs outbound requests, where an unrestricted secret read is an
110
+ exfiltration primitive.
111
+
112
+ A grant naming a secret your project does not declare is a build error from
113
+ `pikku all`, resolved through the override map first:
114
+
115
+ ```
116
+ Secret grant 'STIRPE_KEY' in addon 'graph' (@pikku/addon-graph) targets a secret
117
+ that does not exist. Available secrets: BETTER_AUTH_SECRET, GITHUB_OAUTH
118
+ ```
119
+
64
120
  ### `ref(name)`
65
121
 
66
122
  Type-safe reference to a function — local or addon — for use in any wiring. It
@@ -264,10 +320,10 @@ wireHTTPRoutes({ basePath: '/api', routes: { todos: todoRoutes } })
264
320
  ### Use in AI Agents
265
321
 
266
322
  ```typescript
267
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
323
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
268
324
  import { ref } from '#pikku'
269
325
 
270
- export const todoAgent = pikkuAIAgent({
326
+ export const todoAgent = pikkuAgent({
271
327
  name: 'todo-agent',
272
328
  description: 'Manages a todo list',
273
329
  goal: 'You help users manage their todos.',
@@ -281,4 +337,4 @@ export const todoAgent = pikkuAIAgent({
281
337
  })
282
338
  ```
283
339
 
284
- See `pikku-ai-agent` — an addon function is just another `ref()` in `tools`.
340
+ See `pikku-agent` — an addon function is just another `ref()` in `tools`.
@@ -1,10 +1,10 @@
1
1
  ---
2
- name: pikku-ai-agent
2
+ name: pikku-agent
3
3
  description: >-
4
4
  Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers
5
- pikkuAIAgent, ref() tool registration, memory, streaming, tool approval, thread ownership, and
6
- invocation via rpc.agent. TRIGGER when: code uses pikkuAIAgent/rpc.agent/runAIAgent/
7
- streamAIAgent, user asks about AI agents, chatbots, LLM assistants, tool-calling agents, agent
5
+ pikkuAgent, ref() tool registration, memory, streaming, tool approval, thread ownership, and
6
+ invocation via rpc.agent. TRIGGER when: code uses pikkuAgent/rpc.agent/runAgent/
7
+ streamAgent, user asks about AI agents, chatbots, LLM assistants, tool-calling agents, agent
8
8
  memory/streaming, or `pikku enable agent`. DO NOT TRIGGER when: user asks about MCP tool
9
9
  exposure (use pikku-mcp) or general function definitions (use pikku-concepts).
10
10
  installGroups: [core]
@@ -35,15 +35,15 @@ See `pikku-concepts` for the core mental model.
35
35
 
36
36
  ## API Reference
37
37
 
38
- ### `pikkuAIAgent(config)`
38
+ ### `pikkuAgent(config)`
39
39
 
40
40
  Import it from the generated agent types file — `#pikku` does not re-export it:
41
41
 
42
42
  ```typescript
43
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
43
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
44
44
  import { ref } from '#pikku/pikku-types.gen.js'
45
45
 
46
- pikkuAIAgent({
46
+ pikkuAgent({
47
47
  name: string, // Unique agent identifier
48
48
  description: string, // What the agent does (shown in agent listings)
49
49
  summary?: string,
@@ -67,7 +67,7 @@ pikkuAIAgent({
67
67
  agentMode?: 'delegate' | 'supervise',
68
68
 
69
69
  memory?: {
70
- storage?: string, // Service name for persistence (e.g. 'aiStorage')
70
+ storage?: string, // Service name for persistence (e.g. 'agentStorage')
71
71
  vector?: string, // Vector store service name
72
72
  embedder?: string, // Embedding service name
73
73
  lastMessages?: number, // How many messages to retain in context
@@ -88,7 +88,7 @@ pikkuAIAgent({
88
88
 
89
89
  middleware?: PikkuMiddleware[],
90
90
  channelMiddleware?: PikkuChannelMiddleware[],
91
- aiMiddleware?: PikkuAIMiddlewareHooks[],
91
+ agentMiddleware?: PikkuAgentMiddlewareHooks[],
92
92
  })
93
93
  ```
94
94
 
@@ -140,15 +140,15 @@ asking the user for identifiers it could have been handed.
140
140
  }
141
141
  ```
142
142
 
143
- `runAIAgent` / `streamAIAgent` from `@pikku/core/ai-agent` are the layer beneath
144
- this. Their third argument is `RunAIAgentParams` (`{ sessionService?,
143
+ `runAgent` / `streamAgent` from `@pikku/core/agent` are the layer beneath
144
+ this. Their third argument is `RunAgentParams` (`{ sessionService?,
145
145
  getCredential?, anonymousOwnerResourceId? }`) — **not** `{ singletonServices }`.
146
146
  Reach for them only outside a wired function; inside one, `rpc.agent` is the
147
147
  supported path.
148
148
 
149
149
  ### Stream events
150
150
 
151
- `rpc.agent.stream` pushes `AIStreamEvent`s onto the channel:
151
+ `rpc.agent.stream` pushes `AgentStreamEvent`s onto the channel:
152
152
 
153
153
  ```typescript
154
154
  // { type: 'step-start', stepNumber }
@@ -178,10 +178,10 @@ than folding it into the parent's transcript.
178
178
  ### Define an Agent
179
179
 
180
180
  ```typescript
181
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
181
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
182
182
  import { ref } from '#pikku/pikku-types.gen.js'
183
183
 
184
- export const todoAgent = pikkuAIAgent({
184
+ export const todoAgent = pikkuAgent({
185
185
  name: 'todo-agent',
186
186
  description: 'Manages a todo list',
187
187
  goal: 'You help users manage their todos. You can list, add, complete and delete them.',
@@ -192,7 +192,7 @@ export const todoAgent = pikkuAIAgent({
192
192
  ref('todos:completeTodo'),
193
193
  ref('graph:sleep'),
194
194
  ],
195
- memory: { storage: 'aiStorage', lastMessages: 20 },
195
+ memory: { storage: 'agentStorage', lastMessages: 20 },
196
196
  maxSteps: 10,
197
197
  toolChoice: 'auto',
198
198
  })
@@ -216,7 +216,7 @@ tools** — with a tool present the runner falls back to free text, silently. If
216
216
  you need both, split the classification into its own tool-free agent.
217
217
 
218
218
  ```typescript
219
- export const structuredAgent = pikkuAIAgent({
219
+ export const structuredAgent = pikkuAgent({
220
220
  name: 'structured-agent',
221
221
  description: 'Classifies a message and returns a structured verdict',
222
222
  goal: 'You classify the sentiment of the user message.',
@@ -234,14 +234,14 @@ rather than signalling that it was short-circuited.
234
234
 
235
235
  ```typescript
236
236
  prepareStep: ({ stepNumber, tools }) => {
237
- if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
237
+ if (stepNumber >= 1) tools.length = 0 // withdraw tools after the first step
238
238
  }
239
239
  ```
240
240
 
241
241
  ### Tool approval
242
242
 
243
243
  A tool that should pause for a human sets `approvalRequired: true` (with an
244
- optional `approvalDescription`) on the *function*, not on the agent. The run then
244
+ optional `approvalDescription`) on the _function_, not on the agent. The run then
245
245
  resolves with `status: 'suspended'` and `pendingApprovals`, and streaming emits
246
246
  `approval-request`. Answer with `rpc.agent.approve(runId, approvals)`.
247
247
 
@@ -282,10 +282,10 @@ export const completeTodo = pikkuFunc({
282
282
  })
283
283
 
284
284
  // agents/todo-assistant.agent.ts
285
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
285
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
286
286
  import { ref } from '#pikku/pikku-types.gen.js'
287
287
 
288
- export const todoAssistant = pikkuAIAgent({
288
+ export const todoAssistant = pikkuAgent({
289
289
  name: 'todo-assistant',
290
290
  description: 'A helpful assistant that manages todos',
291
291
  role: 'You are an assistant that manages a user’s todo list.',
@@ -299,7 +299,7 @@ export const todoAssistant = pikkuAIAgent({
299
299
  ref('todos:createTodo'),
300
300
  ref('todos:completeTodo'),
301
301
  ],
302
- memory: { storage: 'aiStorage', lastMessages: 20 },
302
+ memory: { storage: 'agentStorage', lastMessages: 20 },
303
303
  maxSteps: 5,
304
304
  temperature: 0.7,
305
305
  })
@@ -2,9 +2,9 @@
2
2
  name: pikku-ai-vercel
3
3
  description: >-
4
4
  Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers
5
- VercelAIAgentRunner for streaming and non-streaming AI agent steps. TRIGGER when: code uses
6
- VercelAIAgentRunner, user asks about Vercel AI SDK integration, AI agent runners, or
7
- @pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-ai-agent) or
5
+ VercelAgentRunner for streaming and non-streaming AI agent steps. TRIGGER when: code uses
6
+ VercelAgentRunner, user asks about Vercel AI SDK integration, AI agent runners, or
7
+ @pikku/ai-vercel. DO NOT TRIGGER when: user asks about AI agent wiring (use pikku-agent) or
8
8
  voice I/O (use pikku-ai-voice).
9
9
  installGroups: [core]
10
10
  ---
@@ -21,7 +21,7 @@ Use this skill as an execution checklist, not reference material.
21
21
  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.
22
22
  5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
23
23
 
24
- `@pikku/ai-vercel` provides an AI agent runner backed by the [Vercel AI SDK](https://sdk.vercel.ai/). Implements `AIAgentRunnerService` from `@pikku/core`.
24
+ `@pikku/ai-vercel` provides an AI agent runner backed by the [Vercel AI SDK](https://sdk.vercel.ai/). Implements `AgentRunnerService` from `@pikku/core`.
25
25
 
26
26
  ## Installation
27
27
 
@@ -31,12 +31,12 @@ yarn add @pikku/ai-vercel ai @ai-sdk/openai # or any AI SDK provider
31
31
 
32
32
  ## API Reference
33
33
 
34
- ### `VercelAIAgentRunner`
34
+ ### `VercelAgentRunner`
35
35
 
36
36
  ```typescript
37
- import { VercelAIAgentRunner } from '@pikku/ai-vercel'
37
+ import { VercelAgentRunner } from '@pikku/ai-vercel'
38
38
 
39
- const runner = new VercelAIAgentRunner(
39
+ const runner = new VercelAgentRunner(
40
40
  providers: Record<string, any>, // provider name → AI SDK provider
41
41
  providerFactory?: (apiKey: string) => Record<string, any>,
42
42
  allowedAttachmentHosts?: string[]
@@ -45,8 +45,8 @@ const runner = new VercelAIAgentRunner(
45
45
 
46
46
  **Methods:**
47
47
 
48
- - `stream(params: AIAgentRunnerParams, channel: AIStreamChannel): Promise<AIAgentStepResult>` — Stream AI responses with tool calls
49
- - `run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>` — Execute a single AI step (non-streaming)
48
+ - `stream(params: AgentRunnerParams, channel: AgentStreamChannel): Promise<AgentStepResult>` — Stream AI responses with tool calls
49
+ - `run(params: AgentRunnerParams): Promise<AgentStepResult>` — Execute a single AI step (non-streaming)
50
50
  - `transcribe({ model, audio, … })` / `generateSpeech({ model, text, voice, … })` — what `voiceInput`/`voiceOutput` call; see `pikku-ai-voice`
51
51
  - `generateImage`, `embed`, `embedMany`, `rerank` — the remaining AI SDK surfaces
52
52
  - `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
@@ -75,7 +75,7 @@ gateway-routed providers after construction.
75
75
  ### Basic Setup
76
76
 
77
77
  ```typescript
78
- import { VercelAIAgentRunner } from '@pikku/ai-vercel'
78
+ import { VercelAgentRunner } from '@pikku/ai-vercel'
79
79
  import { createOpenAI } from '@ai-sdk/openai'
80
80
  import { createAnthropic } from '@ai-sdk/anthropic'
81
81
 
@@ -86,19 +86,19 @@ const createSingletonServices = pikkuServices(async (config, { secrets }) => {
86
86
  apiKey: (await secrets.getSecret('OPENAI_API_KEY')).reveal(),
87
87
  })
88
88
  }
89
- return { config, aiAgentRunner: new VercelAIAgentRunner(providers) }
89
+ return { config, agentRunner: new VercelAgentRunner(providers) }
90
90
  })
91
91
  ```
92
92
 
93
- The service key is **`aiAgentRunner`** — that is the name the agent wiring looks
93
+ The service key is **`agentRunner`** — that is the name the agent wiring looks
94
94
  up. Registering it as `aiRunner` leaves every agent unable to call a model.
95
95
 
96
96
  ### With an agent
97
97
 
98
98
  ```typescript
99
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
99
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
100
100
 
101
- export const assistant = pikkuAIAgent({
101
+ export const assistant = pikkuAgent({
102
102
  name: 'assistant',
103
103
  description: 'Answers questions',
104
104
  goal: 'You are a helpful assistant.',
@@ -106,16 +106,16 @@ export const assistant = pikkuAIAgent({
106
106
  })
107
107
  ```
108
108
 
109
- There is no `wireAIAgent` — agents are declared with `pikkuAIAgent` from the
110
- generated agent types. See `pikku-ai-agent` for the full config.
109
+ There is no `wireAgent` — agents are declared with `pikkuAgent` from the
110
+ generated agent types. See `pikku-agent` for the full config.
111
111
 
112
112
  ### Testing without a real provider
113
113
 
114
- Replacing the *provider* rather than the runner keeps every code path under test
114
+ Replacing the _provider_ rather than the runner keeps every code path under test
115
115
  real — tool loop, streaming, memory, approvals — and only scripts the replies.
116
116
  Sealing it with `'*'` means no model string, including ones added later, can
117
117
  reach a live endpoint:
118
118
 
119
119
  ```typescript
120
- new VercelAIAgentRunner({ '*': createMockLlmProvider() })
120
+ new VercelAgentRunner({ '*': createMockLlmProvider() })
121
121
  ```
@@ -2,10 +2,10 @@
2
2
  name: pikku-ai-voice
3
3
  description: >-
4
4
  Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a
5
- Pikku app. Covers the voiceInput/voiceOutput AI middleware from @pikku/core/ai-agent, per-script
5
+ Pikku app. Covers the voiceInput/voiceOutput AI middleware from @pikku/core/agent, per-script
6
6
  voices, and barge-in. TRIGGER when: code uses voiceInput, voiceOutput, or user asks about voice
7
7
  agents, speech-to-text, text-to-speech, transcription, or @pikku/ai-voice. DO NOT TRIGGER when:
8
- user asks about AI agent wiring generally (use pikku-ai-agent) or the runner itself (use
8
+ user asks about AI agent wiring generally (use pikku-agent) or the runner itself (use
9
9
  pikku-ai-vercel).
10
10
  ---
11
11
 
@@ -27,14 +27,14 @@ The package still publishes, but its entire source is `export {}` — there are
27
27
  `STTService`/`TTSService` interfaces and nothing to import. Do not add it as a
28
28
  dependency.
29
29
 
30
- Voice now lives in **`@pikku/core/ai-agent`** as two AI middlewares, and the
31
- speech models are reached through the `aiAgentRunner` (`transcribe` /
30
+ Voice now lives in **`@pikku/core/agent`** as two AI middlewares, and the
31
+ speech models are reached through the `agentRunner` (`transcribe` /
32
32
  `generateSpeech`) rather than through separate services. See `pikku-ai-vercel`.
33
33
 
34
34
  ## API Reference
35
35
 
36
36
  ```typescript
37
- import { voiceInput, voiceOutput } from '@pikku/core/ai-agent'
37
+ import { voiceInput, voiceOutput } from '@pikku/core/agent'
38
38
 
39
39
  voiceInput(config?: {
40
40
  model?: string // transcription model — required in practice
@@ -54,9 +54,9 @@ voiceOutput(config?: {
54
54
  })
55
55
  ```
56
56
 
57
- Both attach through the agent's **`aiMiddleware`** array, not a
58
- `middlewareHooks` option, and the agent is declared with `pikkuAIAgent` — there
59
- is no `wireAIAgent`.
57
+ Both attach through the agent's **`agentMiddleware`** array, not a
58
+ `middlewareHooks` option, and the agent is declared with `pikkuAgent` — there
59
+ is no `wireAgent`.
60
60
 
61
61
  ### `voiceInput` — audio in, text in its place
62
62
 
@@ -74,7 +74,7 @@ spoken, which is why it records two shared-notes keys on the way past:
74
74
 
75
75
  Behaviours that decide how a voice loop should be written:
76
76
 
77
- - **It is a no-op without `aiAgentRunner.transcribe`** — no error, the audio
77
+ - **It is a no-op without `agentRunner.transcribe`** — no error, the audio
78
78
  simply passes through untouched.
79
79
  - **`config.model` is required once audio actually arrives**, and throws then
80
80
  rather than at wiring time.
@@ -84,7 +84,7 @@ Behaviours that decide how a voice loop should be written:
84
84
  distinct from a transcription failure, which is worth reporting.
85
85
  - **Non-speech means an empty transcript, and nothing cleverer.** There was a
86
86
  per-segment confidence gate here and it was removed: Whisper is
87
- subtitle-trained, so it is *confident* when it invents ("Thank you." scored
87
+ subtitle-trained, so it is _confident_ when it invents ("Thank you." scored
88
88
  better than the real sentence beside it). Pick an ASR that returns an empty
89
89
  string on silence rather than trying to filter one that doesn't.
90
90
  - Audio arrives either inline (base64 `data`) or as a `url` fetched through
@@ -112,7 +112,7 @@ awaits the chain, and emits `audio-done` before the `done` event.
112
112
  ### `speakableScripts` — declare what the model can pronounce
113
113
 
114
114
  Handed a script it has no voice for, a speech model typically neither fails nor
115
- stays quiet: Kokoro reads out the *letter names* — 24 seconds of "Arabic meem,
115
+ stays quiet: Kokoro reads out the _letter names_ — 24 seconds of "Arabic meem,
116
116
  Arabic ra" for a one-line sentence. Declaring the range leaves anything outside
117
117
  it unspoken and reports it once per reply as a `voice-unsupported` data event.
118
118
 
@@ -133,15 +133,15 @@ fallback)` are exported if you need the same decision outside the middleware.
133
133
  ## Usage Pattern
134
134
 
135
135
  ```typescript
136
- import { pikkuAIAgent } from '#pikku/agent/pikku-agent-types.gen.js'
137
- import { voiceInput, voiceOutput } from '@pikku/core/ai-agent'
136
+ import { pikkuAgent } from '#pikku/agent/pikku-agent-types.gen.js'
137
+ import { voiceInput, voiceOutput } from '@pikku/core/agent'
138
138
 
139
- export const voiceAssistant = pikkuAIAgent({
139
+ export const voiceAssistant = pikkuAgent({
140
140
  name: 'voice-assistant',
141
141
  description: 'Holds a spoken conversation',
142
142
  goal: 'You are a voice assistant. You are being listened to, not read.',
143
143
  model: 'openai/gpt-5-mini',
144
- aiMiddleware: [
144
+ agentMiddleware: [
145
145
  voiceInput({ model: 'deepinfra/openai/whisper-large-v3-turbo' }),
146
146
  voiceOutput({
147
147
  model: 'deepinfra/hexgrad/Kokoro-82M',
@@ -27,7 +27,7 @@ Pikku is a TypeScript framework that separates business logic from transport mec
27
27
 
28
28
  For deep-dive on each topic, see the dedicated skills:
29
29
 
30
- - **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-ai-agent`, `pikku-workflow`
30
+ - **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-agent`, `pikku-workflow`
31
31
  - **Authorization**: `pikku-security` (authentication/sessions), `pikku-permissions` (permission checks, scopes), `pikku-middleware` (global/tag/route middleware)
32
32
  - **Infrastructure**: `pikku-services`, `pikku-config`
33
33
  - **Project introspection**: `pikku-info`
@@ -44,7 +44,7 @@ pikkuFunc (pure business logic)
44
44
  ├── wireMCPTool → Model Context Protocol (AI tools)
45
45
  ├── wireCLI → CLI commands
46
46
  ├── wireTrigger → Event-driven (Redis pub/sub, PG LISTEN/NOTIFY)
47
- ├── pikkuAIAgent → AI agents / chatbots
47
+ ├── pikkuAgent → AI agents / chatbots
48
48
  ├── pikkuWorkflow → Multi-step durable workflows
49
49
  └── wire.rpc → Internal function-to-function calls
50
50
  ```
@@ -122,7 +122,7 @@ pikkuFunc({
122
122
  permissionsInBody?: boolean, // Last resort; needs allow.permissionsInBody in config
123
123
  middleware?: PikkuMiddleware[], // See pikku-middleware
124
124
 
125
- // Agent tooling — see pikku-ai-agent
125
+ // Agent tooling — see pikku-agent
126
126
  approvalRequired?: boolean,
127
127
  approvalDescription?: (services, data) => Promise<string>,
128
128
 
@@ -144,7 +144,7 @@ reject every caller it exists to serve. Gate those with `permissions`, which
144
144
  receive the optional session and may pass anonymous.
145
145
 
146
146
  **Generics XOR `input`/`output` — never both.** A function's data and return
147
- types come from *one* source: either the `input`/`output` schemas (preferred —
147
+ types come from _one_ source: either the `input`/`output` schemas (preferred —
148
148
  they double as runtime validation and OpenAPI) or type generics
149
149
  (`pikkuFunc<In, Out>({ ... })`). Passing both makes the two disagree and forces
150
150
  `as any` casts. Do not annotate the `func` return type inline either — let the
@@ -230,7 +230,7 @@ await server.start()
230
230
 
231
231
  **Lifecycle hooks do not run on this path** — only `pikku dev` and `pikku serve` invoke them. Do your startup work directly in the entrypoint instead.
232
232
 
233
- `pikku validate` warns when a project starts a server by hand *and* depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
233
+ `pikku validate` warns when a project starts a server by hand _and_ depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
234
234
 
235
235
  ## Code Generation
236
236
 
@@ -42,14 +42,14 @@ export default createCloudflareHandler(
42
42
  )
43
43
  ```
44
44
 
45
- | Factory | For |
46
- | --- | --- |
47
- | `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled` |
48
- | `createCloudflareWorkerHandler(factories)` | HTTP / agent / RPC / workflow-orchestrator units |
49
- | `createCloudflareCronHandler(factories)` | cron units |
50
- | `createCloudflareQueueHandler(factories)` | queue-consumer and workflow-step units |
51
- | `createCloudflareMCPHandler(factories)` | MCP units (same HTTP transport) |
52
- | `createCloudflareWebSocketHandler(factories)` | channel units, delegating to the DO |
45
+ | Factory | For |
46
+ | -------------------------------------------------- | ------------------------------------------------ |
47
+ | `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled` |
48
+ | `createCloudflareWorkerHandler(factories)` | HTTP / agent / RPC / workflow-orchestrator units |
49
+ | `createCloudflareCronHandler(factories)` | cron units |
50
+ | `createCloudflareQueueHandler(factories)` | queue-consumer and workflow-step units |
51
+ | `createCloudflareMCPHandler(factories)` | MCP units (same HTTP transport) |
52
+ | `createCloudflareWebSocketHandler(factories)` | channel units, delegating to the DO |
53
53
 
54
54
  `factories` is `{ createConfig, createSingletonServices, createPlatformServices? }`.
55
55
 
@@ -71,7 +71,7 @@ const services = await setupServices(env, {
71
71
  **Do not hand-roll this.** Beyond building `LocalVariablesService` /
72
72
  `LocalSecretService` and caching the result, it calls `setSingletonServices()` —
73
73
  and the core runners (`fetchData`, `runQueueJob`, `runScheduled`) resolve
74
- services through that global slot, *not* through the value you were returned. A
74
+ services through that global slot, _not_ through the value you were returned. A
75
75
  setup function that only returns the services leaves every request throwing
76
76
  "Singleton services not initialized" as a CF `1101`. It also stashes the env via
77
77
  `setCloudflareEnv`, which `getCloudflareEnv()` reads for bindings.
@@ -102,14 +102,17 @@ yarn add @pikku/ws
102
102
  ```
103
103
 
104
104
  ```typescript
105
- import { pikkuWebsocketHandler } from '@pikku/ws'
105
+ import { DEFAULT_WS_MAX_PAYLOAD, pikkuWebsocketHandler } from '@pikku/ws'
106
106
  import { stopSingletonServices } from '@pikku/core'
107
107
  import { Server } from 'http'
108
108
  import { WebSocketServer } from 'ws'
109
109
  import './.pikku/pikku-bootstrap.gen.js'
110
110
 
111
111
  const server = new Server()
112
- const wss = new WebSocketServer({ noServer: true })
112
+ const wss = new WebSocketServer({
113
+ noServer: true,
114
+ maxPayload: DEFAULT_WS_MAX_PAYLOAD,
115
+ })
113
116
 
114
117
  pikkuWebsocketHandler({
115
118
  server,
@@ -4,8 +4,11 @@ description: >-
4
4
  Use for the Pikku dependency security audit: the `pikku audit` CLI command, the
5
5
  `.pikku/audit.json` artifact, the `SecurityAuditReport` type in @pikku/core, and the console
6
6
  Security screen (getSecurityAudit / runSecurityAudit / updateDependency + SecurityAuditView).
7
- TRIGGER when: user asks about `pikku audit`, dependency vulnerabilities/advisories, outdated
8
- dependencies, the Security screen/page in the console, updating a vulnerable dependency, or
7
+ Also covers `pikku update`, which moves the @pikku/* dependency set forward and reports the
8
+ peers those versions need.
9
+ TRIGGER when: user asks about `pikku audit` or `pikku update`, dependency
10
+ vulnerabilities/advisories, outdated dependencies, upgrading Pikku itself, peer dependency
11
+ conflicts, the Security screen/page in the console, updating a vulnerable dependency, or
9
12
  reading/rendering audit.json. DO NOT TRIGGER when: user asks about authentication/sessions/JWT
10
13
  (use pikku-security), permissions (use pikku-permissions), or secrets/env vars (use
11
14
  pikku-config).
@@ -43,11 +46,47 @@ installGroups: [core]
43
46
  `bun outdated`, normalised into one `SecurityAuditReport` with per-severity /
44
47
  per-update-level counts). Other PMs are detected but **stubbed** with a `note`
45
48
  field until their shapes are normalised — issues/updates come back empty.
46
- - `bun audit` exits non-zero when it *finds* advisories but still writes the
49
+ - `bun audit` exits non-zero when it _finds_ advisories but still writes the
47
50
  payload to stdout, so a non-zero exit **with output** is data. A non-zero exit
48
51
  with **no** output — or a launch failure, timeout, or a blown 32MB buffer —
49
52
  throws, precisely so a failed run can't masquerade as "0 advisories".
50
53
 
54
+ ## The `pikku update` command
55
+
56
+ Narrower than `audit --outdated`, and the only one that writes: it moves the
57
+ **@pikku/\* set** forward and reports the peers those versions need. Use `audit`
58
+ to learn a dependency is vulnerable; use `update` to move Pikku itself.
59
+
60
+ - `pikku update` — reports only. Nothing is written without `--update`.
61
+ - `pikku update --update` — writes the new ranges into every covered
62
+ package.json, then runs an install. `--no-install` writes and stops.
63
+ - `pikku update --update-peers` — implies `--update` and additionally writes the
64
+ ranges unsatisfied peers require, **for peers the project already declares**.
65
+ A peer it does not declare is reported and never added — adding a dependency
66
+ is not an update. Separate from `--update` because a peer bump can cross a
67
+ **major** of a third-party package (`ai` 5 → 6), which is not a call to make
68
+ on the user's behalf.
69
+ - `--tag <dist-tag>` (default `latest`) reads each package's own dist-tag, so
70
+ `--tag next` moves the whole set onto prereleases. `--registry <url>` defaults
71
+ to `npm_config_registry`.
72
+ - Coverage is the nearest package.json walking up from the project root, plus
73
+ every workspace it declares — a monorepo updates in one pass. All four
74
+ dependency fields are read, `peerDependencies` included, so an addon's own
75
+ declared peer range moves with it.
76
+
77
+ Statuses, per dependency: `outdated` (the range floor is behind latest — this is
78
+ what `--update` writes), `stale-install` (the range already admits latest but
79
+ node_modules is behind — an install fixes it, no edit needed), `linked` (a
80
+ `workspace:`/`file:`/`link:`/`portal:` range — a deliberate local checkout,
81
+ counted but never listed), `manual` (a registry range we refuse to substitute
82
+ into: a union, an x-range, a `*`), `unresolved` (the registry had no such tag —
83
+ this must **never** read as "current", the same rule as a failed audit).
84
+
85
+ Peers are read off the version the run **lands on**, not the one installed —
86
+ the point is what the target needs. An @pikku peer the same run already brings
87
+ forward is not reported, and an unsatisfied _optional_ peer the project never
88
+ declared is skipped.
89
+
51
90
  ## Console integration (@pikku/addon-console)
52
91
 
53
92
  Three RPCs, all reading/writing the same artifact via the meta service. Shared