@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.
- package/.output/nitro.json +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +278 -140
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/health.get.mjs +5 -0
- package/.output/server/chunks/routes/api/health.get.mjs.map +1 -1
- package/.output/server/index.mjs +6 -1
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/node_modules/anymatch/index.js +104 -0
- package/.output/server/node_modules/anymatch/package.json +48 -0
- package/.output/server/node_modules/chokidar/handler.js +632 -0
- package/.output/server/node_modules/chokidar/index.js +822 -0
- package/.output/server/node_modules/chokidar/package.json +63 -0
- package/.output/server/node_modules/normalize-path/index.js +35 -0
- package/.output/server/node_modules/normalize-path/package.json +77 -0
- package/.output/server/node_modules/picomatch/index.js +3 -0
- package/.output/server/node_modules/picomatch/lib/constants.js +179 -0
- package/.output/server/node_modules/picomatch/lib/parse.js +1091 -0
- package/.output/server/node_modules/picomatch/lib/picomatch.js +342 -0
- package/.output/server/node_modules/picomatch/lib/scan.js +391 -0
- package/.output/server/node_modules/picomatch/lib/utils.js +64 -0
- package/.output/server/node_modules/picomatch/package.json +81 -0
- package/.output/server/node_modules/readdirp/index.js +272 -0
- package/.output/server/node_modules/readdirp/package.json +66 -0
- package/.output/server/package.json +6 -1
- package/README.md +98 -42
- 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.
|
|
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 `
|
|
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
|
|
56
|
+
Run the agent standalone:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
docker run --env-file .env \
|
|
60
60
|
-p 3000:3000 \
|
|
61
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
- "3001:3000"
|
|
75
|
-
env_file:
|
|
76
|
-
- .env
|
|
81
|
+
container_name: kubb-agent
|
|
77
82
|
environment:
|
|
78
|
-
PORT:
|
|
79
|
-
KUBB_AGENT_ROOT: /kubb/agent
|
|
80
|
-
KUBB_AGENT_CONFIG: kubb.config.ts
|
|
81
|
-
KUBB_STUDIO_URL:
|
|
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
|
-
-
|
|
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
|
|
90
|
-
interval:
|
|
91
|
-
timeout:
|
|
92
|
-
start_period:
|
|
93
|
-
retries:
|
|
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
|
|
103
|
-
|
|
104
|
-
| `KUBB_AGENT_CONFIG`
|
|
105
|
-
| `KUBB_AGENT_ROOT`
|
|
106
|
-
| `PORT`
|
|
107
|
-
| `HOST`
|
|
108
|
-
| `KUBB_STUDIO_URL`
|
|
109
|
-
| `KUBB_AGENT_TOKEN`
|
|
110
|
-
| `KUBB_AGENT_NO_CACHE`
|
|
111
|
-
| `KUBB_AGENT_ALLOW_WRITE`
|
|
112
|
-
| `KUBB_AGENT_ALLOW_ALL`
|
|
113
|
-
| `KUBB_AGENT_RETRY_TIMEOUT`
|
|
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 `
|
|
150
|
-
2. **Create session** — calls `POST /api/agent/session/create` (includes `
|
|
151
|
-
3. **Connect** — opens a WebSocket to the returned URL
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
48
|
-
"@kubb/plugin-client": "4.
|
|
49
|
-
"@kubb/plugin-cypress": "4.
|
|
50
|
-
"@kubb/plugin-faker": "4.
|
|
51
|
-
"@kubb/plugin-mcp": "4.
|
|
52
|
-
"@kubb/plugin-msw": "4.
|
|
53
|
-
"@kubb/plugin-oas": "4.
|
|
54
|
-
"@kubb/plugin-react-query": "4.
|
|
55
|
-
"@kubb/plugin-redoc": "4.
|
|
56
|
-
"@kubb/plugin-solid-query": "4.
|
|
57
|
-
"@kubb/plugin-svelte-query": "4.
|
|
58
|
-
"@kubb/plugin-swr": "4.
|
|
59
|
-
"@kubb/plugin-ts": "4.
|
|
60
|
-
"@kubb/plugin-vue-query": "4.
|
|
61
|
-
"@kubb/plugin-zod": "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",
|