@kubb/agent 4.27.4 → 4.28.1

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.
Files changed (26) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/server/chunks/nitro/nitro.mjs +278 -140
  3. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  4. package/.output/server/chunks/routes/api/health.get.mjs +5 -0
  5. package/.output/server/chunks/routes/api/health.get.mjs.map +1 -1
  6. package/.output/server/index.mjs +6 -1
  7. package/.output/server/index.mjs.map +1 -1
  8. package/.output/server/node_modules/anymatch/index.js +104 -0
  9. package/.output/server/node_modules/anymatch/package.json +48 -0
  10. package/.output/server/node_modules/chokidar/handler.js +632 -0
  11. package/.output/server/node_modules/chokidar/index.js +822 -0
  12. package/.output/server/node_modules/chokidar/package.json +63 -0
  13. package/.output/server/node_modules/normalize-path/index.js +35 -0
  14. package/.output/server/node_modules/normalize-path/package.json +77 -0
  15. package/.output/server/node_modules/picomatch/index.js +3 -0
  16. package/.output/server/node_modules/picomatch/lib/constants.js +179 -0
  17. package/.output/server/node_modules/picomatch/lib/parse.js +1091 -0
  18. package/.output/server/node_modules/picomatch/lib/picomatch.js +342 -0
  19. package/.output/server/node_modules/picomatch/lib/scan.js +391 -0
  20. package/.output/server/node_modules/picomatch/lib/utils.js +64 -0
  21. package/.output/server/node_modules/picomatch/package.json +81 -0
  22. package/.output/server/node_modules/readdirp/index.js +272 -0
  23. package/.output/server/node_modules/readdirp/package.json +66 -0
  24. package/.output/server/package.json +6 -1
  25. package/README.md +98 -42
  26. package/package.json +16 -23
@@ -0,0 +1,272 @@
1
+ import { lstat, readdir, realpath, stat } from 'node:fs/promises';
2
+ import { join as pjoin, relative as prelative, resolve as presolve, sep as psep } from 'node:path';
3
+ import { Readable } from 'node:stream';
4
+ export const EntryTypes = {
5
+ FILE_TYPE: 'files',
6
+ DIR_TYPE: 'directories',
7
+ FILE_DIR_TYPE: 'files_directories',
8
+ EVERYTHING_TYPE: 'all',
9
+ };
10
+ const defaultOptions = {
11
+ root: '.',
12
+ fileFilter: (_entryInfo) => true,
13
+ directoryFilter: (_entryInfo) => true,
14
+ type: EntryTypes.FILE_TYPE,
15
+ lstat: false,
16
+ depth: 2147483648,
17
+ alwaysStat: false,
18
+ highWaterMark: 4096,
19
+ };
20
+ Object.freeze(defaultOptions);
21
+ const RECURSIVE_ERROR_CODE = 'READDIRP_RECURSIVE_ERROR';
22
+ const NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP', RECURSIVE_ERROR_CODE]);
23
+ const ALL_TYPES = [
24
+ EntryTypes.DIR_TYPE,
25
+ EntryTypes.EVERYTHING_TYPE,
26
+ EntryTypes.FILE_DIR_TYPE,
27
+ EntryTypes.FILE_TYPE,
28
+ ];
29
+ const DIR_TYPES = new Set([
30
+ EntryTypes.DIR_TYPE,
31
+ EntryTypes.EVERYTHING_TYPE,
32
+ EntryTypes.FILE_DIR_TYPE,
33
+ ]);
34
+ const FILE_TYPES = new Set([
35
+ EntryTypes.EVERYTHING_TYPE,
36
+ EntryTypes.FILE_DIR_TYPE,
37
+ EntryTypes.FILE_TYPE,
38
+ ]);
39
+ const isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
40
+ const wantBigintFsStats = process.platform === 'win32';
41
+ const emptyFn = (_entryInfo) => true;
42
+ const normalizeFilter = (filter) => {
43
+ if (filter === undefined)
44
+ return emptyFn;
45
+ if (typeof filter === 'function')
46
+ return filter;
47
+ if (typeof filter === 'string') {
48
+ const fl = filter.trim();
49
+ return (entry) => entry.basename === fl;
50
+ }
51
+ if (Array.isArray(filter)) {
52
+ const trItems = filter.map((item) => item.trim());
53
+ return (entry) => trItems.some((f) => entry.basename === f);
54
+ }
55
+ return emptyFn;
56
+ };
57
+ /** Readable readdir stream, emitting new files as they're being listed. */
58
+ export class ReaddirpStream extends Readable {
59
+ parents;
60
+ reading;
61
+ parent;
62
+ _stat;
63
+ _maxDepth;
64
+ _wantsDir;
65
+ _wantsFile;
66
+ _wantsEverything;
67
+ _root;
68
+ _isDirent;
69
+ _statsProp;
70
+ _rdOptions;
71
+ _fileFilter;
72
+ _directoryFilter;
73
+ constructor(options = {}) {
74
+ super({
75
+ objectMode: true,
76
+ autoDestroy: true,
77
+ highWaterMark: options.highWaterMark,
78
+ });
79
+ const opts = { ...defaultOptions, ...options };
80
+ const { root, type } = opts;
81
+ this._fileFilter = normalizeFilter(opts.fileFilter);
82
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
83
+ const statMethod = opts.lstat ? lstat : stat;
84
+ // Use bigint stats if it's windows and stat() supports options (node 10+).
85
+ if (wantBigintFsStats) {
86
+ this._stat = (path) => statMethod(path, { bigint: true });
87
+ }
88
+ else {
89
+ this._stat = statMethod;
90
+ }
91
+ this._maxDepth =
92
+ opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
93
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
94
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
95
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
96
+ this._root = presolve(root);
97
+ this._isDirent = !opts.alwaysStat;
98
+ this._statsProp = this._isDirent ? 'dirent' : 'stats';
99
+ this._rdOptions = { encoding: 'utf8', withFileTypes: this._isDirent };
100
+ // Launch stream with one parent, the root dir.
101
+ this.parents = [this._exploreDir(root, 1)];
102
+ this.reading = false;
103
+ this.parent = undefined;
104
+ }
105
+ async _read(batch) {
106
+ if (this.reading)
107
+ return;
108
+ this.reading = true;
109
+ try {
110
+ while (!this.destroyed && batch > 0) {
111
+ const par = this.parent;
112
+ const fil = par && par.files;
113
+ if (fil && fil.length > 0) {
114
+ const { path, depth } = par;
115
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
116
+ const awaited = await Promise.all(slice);
117
+ for (const entry of awaited) {
118
+ if (!entry)
119
+ continue;
120
+ if (this.destroyed)
121
+ return;
122
+ const entryType = await this._getEntryType(entry);
123
+ if (entryType === 'directory' && this._directoryFilter(entry)) {
124
+ if (depth <= this._maxDepth) {
125
+ this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
126
+ }
127
+ if (this._wantsDir) {
128
+ this.push(entry);
129
+ batch--;
130
+ }
131
+ }
132
+ else if ((entryType === 'file' || this._includeAsFile(entry)) &&
133
+ this._fileFilter(entry)) {
134
+ if (this._wantsFile) {
135
+ this.push(entry);
136
+ batch--;
137
+ }
138
+ }
139
+ }
140
+ }
141
+ else {
142
+ const parent = this.parents.pop();
143
+ if (!parent) {
144
+ this.push(null);
145
+ break;
146
+ }
147
+ this.parent = await parent;
148
+ if (this.destroyed)
149
+ return;
150
+ }
151
+ }
152
+ }
153
+ catch (error) {
154
+ this.destroy(error);
155
+ }
156
+ finally {
157
+ this.reading = false;
158
+ }
159
+ }
160
+ async _exploreDir(path, depth) {
161
+ let files;
162
+ try {
163
+ files = await readdir(path, this._rdOptions);
164
+ }
165
+ catch (error) {
166
+ this._onError(error);
167
+ }
168
+ return { files, depth, path };
169
+ }
170
+ async _formatEntry(dirent, path) {
171
+ let entry;
172
+ const basename = this._isDirent ? dirent.name : dirent;
173
+ try {
174
+ const fullPath = presolve(pjoin(path, basename));
175
+ entry = { path: prelative(this._root, fullPath), fullPath, basename };
176
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
177
+ }
178
+ catch (err) {
179
+ this._onError(err);
180
+ return;
181
+ }
182
+ return entry;
183
+ }
184
+ _onError(err) {
185
+ if (isNormalFlowError(err) && !this.destroyed) {
186
+ this.emit('warn', err);
187
+ }
188
+ else {
189
+ this.destroy(err);
190
+ }
191
+ }
192
+ async _getEntryType(entry) {
193
+ // entry may be undefined, because a warning or an error were emitted
194
+ // and the statsProp is undefined
195
+ if (!entry && this._statsProp in entry) {
196
+ return '';
197
+ }
198
+ const stats = entry[this._statsProp];
199
+ if (stats.isFile())
200
+ return 'file';
201
+ if (stats.isDirectory())
202
+ return 'directory';
203
+ if (stats && stats.isSymbolicLink()) {
204
+ const full = entry.fullPath;
205
+ try {
206
+ const entryRealPath = await realpath(full);
207
+ const entryRealPathStats = await lstat(entryRealPath);
208
+ if (entryRealPathStats.isFile()) {
209
+ return 'file';
210
+ }
211
+ if (entryRealPathStats.isDirectory()) {
212
+ const len = entryRealPath.length;
213
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
214
+ const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
215
+ // @ts-ignore
216
+ recursiveError.code = RECURSIVE_ERROR_CODE;
217
+ return this._onError(recursiveError);
218
+ }
219
+ return 'directory';
220
+ }
221
+ }
222
+ catch (error) {
223
+ this._onError(error);
224
+ return '';
225
+ }
226
+ }
227
+ }
228
+ _includeAsFile(entry) {
229
+ const stats = entry && entry[this._statsProp];
230
+ return stats && this._wantsEverything && !stats.isDirectory();
231
+ }
232
+ }
233
+ /**
234
+ * Streaming version: Reads all files and directories in given root recursively.
235
+ * Consumes ~constant small amount of RAM.
236
+ * @param root Root directory
237
+ * @param options Options to specify root (start directory), filters and recursion depth
238
+ */
239
+ export function readdirp(root, options = {}) {
240
+ // @ts-ignore
241
+ let type = options.entryType || options.type;
242
+ if (type === 'both')
243
+ type = EntryTypes.FILE_DIR_TYPE; // backwards-compatibility
244
+ if (type)
245
+ options.type = type;
246
+ if (!root) {
247
+ throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)');
248
+ }
249
+ else if (typeof root !== 'string') {
250
+ throw new TypeError('readdirp: root argument must be a string. Usage: readdirp(root, options)');
251
+ }
252
+ else if (type && !ALL_TYPES.includes(type)) {
253
+ throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`);
254
+ }
255
+ options.root = root;
256
+ return new ReaddirpStream(options);
257
+ }
258
+ /**
259
+ * Promise version: Reads all files and directories in given root recursively.
260
+ * Compared to streaming version, will consume a lot of RAM e.g. when 1 million files are listed.
261
+ * @returns array of paths and their entry infos
262
+ */
263
+ export function readdirpPromise(root, options = {}) {
264
+ return new Promise((resolve, reject) => {
265
+ const files = [];
266
+ readdirp(root, options)
267
+ .on('data', (entry) => files.push(entry))
268
+ .on('end', () => resolve(files))
269
+ .on('error', (error) => reject(error));
270
+ });
271
+ }
272
+ export default readdirp;
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "readdirp",
3
+ "description": "Recursive version of fs.readdir with small RAM & CPU footprint",
4
+ "version": "5.0.0",
5
+ "homepage": "https://github.com/paulmillr/readdirp",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/paulmillr/readdirp.git"
9
+ },
10
+ "license": "MIT",
11
+ "bugs": {
12
+ "url": "https://github.com/paulmillr/readdirp/issues"
13
+ },
14
+ "author": "Thorsten Lorenz <thlorenz@gmx.de> (thlorenz.com)",
15
+ "contributors": [
16
+ "Thorsten Lorenz <thlorenz@gmx.de> (thlorenz.com)",
17
+ "Paul Miller (https://paulmillr.com)"
18
+ ],
19
+ "engines": {
20
+ "node": ">= 20.19.0"
21
+ },
22
+ "files": [
23
+ "index.js",
24
+ "index.d.ts",
25
+ "index.d.ts.map",
26
+ "index.js.map"
27
+ ],
28
+ "type": "module",
29
+ "main": "./index.js",
30
+ "module": "./index.js",
31
+ "types": "./index.d.ts",
32
+ "exports": {
33
+ ".": "./index.js"
34
+ },
35
+ "sideEffects": false,
36
+ "keywords": [
37
+ "recursive",
38
+ "fs",
39
+ "stream",
40
+ "streams",
41
+ "readdir",
42
+ "filesystem",
43
+ "find",
44
+ "filter"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "lint": "prettier --check index.ts test/index.test.js",
49
+ "format": "prettier --write index.ts test/index.test.js",
50
+ "test": "node test/index.test.js",
51
+ "test:coverage": "c8 node test/index.test.js"
52
+ },
53
+ "devDependencies": {
54
+ "@paulmillr/jsbt": "0.4.5",
55
+ "@types/node": "24.10.1",
56
+ "c8": "10.1.3",
57
+ "chai": "4.3.4",
58
+ "chai-subset": "1.6.0",
59
+ "prettier": "3.1.1",
60
+ "typescript": "5.9.2"
61
+ },
62
+ "funding": {
63
+ "type": "individual",
64
+ "url": "https://paulmillr.com/funding/"
65
+ }
66
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/agent-prod",
3
- "version": "4.27.4",
3
+ "version": "4.28.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
@@ -22,11 +22,13 @@
22
22
  "@sindresorhus/merge-streams": "4.0.0",
23
23
  "ajv": "8.17.1",
24
24
  "ajv-draft-04": "1.0.0",
25
+ "anymatch": "3.1.3",
25
26
  "balanced-match": "1.0.2",
26
27
  "brace-expansion": "1.1.12",
27
28
  "buffer-from": "1.1.2",
28
29
  "bytes": "3.0.0",
29
30
  "call-me-maybe": "1.0.2",
31
+ "chokidar": "5.0.0",
30
32
  "compute-gcd": "1.2.1",
31
33
  "compute-lcm": "1.1.2",
32
34
  "concat-map": "0.0.1",
@@ -78,6 +80,7 @@
78
80
  "natural-orderby": "5.0.0",
79
81
  "next-tick": "1.1.0",
80
82
  "node-fetch-h2": "2.3.0",
83
+ "normalize-path": "3.0.0",
81
84
  "npm-run-path": "6.0.0",
82
85
  "oas": "28.9.0",
83
86
  "oas-kit-common": "1.0.8",
@@ -90,9 +93,11 @@
90
93
  "path-key": "4.0.0",
91
94
  "path-to-regexp": "8.3.0",
92
95
  "picocolors": "1.1.1",
96
+ "picomatch": "2.3.1",
93
97
  "pretty-ms": "9.3.0",
94
98
  "range-parser": "1.2.0",
95
99
  "react-devtools-core": "6.1.5",
100
+ "readdirp": "5.0.0",
96
101
  "reftools": "1.1.9",
97
102
  "remeda": "2.33.6",
98
103
  "remove-undefined-objects": "7.0.0",
package/README.md CHANGED
@@ -13,7 +13,7 @@ Kubb Agent Server — HTTP server for code generation powered by [Nitro](https:/
13
13
  - 🔧 Easy integration with Kubb configuration
14
14
  - 📊 Health and info endpoints
15
15
  - 🔗 Bidirectional WebSocket with Kubb Studio
16
- - 🖥️ Machine binding — token locked to the registered machine via stable `machineId`
16
+ - 🖥️ Machine binding — token locked to the registered machine via stable `machineToken`
17
17
  - 💾 Automatic session caching for faster reconnects
18
18
  - ⚡ Production-ready
19
19
 
@@ -53,64 +53,72 @@ The server will be available at `http://localhost:3000`.
53
53
 
54
54
  ### Docker
55
55
 
56
- Run the agent standalone, mounting your config file into the container:
56
+ Run the agent standalone:
57
57
 
58
58
  ```bash
59
59
  docker run --env-file .env \
60
60
  -p 3000:3000 \
61
- -v ./kubb.config.ts:/kubb/agent/kubb.config.ts \
61
+ kubblabs/kubb-agent
62
+ ```
63
+
64
+ A default `kubb.config.ts` is baked into the image at `/kubb/agent/data/kubb.config.ts`. To use your own config, bind-mount it over the default:
65
+
66
+ ```bash
67
+ docker run --env-file .env \
68
+ -p 3000:3000 \
69
+ -v ./kubb.config.ts:/kubb/agent/data/kubb.config.ts \
62
70
  kubblabs/kubb-agent
63
71
  ```
64
72
 
65
73
  ### Docker Compose
66
74
 
67
- To run the full stack (Studio + Agent + Postgres), use the provided `docker-compose.yml`:
75
+ Use the provided `docker-compose.yaml` in `packages/agent`:
68
76
 
69
77
  ```yaml
70
78
  services:
71
79
  agent:
72
80
  image: kubblabs/kubb-agent:latest
73
- ports:
74
- - "3001:3000"
75
- env_file:
76
- - .env
81
+ container_name: kubb-agent
77
82
  environment:
78
- PORT: 3000
79
- KUBB_AGENT_ROOT: /kubb/agent
80
- KUBB_AGENT_CONFIG: kubb.config.ts
81
- KUBB_STUDIO_URL: http://kubb-studio:3000
83
+ PORT: 80
84
+ KUBB_AGENT_ROOT: /kubb/agent/data
85
+ KUBB_AGENT_CONFIG: ./kubb.config.ts
86
+ KUBB_STUDIO_URL: https://studio.kubb.dev
82
87
  volumes:
83
- - ./kubb.config.ts:/kubb/agent/kubb.config.ts
84
- depends_on:
85
- studio:
86
- condition: service_healthy
88
+ - agent_kv:/kubb/agent/.kubb/data
87
89
  restart: unless-stopped
88
90
  healthcheck:
89
- test: ["CMD-SHELL", "wget -qO- http://localhost:3000/api/health || exit 1"]
90
- interval: 30s
91
- timeout: 5s
92
- start_period: 10s
93
- retries: 3
91
+ test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"]
92
+ interval: 15s
93
+ timeout: 10s
94
+ start_period: 60s
95
+ retries: 5
96
+
97
+ volumes:
98
+ agent_kv:
94
99
  ```
95
100
 
96
101
  ```bash
97
102
  docker compose up
98
103
  ```
99
104
 
105
+ The `agent_kv` named volume persists the KV store (session cache, machine token) across container restarts and upgrades.
106
+
100
107
  ### Environment Variables
101
108
 
102
- | Variable | Default | Description |
103
- |---|---|---|
104
- | `KUBB_AGENT_CONFIG` | `kubb.config.ts` | Path to your Kubb config file. Relative paths are resolved against `KUBB_AGENT_ROOT`. |
105
- | `KUBB_AGENT_ROOT` | `/kubb/agent` (Docker) / `cwd` | Root directory for resolving relative paths. |
106
- | `PORT` | `3000` | Server port. |
107
- | `HOST` | `0.0.0.0` | Server host. |
108
- | `KUBB_STUDIO_URL` | `https://studio.kubb.dev` | Kubb Studio WebSocket URL. |
109
- | `KUBB_AGENT_TOKEN` | _(empty)_ | Authentication token for Studio. Required to connect. |
110
- | `KUBB_AGENT_NO_CACHE` | `false` | Set to `true` to disable session caching. |
111
- | `KUBB_AGENT_ALLOW_WRITE` | `false` | Set to `true` to allow writing generated files to disk. |
112
- | `KUBB_AGENT_ALLOW_ALL` | `false` | Set to `true` to grant all permissions (implies `KUBB_AGENT_ALLOW_WRITE=true`). |
113
- | `KUBB_AGENT_RETRY_TIMEOUT` | `30000` | Milliseconds to wait before retrying a failed Studio connection. |
109
+ | Variable | Default | Description |
110
+ |------------------------------|---|---|
111
+ | `KUBB_AGENT_CONFIG` | `kubb.config.ts` | Path to your Kubb config file. Relative paths are resolved against `KUBB_AGENT_ROOT`. |
112
+ | `KUBB_AGENT_ROOT` | `/kubb/agent` (Docker) / `cwd` | Root directory for resolving relative paths. |
113
+ | `PORT` | `3000` | Server port. |
114
+ | `HOST` | `0.0.0.0` | Server host. |
115
+ | `KUBB_STUDIO_URL` | `https://studio.kubb.dev` | Kubb Studio WebSocket URL. |
116
+ | `KUBB_AGENT_TOKEN` | _(empty)_ | Authentication token for Studio. Required to connect. |
117
+ | `KUBB_AGENT_NO_CACHE` | `false` | Set to `true` to disable session caching. |
118
+ | `KUBB_AGENT_ALLOW_WRITE` | `false` | Set to `true` to allow writing generated files to disk. |
119
+ | `KUBB_AGENT_ALLOW_ALL` | `false` | Set to `true` to grant all permissions (implies `KUBB_AGENT_ALLOW_WRITE=true`). |
120
+ | `KUBB_AGENT_RETRY_TIMEOUT` | `30000` | Milliseconds to wait before retrying a failed Studio connection. |
121
+ | `KUBB_STUDIO_SECRET` | _(empty)_ | Fixed machine name for stable identity across container restarts (e.g. Docker). When set, the `machineToken` is derived from this value instead of network interfaces and hostname. |
114
122
 
115
123
  ### Automatic .env Loading
116
124
 
@@ -146,9 +154,9 @@ The agent connects to Kubb Studio on startup when `KUBB_AGENT_TOKEN` is set.
146
154
 
147
155
  On startup the agent performs these steps before opening a WebSocket:
148
156
 
149
- 1. **Register** — calls `POST /api/agent/register` with a stable `machineId` derived from the machine's network interfaces and hostname (SHA-256). This binds the token to the machine. If registration fails the agent throws and does not continue.
150
- 2. **Create session** — calls `POST /api/agent/session/create` (includes `machineId` for verification) and receives a WebSocket URL.
151
- 3. **Connect** — opens a WebSocket to the returned URL (appends `?machineId=` for server-side verification).
157
+ 1. **Register** — calls `POST /api/agent/register` with a stable `machineToken` derived from the machine's network interfaces and hostname (SHA-256). This binds the token to the machine. Registration failure is non-fatal a warning is logged and the agent continues.
158
+ 2. **Create session** — calls `POST /api/agent/session/create` (includes `machineToken` for verification) and receives a WebSocket URL.
159
+ 3. **Connect** — opens a WebSocket to the returned URL using the `Authorization` header for authentication.
152
160
 
153
161
  ### Connection Features
154
162
 
@@ -160,7 +168,7 @@ On startup the agent performs these steps before opening a WebSocket:
160
168
 
161
169
  ### Session Caching
162
170
 
163
- Sessions are cached in `~/.kubb/config.json` for faster reconnects:
171
+ Sessions are cached in `./.kubb/data` (relative to the working directory, or `agent_kv` volume in Docker) for faster reconnects:
164
172
  - Tokens are hashed (non-reversible) for security
165
173
  - Sessions auto-expire after 24 hours
166
174
  - Use `--no-cache` flag to disable caching
@@ -219,7 +227,9 @@ Available `payload.type` values: `plugin:start`, `plugin:end`, `files:processing
219
227
  "payload": { "plugins": [] }
220
228
  }
221
229
  ```
222
- `payload` is optional. When omitted, the agent uses the config loaded from disk.
230
+ `payload` is optional. When omitted, the agent falls back to `kubb.config.studio.json` (a temporal config file next to `kubb.config.ts`), and then to the config loaded from disk.
231
+
232
+ The `payload` may also include an `input` field containing a raw OpenAPI / Swagger spec (YAML or JSON string). **This field is only honoured in sandbox mode** — outside of sandbox it is silently ignored for security reasons. See [Sandbox Mode](#sandbox-mode) below.
223
233
 
224
234
  **Connect Command** — requests agent info
225
235
  ```json
@@ -233,6 +243,56 @@ Available `payload.type` values: `plugin:start`, `plugin:end`, `files:processing
233
243
  }
234
244
  ```
235
245
 
246
+ **Pong** — sent by Studio in response to an agent ping
247
+ ```json
248
+ { "type": "pong" }
249
+ ```
250
+
251
+ **Status** — sent by Studio with information about connected agents
252
+ ```json
253
+ {
254
+ "type": "status",
255
+ "message": "...",
256
+ "connectedAgents": 1,
257
+ "agents": [
258
+ { "name": "...", "connectedAt": "..." }
259
+ ]
260
+ }
261
+ ```
262
+
263
+ ## Sandbox Mode
264
+
265
+ When Kubb Studio provisions a session for the **Sandbox Agent** (the shared agent hosted by Studio itself), it sets `isSandbox: true` in the session response. In sandbox mode the agent behaves differently from a user-owned agent:
266
+
267
+ | Behaviour | Normal agent | Sandbox agent |
268
+ |---|---|---|
269
+ | Write generated files to disk | ✅ (when `KUBB_AGENT_ALLOW_WRITE=true`) | ❌ Never |
270
+ | Read `input.path` from disk | ✅ | ✅ (falls back when no inline input supplied) |
271
+ | Accept inline `input` in generate payload | ❌ Ignored | ✅ |
272
+
273
+ ### Why no filesystem writes?
274
+
275
+ The sandbox agent runs in a shared, docker environment inside Kubb Studio. Allowing arbitrary disk writes would create security and isolation problems. Instead, `output.write` is always set to `false`, and the generated files are returned to Studio via the WebSocket `generation:end` event where the UI renders them.
276
+
277
+ ### Inline `input` in sandbox mode
278
+
279
+ Because the sandbox agent cannot read arbitrary files from disk, callers must supply the OpenAPI / Swagger spec content inline via the `input` field in the generate command payload:
280
+
281
+ ```json
282
+ {
283
+ "type": "command",
284
+ "command": "generate",
285
+ "payload": {
286
+ "input": "openapi: 3.0.0\ninfo:\n title: Pet Store\n version: 1.0.0\n...",
287
+ "plugins": [
288
+ { "name": "@kubb/plugin-ts", "options": {} }
289
+ ]
290
+ }
291
+ }
292
+ ```
293
+
294
+ The `input` value is treated as `InputData` (i.e. `{ data: "<content>" }`) and overrides the `input` from the loaded config for that generation cycle. **Outside of sandbox mode this field is ignored.**
295
+
236
296
  ## Configuration Example
237
297
 
238
298
  ### 1. Create a Kubb configuration file (`kubb.config.ts`):
@@ -263,7 +323,3 @@ npx kubb agent start
263
323
  ```
264
324
 
265
325
  You'll receive a stream of events as the code generation progresses.
266
-
267
- ## License
268
-
269
- MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/agent",
3
- "version": "4.27.4",
3
+ "version": "4.28.1",
4
4
  "description": "Agent server for Kubb, enabling HTTP-based access to code generation capabilities.",
5
5
  "keywords": [
6
6
  "agent",
@@ -23,15 +23,8 @@
23
23
  "sideEffects": false,
24
24
  "type": "module",
25
25
  "exports": {
26
- ".": {
27
- "require": "./dist/index.cjs",
28
- "import": "./dist/index.js"
29
- },
30
26
  "./package.json": "./package.json"
31
27
  },
32
- "main": "./dist/index.cjs",
33
- "module": "./dist/index.js",
34
- "types": "./dist/index.d.cts",
35
28
  "files": [
36
29
  ".output",
37
30
  "!/**/**.test.**",
@@ -44,21 +37,21 @@
44
37
  "picocolors": "^1.1.1",
45
38
  "string-argv": "^0.3.2",
46
39
  "ws": "^8.19.0",
47
- "@kubb/core": "4.27.4",
48
- "@kubb/plugin-client": "4.27.4",
49
- "@kubb/plugin-cypress": "4.27.4",
50
- "@kubb/plugin-faker": "4.27.4",
51
- "@kubb/plugin-mcp": "4.27.4",
52
- "@kubb/plugin-msw": "4.27.4",
53
- "@kubb/plugin-oas": "4.27.4",
54
- "@kubb/plugin-react-query": "4.27.4",
55
- "@kubb/plugin-redoc": "4.27.4",
56
- "@kubb/plugin-solid-query": "4.27.4",
57
- "@kubb/plugin-svelte-query": "4.27.4",
58
- "@kubb/plugin-swr": "4.27.4",
59
- "@kubb/plugin-ts": "4.27.4",
60
- "@kubb/plugin-vue-query": "4.27.4",
61
- "@kubb/plugin-zod": "4.27.4"
40
+ "@kubb/core": "4.28.1",
41
+ "@kubb/plugin-client": "4.28.1",
42
+ "@kubb/plugin-cypress": "4.28.1",
43
+ "@kubb/plugin-faker": "4.28.1",
44
+ "@kubb/plugin-mcp": "4.28.1",
45
+ "@kubb/plugin-msw": "4.28.1",
46
+ "@kubb/plugin-oas": "4.28.1",
47
+ "@kubb/plugin-react-query": "4.28.1",
48
+ "@kubb/plugin-redoc": "4.28.1",
49
+ "@kubb/plugin-solid-query": "4.28.1",
50
+ "@kubb/plugin-svelte-query": "4.28.1",
51
+ "@kubb/plugin-swr": "4.28.1",
52
+ "@kubb/plugin-ts": "4.28.1",
53
+ "@kubb/plugin-vue-query": "4.28.1",
54
+ "@kubb/plugin-zod": "4.28.1"
62
55
  },
63
56
  "devDependencies": {
64
57
  "@types/ws": "^8.18.1",