@north-light/cli 0.3.456 → 0.3.457

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.
@@ -10,3 +10,8 @@ surfaces:
10
10
  # `@north-light/plugin`
11
11
 
12
12
  Read `crouter-plugin/README` for the authoring overview and the directory pages for the specific plugin surface. The directory listing names every page. Use the directory only to choose a focused page; the reference pages hold the authoring details.
13
+
14
+ Two things a provider registered in the directory must get right, both in `docs/plugin/deploying`:
15
+
16
+ - A third-party app passes `auth: {issuer, audience, providerId: <its client id>}` to `createFetchHandler`. The directory mints scopes as `<provider id>:<group>`; without `providerId` the kit expects `<plugin name>:<group>` and answers every call 403 `scope_missing`.
17
+ - On Node, Express or Nest, mount the handler with `createNodeHandler(handler, {bodyLimit?, origin?})` instead of hand-writing a request/response adapter. It bounds the body (413 before the handler), streams the response with backpressure, and keeps repeated headers.
@@ -19,4 +19,4 @@ Start with the `node_modules/@north-light/plugin/README.md` for a complete plugi
19
19
  - [[crouter-plugin/deploying]] — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
20
20
  - [[crouter-plugin/bundles-and-memory]] — generating an archive and shipping agent-facing documents.
21
21
 
22
- The package exports only `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. Those are the complete authoring surface.
22
+ The primary authoring surface is `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `createNodeHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. The package also exports `compareManifests`, `createCallerVerifier`, `postReceipt`, `ReceiptPostError` and the structural types `createNodeHandler` takes.
@@ -7,7 +7,7 @@ when-and-why-to-read: When you need Deployment, this knowledge should be read
7
7
 
8
8
  # Deployment
9
9
 
10
- `createFetchHandler(plugin, options)` returns `(request: Request) => Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any framework route that accepts Fetch `Request` and `Response` objects. A framework with different request types needs only an adapter at its boundary; the package itself has no framework dependency.
10
+ `createFetchHandler(plugin, options)` returns `(request: Request) => Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any framework route that accepts Fetch `Request` and `Response` objects. For Node's `http`, Express and Nest, mount it with `createNodeHandler` (see [Node, Express and Nest](#node-express-and-nest)); the package itself has no framework dependency.
11
11
 
12
12
  The handler serves the archive for every `GET` request and runs a command for a matching `POST` request. It routes a command from the longest matching suffix of the request path, so the same handler works at the origin root, under `/crtr`, and behind a proxy that preserves that path.
13
13
 
@@ -23,7 +23,33 @@ Set `baseUrl` only when `request.url` is not the public URL, such as a proxy tha
23
23
 
24
24
  Pass `token` to require `Authorization: Bearer <token>` on archive and command requests. `--auth-env NAME` tells crtr which environment variable of the calling process holds that value. It does not configure the server. For a command an agent runs, the calling process is its broker, which reads the profile env store (`crtr profile env set <profile> --name NAME`) rather than your shell's exports. Omitting `token` serves without authentication and logs a warning when the handler is created.
25
25
 
26
- A capability provider registered in the directory passes `auth: {issuer, audience}` instead of `token`; passing both throws. `issuer` is the directory's issuer URL and `audience` is the provider's endpoint URL exactly as registered. Every command request's bearer must be an outbound token the directory minted. The handler verifies it with `@north-light/identity`: it fetches the JWKS from `<issuer>/.well-known/jwks.json`, caches it, and refetches once when a token names an unknown `kid`. It checks the signature, `iss`, `aud` (exact match, so a trailing slash differs) and `exp`. A failed check answers 401 `unauthorized`. Next the called leaf's `<plugin>:<group>` must be in the token's `scope`. If it is not, the handler answers 403 `scope_missing` with that string in both `field` and `received`. A command request under `auth` must also carry `Crtr-Request-Id`. Every leaf then gets `ctx.caller`: `{sub, grantee, clientId, scope, initiatingApp, act, jti, requestId, runId?}`. `act` and `initiatingApp` are for your audit; the kit authorizes nothing on them. The archive and `GET /versions` stay public under `auth`. For local development, point `issuer` at a test issuer that serves `/.well-known/jwks.json`.
26
+ A capability provider registered in the directory passes `auth: {issuer, audience, providerId}` instead of `token`; passing both throws. `providerId` is the provider's directory principal id — for a third-party app, its OAuth client id, bare or as `app:<id>`; 1–64 characters of `A-Z a-z 0-9 _ -`, validated when the handler is created. `issuer` is the directory's issuer URL and `audience` is the provider's endpoint URL exactly as registered. Every command request's bearer must be an outbound token the directory minted. The handler verifies it with `@north-light/identity`: it fetches the JWKS from `<issuer>/.well-known/jwks.json`, caches it, and refetches once when a token names an unknown `kid`. It checks the signature, `iss`, `aud` (exact match, so a trailing slash differs) and `exp`. A failed check answers 401 `unauthorized`. Next the called leaf's `<providerId>:<group>` must be in the token's `scope` (`<plugin name>:<group>` when `providerId` is not set, which is right only for a first-party provider whose name is its id; the directory always mints `<provider id>:<group>`, so a third-party app that omits `providerId` gets 403 on every call). If the scope is missing, the handler answers 403 `scope_missing` with that string in both `field` and `received`; when `providerId` is unset and the token carries the leaf's group under another id, `next` reads `Set auth.providerId to your app's client id (<that id>).` A command request under `auth` must also carry `Crtr-Request-Id`. Every leaf then gets `ctx.caller`: `{sub, grantee, clientId, scope, initiatingApp, act, jti, requestId, runId?}`. `act` and `initiatingApp` are for your audit; the kit authorizes nothing on them. The archive and `GET /versions` stay public under `auth`. For local development, point `issuer` at a test issuer that serves `/.well-known/jwks.json`.
27
+
28
+ ## Node, Express and Nest
29
+
30
+ `createNodeHandler(handler, { bodyLimit?, origin? })` adapts the Fetch handler to `(req, res) => Promise<void>` for Node's `http`, Express and Nest. It imports nothing from `node:`, and its request and response types are structural, so it needs none of those frameworks' types.
31
+
32
+ ```ts
33
+ import { createFetchHandler, createNodeHandler } from '@north-light/plugin';
34
+
35
+ const handler = createFetchHandler(plugin, {
36
+ auth: { issuer: ISSUER, audience: `${APP_ORIGIN}/plugin`, providerId: CLIENT_ID },
37
+ baseUrl: `${APP_ORIGIN}/plugin`,
38
+ requests,
39
+ });
40
+ const node = createNodeHandler(handler, { bodyLimit: 65536 });
41
+
42
+ // Express / Nest controller method:
43
+ // @All(['', '*path']) serve(@Req() req, @Res() res) { return node(req, res); }
44
+ // node:http:
45
+ // createServer((req, res) => void node(req, res));
46
+ ```
47
+
48
+ - **URL.** `originalUrl ?? url` resolved against `origin`, or against `http://<Host header>` when `origin` is not set. Pass `origin` when the public origin differs from the Host the app sees.
49
+ - **Body.** For a non-`GET`/`HEAD` request, `req.rawBody` (Nest with `rawBody: true`) is used when present; otherwise the stream is read. A `content-length` above `bodyLimit` (default 65536 bytes), or more bytes than that, answers 413 `invalid_request` before the handler runs, so before the token is checked.
50
+ - **Headers.** Every request header is passed on; a header Node delivers as an array is appended value by value. The response keeps repeated headers, `set-cookie` included.
51
+ - **Response.** The body is streamed chunk by chunk, waiting for `drain` when the socket is full. When the client disconnects, the request's `signal` aborts and the response stream is cancelled.
52
+ - **Failure.** A handler that throws answers 500 `handler_failed` in the kit's error shape; the adapter never rejects.
27
53
 
28
54
  ## Major versions
29
55
 
@@ -17,4 +17,4 @@ Start with the [package README](../../packages/crouter-plugin/README.md) for a c
17
17
  - [Deployment](deploying.md) — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
18
18
  - [Bundles and documents](bundles-and-memory.md) — generating an archive and shipping agent-facing documents.
19
19
 
20
- The package exports only `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. Those are the complete authoring surface.
20
+ The primary authoring surface is `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `createNodeHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. The package also exports `compareManifests`, `createCallerVerifier`, `postReceipt`, `ReceiptPostError` and the structural types `createNodeHandler` takes.
@@ -5,7 +5,7 @@ description: Serve plugin Fetch handlers with authentication, mount paths, and a
5
5
 
6
6
  # Deployment
7
7
 
8
- `createFetchHandler(plugin, options)` returns `(request: Request) => Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any framework route that accepts Fetch `Request` and `Response` objects. A framework with different request types needs only an adapter at its boundary; the package itself has no framework dependency.
8
+ `createFetchHandler(plugin, options)` returns `(request: Request) => Promise<Response>`. Use it directly in Cloudflare Workers, Bun, Deno, and any framework route that accepts Fetch `Request` and `Response` objects. For Node's `http`, Express and Nest, mount it with `createNodeHandler` (see [Node, Express and Nest](#node-express-and-nest)); the package itself has no framework dependency.
9
9
 
10
10
  The handler serves the archive for every `GET` request and runs a command for a matching `POST` request. It routes a command from the longest matching suffix of the request path, so the same handler works at the origin root, under `/crtr`, and behind a proxy that preserves that path.
11
11
 
@@ -21,7 +21,33 @@ Set `baseUrl` only when `request.url` is not the public URL, such as a proxy tha
21
21
 
22
22
  Pass `token` to require `Authorization: Bearer <token>` on archive and command requests. `--auth-env NAME` tells crtr which environment variable of the calling process holds that value. It does not configure the server. For a command an agent runs, the calling process is its broker, which reads the profile env store (`crtr profile env set <profile> --name NAME`) rather than your shell's exports. Omitting `token` serves without authentication and logs a warning when the handler is created.
23
23
 
24
- A capability provider registered in the directory passes `auth: {issuer, audience}` instead of `token`; passing both throws. `issuer` is the directory's issuer URL and `audience` is the provider's endpoint URL exactly as registered. Every command request's bearer must be an outbound token the directory minted. The handler verifies it with `@north-light/identity`: it fetches the JWKS from `<issuer>/.well-known/jwks.json`, caches it, and refetches once when a token names an unknown `kid`. It checks the signature, `iss`, `aud` (exact match, so a trailing slash differs) and `exp`. A failed check answers 401 `unauthorized`. Next the called leaf's `<plugin>:<group>` must be in the token's `scope`. If it is not, the handler answers 403 `scope_missing` with that string in both `field` and `received`. A command request under `auth` must also carry `Crtr-Request-Id`. Every leaf then gets `ctx.caller`: `{sub, grantee, clientId, scope, initiatingApp, act, jti, requestId, runId?}`. `act` and `initiatingApp` are for your audit; the kit authorizes nothing on them. The archive and `GET /versions` stay public under `auth`. For local development, point `issuer` at a test issuer that serves `/.well-known/jwks.json`.
24
+ A capability provider registered in the directory passes `auth: {issuer, audience, providerId}` instead of `token`; passing both throws. `providerId` is the provider's directory principal id — for a third-party app, its OAuth client id, bare or as `app:<id>`; 1–64 characters of `A-Z a-z 0-9 _ -`, validated when the handler is created. `issuer` is the directory's issuer URL and `audience` is the provider's endpoint URL exactly as registered. Every command request's bearer must be an outbound token the directory minted. The handler verifies it with `@north-light/identity`: it fetches the JWKS from `<issuer>/.well-known/jwks.json`, caches it, and refetches once when a token names an unknown `kid`. It checks the signature, `iss`, `aud` (exact match, so a trailing slash differs) and `exp`. A failed check answers 401 `unauthorized`. Next the called leaf's `<providerId>:<group>` must be in the token's `scope` (`<plugin name>:<group>` when `providerId` is not set, which is right only for a first-party provider whose name is its id; the directory always mints `<provider id>:<group>`, so a third-party app that omits `providerId` gets 403 on every call). If the scope is missing, the handler answers 403 `scope_missing` with that string in both `field` and `received`; when `providerId` is unset and the token carries the leaf's group under another id, `next` reads `Set auth.providerId to your app's client id (<that id>).` A command request under `auth` must also carry `Crtr-Request-Id`. Every leaf then gets `ctx.caller`: `{sub, grantee, clientId, scope, initiatingApp, act, jti, requestId, runId?}`. `act` and `initiatingApp` are for your audit; the kit authorizes nothing on them. The archive and `GET /versions` stay public under `auth`. For local development, point `issuer` at a test issuer that serves `/.well-known/jwks.json`.
25
+
26
+ ## Node, Express and Nest
27
+
28
+ `createNodeHandler(handler, { bodyLimit?, origin? })` adapts the Fetch handler to `(req, res) => Promise<void>` for Node's `http`, Express and Nest. It imports nothing from `node:`, and its request and response types are structural, so it needs none of those frameworks' types.
29
+
30
+ ```ts
31
+ import { createFetchHandler, createNodeHandler } from '@north-light/plugin';
32
+
33
+ const handler = createFetchHandler(plugin, {
34
+ auth: { issuer: ISSUER, audience: `${APP_ORIGIN}/plugin`, providerId: CLIENT_ID },
35
+ baseUrl: `${APP_ORIGIN}/plugin`,
36
+ requests,
37
+ });
38
+ const node = createNodeHandler(handler, { bodyLimit: 65536 });
39
+
40
+ // Express / Nest controller method:
41
+ // @All(['', '*path']) serve(@Req() req, @Res() res) { return node(req, res); }
42
+ // node:http:
43
+ // createServer((req, res) => void node(req, res));
44
+ ```
45
+
46
+ - **URL.** `originalUrl ?? url` resolved against `origin`, or against `http://<Host header>` when `origin` is not set. Pass `origin` when the public origin differs from the Host the app sees.
47
+ - **Body.** For a non-`GET`/`HEAD` request, `req.rawBody` (Nest with `rawBody: true`) is used when present; otherwise the stream is read. A `content-length` above `bodyLimit` (default 65536 bytes), or more bytes than that, answers 413 `invalid_request` before the handler runs, so before the token is checked.
48
+ - **Headers.** Every request header is passed on; a header Node delivers as an array is appended value by value. The response keeps repeated headers, `set-cookie` included.
49
+ - **Response.** The body is streamed chunk by chunk, waiting for `drain` when the socket is full. When the client disconnects, the request's `signal` aborts and the response stream is cancelled.
50
+ - **Failure.** A handler that throws answers 500 `handler_failed` in the kit's error shape; the adapter never rejects.
25
51
 
26
52
  ## Major versions
27
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.456",
3
+ "version": "0.3.457",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -137,7 +137,7 @@
137
137
  },
138
138
  "license": "UNLICENSED",
139
139
  "dependencies": {
140
- "@north-light/identity": "0.3.456",
140
+ "@north-light/identity": "0.3.457",
141
141
  "@earendil-works/pi-agent-core": "1.1.0",
142
142
  "@earendil-works/pi-ai": "1.1.0",
143
143
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.456",
3
+ "version": "0.3.457",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.456",
3
+ "version": "0.3.457",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.456",
9
+ "version": "0.3.457",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -18,7 +18,7 @@
18
18
  "@earendil-works/pi-ai": "1.1.0",
19
19
  "@earendil-works/pi-coding-agent": "1.1.0",
20
20
  "@earendil-works/pi-tui": "1.1.0",
21
- "@north-light/identity": "0.3.456",
21
+ "@north-light/identity": "0.3.457",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12095,17 +12095,17 @@
12095
12095
  },
12096
12096
  "packages/crouter-api": {
12097
12097
  "name": "@north-light/api",
12098
- "version": "0.3.456",
12098
+ "version": "0.3.457",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.456",
12103
+ "version": "0.3.457",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.456",
12108
+ "version": "0.3.457",
12109
12109
  "license": "UNLICENSED",
12110
12110
  "dependencies": {
12111
12111
  "jose": "^6.2.1"
@@ -12113,20 +12113,20 @@
12113
12113
  },
12114
12114
  "packages/crouter-plugin": {
12115
12115
  "name": "@north-light/plugin",
12116
- "version": "0.3.456",
12116
+ "version": "0.3.457",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.456",
12120
- "@north-light/identity": "0.3.456"
12119
+ "@north-light/api": "0.3.457",
12120
+ "@north-light/identity": "0.3.457"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.456",
12125
+ "version": "0.3.457",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.456",
12129
- "@north-light/identity": "0.3.456",
12128
+ "@north-light/api": "0.3.457",
12129
+ "@north-light/identity": "0.3.457",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {