@netlify/agent-runner-cli 2.16.0-server.1 → 2.17.0-PAW-612.0

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.
@@ -9,20 +9,17 @@ A Netlify Server is one long-running Node.js process deployed alongside your sit
9
9
 
10
10
  ## When to use this skill: explicit request only
11
11
 
12
- Netlify Server is in Early Access. The rule for choosing it is strict:
12
+ Netlify Server is an experimental primitive. The rule for choosing it is strict:
13
13
 
14
14
  - ONLY build with a Netlify Server when the prompt **explicitly requests a "Netlify Server"** by name (for example "build this as a Netlify Server" or "use the Netlify Server primitive").
15
15
  - If the prompt does not name it, discard this skill entirely, even when the request mentions Express, Hono, WebSockets, servers in general, or would otherwise seem like a good fit. In those cases use **Netlify Functions** (see the `netlify-functions` skill) and the other platform primitives as usual.
16
16
  - NEVER migrate an existing site's functions to a Netlify Server unless explicitly asked to.
17
17
 
18
- Once a Netlify Server has been explicitly requested:
19
-
20
- - Start without a template. Every template is a full-stack framework whose catch-all function takes the requests before the server, so the server would never receive traffic.
21
- - Implement all API routes inside the server app. Create files in `netlify/functions/` only for scheduled jobs (see below), never for endpoints.
18
+ Once a Netlify Server has been explicitly requested: implement all API routes inside the server app, and do NOT create files in `netlify/functions/` alongside it.
22
19
 
23
20
  ## Quick start
24
21
 
25
- Create exactly one entrypoint at `netlify/server/index.mjs`.
22
+ Create exactly one entrypoint at `netlify/server/index.mjs`. Any Node.js server works. It MUST listen on `process.env.PORT`.
26
23
 
27
24
  Express:
28
25
 
@@ -52,87 +49,49 @@ app.get('/api/hello', (c) => c.json({ hello: 'world' }))
52
49
  export default app
53
50
  ```
54
51
 
55
- What the entry file exports decides how Netlify runs it:
56
-
57
- - **An `http.Server`**, such as `http.createServer(app)`: requests go to that server. WebSocket libraries attached to it (`ws`, Socket.IO) work.
58
- - **An object with a `fetch` method**, such as a Hono app: called for each request.
59
- - **An Express app**, or a function with 3 or more parameters: hosted in an HTTP server Netlify creates. WebSocket upgrades get a `501`, so export `http.createServer(app)` instead when you need them.
60
- - **Any other function**: called as a handler `(request, context) => Response`, the same signature as a Netlify Function. A plain Node.js `(req, res)` listener lands here too and breaks; wrap it in `http.createServer()`.
61
- - **Anything else, or no default export**: Netlify waits for your code to start a server with `listen` on `process.env.PORT`, the way the framework's documentation shows. It must be listening within 10 seconds.
62
-
63
- A handler or `fetch` app accepts WebSockets with `upgradeWebSocket(request)` from `@netlify/server`, which returns `{ socket, response }`; return `response` from the handler.
64
-
65
52
  Declare dependencies in `package.json` as usual. Deploy is the normal site deploy; nothing extra to configure. The build log confirms detection with the line `Netlify Server detected at netlify/server/index.mjs`.
66
53
 
67
54
  ## Rules
68
55
 
69
56
  - **One entrypoint only.** `netlify/server/` must contain exactly one of `index.mjs`, `index.js`, `index.ts`, or `index.mts`. Two entry files fail the build. Prefer `index.mjs` with ESM syntax; split the rest of the server into modules that the entrypoint imports.
70
- - **A server that calls `listen` uses `process.env.PORT`.** It is set before your code is imported. Do not hardcode a port.
71
- - **Node.js 24 or later.** The server runs on the build's Node.js version, and the deploy fails on anything earlier.
57
+ - **Listen on `process.env.PORT`.** It is set before your code is imported. Do not hardcode a port.
72
58
  - **Do not daemonize, fork, or spawn long-lived child processes.** One process serves the traffic.
73
59
  - Environment variables work as usual via `process.env`.
74
60
  - When adding npm modules, ensure `node_modules` is in `.gitignore`.
75
61
 
76
62
  ## Routing and static files
77
63
 
78
- By default, the server receives every request that nothing else in the project handles. Requests are handled in this order:
79
-
80
- 1. Edge Functions.
81
- 2. Redirect and rewrite rules.
82
- 3. Static files in the publish directory.
83
- 4. Netlify Functions, on the paths they're configured for.
84
- 5. The server, for everything else (or only the paths it's configured for, see below).
64
+ The server receives every request that is not served as a static file:
85
65
 
86
- Rules:
87
-
88
- - Files in the publish directory win over the server for matching paths, even on paths the server claims. Use the publish directory for assets like images, fonts, or a favicon.
66
+ - Files in the publish directory win over the server for matching paths (`preferStatic`). Use the publish directory for assets like images, fonts, or a favicon.
89
67
  - Do NOT put an `index.html` in the publish directory if the server should render `/`. A static `index.html` shadows the server's root route.
90
- - A rewrite rule that matches every path, such as a single-page app's `/* /index.html 200`, sends requests to its destination instead of the server. Limit such rules to the paths the frontend handles.
91
- - Full-stack frameworks (Next.js, Nuxt, SvelteKit, Astro, TanStack Start) usually deploy a Netlify Function on `/*` that handles every request not served by a static file, covering both page rendering and the framework's API routes. Like the rewrite above, it takes those requests before the server does, so the server never receives any. If a Netlify Server is explicitly required, do not use a full-stack framework. Otherwise, put server code in the framework's own server routes.
92
- - There is no path prefix: the server sees its requests with their original paths.
93
-
94
- To have the server handle only some paths, set `path` (defaults to `/*`) and `excludedPath`, using the same syntax as function paths. Export them as `config` from the entry file, or set them in a `[server]` section in `netlify.toml`:
95
-
96
- ```javascript
97
- export const config = {
98
- path: '/api/*',
99
- excludedPath: ['/api/legacy/*'],
100
- }
101
- ```
68
+ - There is no path prefix: the server sees `/`, `/api/anything`, and everything else, with the original paths.
102
69
 
103
70
  ## Lifecycle: scale to zero
104
71
 
105
72
  This is the most important mental model for building correctly:
106
73
 
107
- - After 5 minutes with no requests, the server is shut down. The next request starts a fresh process (a cold start).
74
+ - After a period with no traffic, the server is shut down. The next request starts a fresh process (a cold start).
108
75
  - **All process memory is lost on every scale-to-zero, deploy, and platform maintenance event.** In-memory caches are fine as caches; they are NEVER storage. Anything that must survive belongs in Netlify Blobs or Netlify Database (see the `general-database` skill to choose).
109
- - Netlify can run several instances of the server at once (up to 3; 1 on Free and Personal plans). Each has its own memory and its own WebSocket connections; nothing in memory is shared between them.
110
- - In-flight requests and open WebSocket or Server-Sent Events connections are never cut off by the idle shutdown; while any are open, the instance stays running.
111
- - The server must send a response's status and headers within 20 seconds, or the request fails with a `502`. Stream slow responses, such as AI output, or respond right away and finish with `waitUntil`.
76
+ - In-flight requests are never cut off by the idle shutdown.
112
77
  - Background work that outlives the request that triggered it (for example, responding early and then finishing a task) MUST be registered with `getContext().waitUntil(promise)`. Registered work keeps the process alive until the promise settles; unregistered work may be reclaimed the moment no requests are active.
113
- - Never keep the server alive on purpose: no self-pings, no timer that calls the server, no `waitUntil` chain that schedules more work to stay running, and no `scale.min` above `0` (during Early Access a deploy that sets it fails). Scale to zero is the design. If the user asks for an always-on server, explain this and build without it.
114
- - Scheduled or recurring work (cron, nightly cleanup, periodic sync) goes in a scheduled function (see the `netlify-functions` skill) that calls a server route. Never use `setInterval` or other timers meant to outlive a request.
115
78
  - `waitUntil` extends the idle lifetime, but deploys and platform maintenance still stop the process with only a short grace window. Keep background tasks short (seconds, not minutes), and persist anything critical before responding. For genuinely long jobs, persist a job record and process it in resumable increments across requests.
116
79
 
117
- Graceful shutdown hook (optional): register it with `onShutdown` from `@netlify/server`. It runs as soon as the instance starts stopping and receives `drained`, a promise that resolves once in-flight requests and `waitUntil` work have finished. Stop background work and close WebSockets right away, then `await drained` before releasing what that work still uses. Apps that already listen for `SIGTERM` keep working.
80
+ Graceful shutdown hook (optional): export a `shutdown` function from the entrypoint; it is called before the process is stopped. Keep it fast (a few seconds), for example flushing a buffer to Blobs. For Express-style apps that call `listen`, you can also listen for `SIGTERM` directly.
118
81
 
119
82
  ```javascript
120
- import { onShutdown } from '@netlify/server'
121
-
122
- onShutdown(async ({ drained }) => {
123
- wss.close()
124
- await drained
125
- await pool.end()
126
- })
83
+ export const shutdown = async () => {
84
+ await flushPendingWrites()
85
+ }
127
86
  ```
128
87
 
129
88
  ## Request context: geolocation, cookies, site metadata
130
89
 
131
- Inside request handling, get the Netlify request context with `getContext` from the `@netlify/server` package (add it to `package.json`):
90
+ Inside request handling, get the Netlify request context with `getContext` from the `@netlify/functions` package (add it to `package.json`):
132
91
 
133
92
  ```javascript
134
93
  import express from 'express'
135
- import { getContext } from '@netlify/server'
94
+ import { getContext } from '@netlify/functions'
136
95
 
137
96
  const app = express()
138
97
 
@@ -148,7 +107,6 @@ Rules:
148
107
 
149
108
  - ONLY call `getContext()` while handling a request (inside a route handler, middleware, or code awaited from one). It throws outside a request.
150
109
  - Works with every server form, including WebSocket upgrade handling.
151
- - Anything started inside a request (a `setInterval`, a lazily started background loop, a client that creates its own timers, WebSocket message handlers) keeps that request's context for its whole life, so `getContext()` there returns a stale request instead of throwing. Start long-lived work outside request handling, and never read per-request context from it.
152
110
 
153
111
  ### SSO user: `context.user`
154
112
 
@@ -168,62 +126,52 @@ See the [changelog post](https://www.netlify.com/changelog/2026-10-07-context-us
168
126
 
169
127
  The AI Gateway works with zero configuration: provider credentials and base URLs (`OPENAI_API_KEY`, `OPENAI_BASE_URL`, and equivalents for other providers) are injected into `process.env` automatically. See the `netlify-ai-gateway` skill for supported models and usage.
170
128
 
171
- AI Gateway, Netlify Blobs, and Netlify Database clients can be created once at module scope and reused: the platform keeps their credentials current. One exception: `NETLIFY_PURGE_API_TOKEN` expires after 15 minutes, so read it from `process.env` each time you call the purge API and never store it in a module-level variable.
129
+ One rule specific to servers: **construct AI SDK clients inside request handling, never at module scope.** Credentials are injected per request, so they do not exist when the module is first imported.
172
130
 
173
131
  ```javascript
174
132
  import OpenAI from 'openai'
175
133
 
176
- const client = new OpenAI()
177
-
178
134
  app.post('/api/chat', async (req, res) => {
135
+ const client = new OpenAI() // created per request: credentials are available here
179
136
  // ...
180
137
  })
181
138
  ```
182
139
 
183
140
  ## WebSockets
184
141
 
185
- WebSockets work for servers that handle HTTP upgrades. Create the HTTP server, attach the WebSocket server to it, and export it. Starting it with `server.listen(process.env.PORT)` instead of exporting it works too.
142
+ WebSockets work for servers that handle HTTP upgrades (the `listen` form). Attach the WebSocket server to the same HTTP server:
186
143
 
187
144
  ```javascript
188
145
  // netlify/server/index.mjs
189
- import { createServer } from 'node:http'
190
146
  import express from 'express'
191
147
  import { WebSocketServer } from 'ws'
192
148
 
193
149
  const app = express()
194
- const server = createServer(app)
195
- const wss = new WebSocketServer({ server, path: '/ws' })
196
-
197
- wss.on('connection', (ws) => {
198
- ws.send('connected')
150
+ const server = app.listen(process.env.PORT)
151
+ const wss = new WebSocketServer({ noServer: true })
152
+
153
+ server.on('upgrade', (req, socket, head) => {
154
+ if (req.url !== '/ws') {
155
+ socket.destroy()
156
+ return
157
+ }
158
+ wss.handleUpgrade(req, socket, head, (ws) => {
159
+ ws.send('connected')
160
+ })
199
161
  })
200
-
201
- export default server
202
162
  ```
203
163
 
204
164
  Rules for WebSocket clients you generate:
205
165
 
206
166
  - ALWAYS implement automatic reconnection with backoff in the browser client. The platform may terminate long-lived connections periodically; a reconnecting client makes this invisible to users.
207
167
  - Do not rely on a socket staying open indefinitely, and do not keep client state only on the server side of a socket: rebuild the needed state on reconnect.
208
- - Clients can be connected to different instances, and each instance only sees its own sockets. Keep state every client must see, such as chat history, in Netlify Database or Blobs.
209
-
210
- ## Fetching user-supplied URLs
211
-
212
- An endpoint that fetches a URL taken from the request (link previews, webhook testers, image proxies) runs inside Netlify's network, so it must:
213
-
214
- - Accept only `http:` and `https:` URLs.
215
- - Resolve the hostname and refuse loopback, private, link-local, and unique-local addresses, including `169.254.169.254`.
216
- - Follow redirects manually (`redirect: 'manual'`) and check every hop the same way.
217
- - Return only what the feature needs (extracted fields, a capped body), never forward the caller's method, headers, or body. A generic `/proxy?url=` endpoint is an open relay; don't build one.
218
168
 
219
169
  ## Common mistakes
220
170
 
221
171
  - Treating process memory as a database. It is wiped on every cold start. Use Blobs or Database.
222
172
  - Hardcoding a port or defaulting to `3000` without reading `process.env.PORT` first.
223
173
  - Adding an `index.html` to the publish directory that shadows the server's `/` route.
224
- - Creating `netlify/functions/` endpoints alongside the server. Put API routes in the server app; use functions only for scheduled jobs.
174
+ - Creating `netlify/functions/` endpoints alongside the server. Put API routes in the server app.
225
175
  - Two entry files in `netlify/server/` (for example both `index.mjs` and `index.ts`). Keep one.
226
176
  - Fire-and-forget background jobs without `getContext().waitUntil(promise)`. Unregistered work may be reclaimed mid-flight; registered work should still be short and non-critical.
227
- - Calling `getContext()` at module scope. It only works while handling a request.
228
- - Storing `NETLIFY_PURGE_API_TOKEN` in a module-level variable. Read it when you call the purge API.
229
- - Keeping the server warm with self-pings or timers, or running scheduled work with `setInterval`. Use a scheduled function that calls a server route.
177
+ - Constructing AI SDK clients (or calling `getContext()`) at module scope. Per-request state does not exist at import time.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/agent-runner-cli",
3
3
  "type": "module",
4
- "version": "2.16.0-server.1",
4
+ "version": "2.17.0-PAW-612.0",
5
5
  "description": "CLI tool for running Netlify agents",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -48,6 +48,7 @@
48
48
  "check:types": "tsc --noEmit",
49
49
  "prompts": "LOG=0 vitest run --config vitest.prompts.config.js",
50
50
  "site:build": "npm run prompts && node site/build.mjs",
51
+ "site:dev": "npm run site:build && netlify dev --dir site/dist",
51
52
  "postinstall": "node scripts/postinstall.js"
52
53
  },
53
54
  "engines": {
@@ -1,78 +0,0 @@
1
- ---
2
- name: general-backend
3
- description: Use this skill whenever the task involves server-side code — API endpoints, backend logic, request handlers, WebSockets, server-side AI calls, auth checks, or any code that must run on a server rather than in the browser. Covers Netlify Server, Netlify Functions, Edge Functions, and full-stack framework server routes.
4
- ---
5
-
6
- # Server-side code on Netlify
7
-
8
- Where server-side code goes depends on the project. Work out which case applies before writing code, and for a
9
- new project, before scaffolding a template.
10
-
11
- ## New project that needs a Netlify Server: no template
12
-
13
- The create-mode templates are built on a full-stack framework that deploys its server-side rendering as a Netlify
14
- Function on every path, so a Netlify Server added to a template project never receives requests. If the project
15
- needs a Netlify Server, don't scaffold a template. A new project needs one when it has any request-driven backend
16
- code: API endpoints, data saved and read through the backend, WebSockets, server-side AI calls, or an HTTP server
17
- framework such as Express, Hono, or Fastify.
18
-
19
- A site with no backend code (static pages, a marketing or content site, a client-only tool) needs neither a server
20
- nor functions. Use the templates as usual for it.
21
-
22
- ## Project with a full-stack framework: use its server routes
23
-
24
- Full-stack frameworks (such as TanStack Start, Next.js, Astro, SvelteKit, Remix, Nuxt) build
25
- their own server code into Netlify Functions at deploy time. In these projects, put request handling in the
26
- framework's server routes or server functions instead of adding a Netlify Server. Call Netlify Database, Blobs, and
27
- the AI Gateway from those routes. Use Netlify Functions alongside them only for the cases listed under "Only when
28
- Server can't" below.
29
-
30
- HTTP server frameworks such as Express, Hono, Fastify, and Koa are different: nothing deploys them for you, so they
31
- run as a Netlify Server, covered next.
32
-
33
- ## Otherwise: Netlify Server
34
-
35
- **Netlify Server** runs one long-running Node.js process (Express, Hono, Fastify, or plain `node:http`) from
36
- `netlify/server/index.mjs`. Put every request-driven piece of backend code in it:
37
-
38
- - API endpoints and route handlers
39
- - Server-rendered pages and form handling in your own routes
40
- - WebSockets and streaming responses
41
- - Calls to the Netlify AI Gateway, Netlify Database, and Netlify Blobs
42
- - Short work that continues after the response (seconds, such as sending an email), registered with `waitUntil`
43
- - Auth checks, webhooks from third-party services, proxies to external APIs
44
-
45
- Don't pick Functions just because the task is small or looks like a single endpoint.
46
-
47
- See the **netlify-server** skill for complete setup instructions and usage examples.
48
-
49
- ## Only when Server can't: Netlify Functions
50
-
51
- Use **Netlify Functions** alongside the server only for:
52
-
53
- - **Scheduled functions**: work that runs on a cron schedule, with no incoming request.
54
- - **Event handlers**: code triggered by platform events, such as Netlify Forms submissions, Netlify Identity events
55
- (signup, login, validate), or deploy events.
56
- - **Background functions**: jobs that can run longer than about a minute and must not be cut off midway, such as
57
- generating a report or processing an upload (up to 15 minutes). When a server instance is replaced by a deploy or
58
- scaling event, its background work gets only 90 seconds to finish. The server enqueues the job by calling the
59
- background function and returns right away.
60
-
61
- Scheduled functions and event handlers run without an incoming request, which a server can't do, and background
62
- functions aren't cut off when a server instance is replaced. Everything else stays in the server, including the
63
- endpoints these functions talk to.
64
-
65
- See the **netlify-functions** skill for complete setup instructions and usage examples.
66
-
67
- ## Request and response changes at the edge: Edge Functions
68
-
69
- Use **Edge Functions** for logic that runs on every request before it reaches the server or the site: rewrites and
70
- redirects based on geolocation or cookies, A/B tests, header changes, or lightweight auth checks. They run in Deno
71
- close to visitors and don't replace the server for application logic.
72
-
73
- See the **netlify-edge-functions** skill for setup instructions.
74
-
75
- ## Existing projects
76
-
77
- If an existing project already serves its backend from `netlify/functions/`, keep using that for changes to it.
78
- Don't migrate an existing backend to a Netlify Server unless asked.