openspecui 0.4.0 → 0.6.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,9 +9,11 @@ import { join } from "path";
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
10
  import { readFile as readFile$1, readdir, stat } from "node:fs/promises";
11
11
  import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
12
- import { existsSync, readFileSync, statSync, watch } from "node:fs";
13
- import { watch as watch$1 } from "fs";
14
- import { spawn } from "child_process";
12
+ import { existsSync, readFileSync, realpathSync, statSync, utimesSync } from "node:fs";
13
+ import { watch } from "fs";
14
+ import { exec, spawn } from "child_process";
15
+ import { promisify } from "util";
16
+ import { homedir } from "node:os";
15
17
  import { createServer as createServer$1 } from "node:net";
16
18
  import { fileURLToPath } from "node:url";
17
19
 
@@ -46,1011 +48,573 @@ var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(
46
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
47
49
 
48
50
  //#endregion
49
- //#region ../../node_modules/.pnpm/@hono+node-server@1.19.6_hono@4.10.6/node_modules/@hono/node-server/dist/index.mjs
50
- var RequestError = class extends Error {
51
- constructor(message, options) {
52
- super(message, options);
53
- this.name = "RequestError";
54
- }
55
- };
56
- var toRequestError = (e) => {
57
- if (e instanceof RequestError) return e;
58
- return new RequestError(e.message, { cause: e });
59
- };
60
- var GlobalRequest = global.Request;
61
- var Request$1 = class extends GlobalRequest {
62
- constructor(input, options) {
63
- if (typeof input === "object" && getRequestCache in input) input = input[getRequestCache]();
64
- if (typeof options?.body?.getReader !== "undefined") options.duplex ??= "half";
65
- super(input, options);
51
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
52
+ var util;
53
+ (function(util$1) {
54
+ util$1.assertEqual = (_) => {};
55
+ function assertIs(_arg) {}
56
+ util$1.assertIs = assertIs;
57
+ function assertNever(_x) {
58
+ throw new Error();
66
59
  }
67
- };
68
- var newHeadersFromIncoming = (incoming) => {
69
- const headerRecord = [];
70
- const rawHeaders = incoming.rawHeaders;
71
- for (let i = 0; i < rawHeaders.length; i += 2) {
72
- const { [i]: key, [i + 1]: value } = rawHeaders;
73
- if (key.charCodeAt(0) !== 58) headerRecord.push([key, value]);
60
+ util$1.assertNever = assertNever;
61
+ util$1.arrayToEnum = (items) => {
62
+ const obj = {};
63
+ for (const item of items) obj[item] = item;
64
+ return obj;
65
+ };
66
+ util$1.getValidEnumValues = (obj) => {
67
+ const validKeys = util$1.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
68
+ const filtered = {};
69
+ for (const k of validKeys) filtered[k] = obj[k];
70
+ return util$1.objectValues(filtered);
71
+ };
72
+ util$1.objectValues = (obj) => {
73
+ return util$1.objectKeys(obj).map(function(e) {
74
+ return obj[e];
75
+ });
76
+ };
77
+ util$1.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => {
78
+ const keys = [];
79
+ for (const key in object) if (Object.prototype.hasOwnProperty.call(object, key)) keys.push(key);
80
+ return keys;
81
+ };
82
+ util$1.find = (arr, checker) => {
83
+ for (const item of arr) if (checker(item)) return item;
84
+ };
85
+ util$1.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
86
+ function joinValues(array, separator = " | ") {
87
+ return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator);
74
88
  }
75
- return new Headers(headerRecord);
76
- };
77
- var wrapBodyStream = Symbol("wrapBodyStream");
78
- var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
79
- const init = {
80
- method,
81
- headers,
82
- signal: abortController.signal
89
+ util$1.joinValues = joinValues;
90
+ util$1.jsonStringifyReplacer = (_, value) => {
91
+ if (typeof value === "bigint") return value.toString();
92
+ return value;
83
93
  };
84
- if (method === "TRACE") {
85
- init.method = "GET";
86
- const req = new Request$1(url, init);
87
- Object.defineProperty(req, "method", { get() {
88
- return "TRACE";
89
- } });
90
- return req;
94
+ })(util || (util = {}));
95
+ var objectUtil;
96
+ (function(objectUtil$1) {
97
+ objectUtil$1.mergeShapes = (first, second) => {
98
+ return {
99
+ ...first,
100
+ ...second
101
+ };
102
+ };
103
+ })(objectUtil || (objectUtil = {}));
104
+ const ZodParsedType = util.arrayToEnum([
105
+ "string",
106
+ "nan",
107
+ "number",
108
+ "integer",
109
+ "float",
110
+ "boolean",
111
+ "date",
112
+ "bigint",
113
+ "symbol",
114
+ "function",
115
+ "undefined",
116
+ "null",
117
+ "array",
118
+ "object",
119
+ "unknown",
120
+ "promise",
121
+ "void",
122
+ "never",
123
+ "map",
124
+ "set"
125
+ ]);
126
+ const getParsedType = (data) => {
127
+ switch (typeof data) {
128
+ case "undefined": return ZodParsedType.undefined;
129
+ case "string": return ZodParsedType.string;
130
+ case "number": return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number;
131
+ case "boolean": return ZodParsedType.boolean;
132
+ case "function": return ZodParsedType.function;
133
+ case "bigint": return ZodParsedType.bigint;
134
+ case "symbol": return ZodParsedType.symbol;
135
+ case "object":
136
+ if (Array.isArray(data)) return ZodParsedType.array;
137
+ if (data === null) return ZodParsedType.null;
138
+ if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") return ZodParsedType.promise;
139
+ if (typeof Map !== "undefined" && data instanceof Map) return ZodParsedType.map;
140
+ if (typeof Set !== "undefined" && data instanceof Set) return ZodParsedType.set;
141
+ if (typeof Date !== "undefined" && data instanceof Date) return ZodParsedType.date;
142
+ return ZodParsedType.object;
143
+ default: return ZodParsedType.unknown;
91
144
  }
92
- if (!(method === "GET" || method === "HEAD")) if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) init.body = new ReadableStream({ start(controller) {
93
- controller.enqueue(incoming.rawBody);
94
- controller.close();
95
- } });
96
- else if (incoming[wrapBodyStream]) {
97
- let reader;
98
- init.body = new ReadableStream({ async pull(controller) {
99
- try {
100
- reader ||= Readable.toWeb(incoming).getReader();
101
- const { done, value } = await reader.read();
102
- if (done) controller.close();
103
- else controller.enqueue(value);
104
- } catch (error) {
105
- controller.error(error);
106
- }
107
- } });
108
- } else init.body = Readable.toWeb(incoming);
109
- return new Request$1(url, init);
110
145
  };
111
- var getRequestCache = Symbol("getRequestCache");
112
- var requestCache = Symbol("requestCache");
113
- var incomingKey = Symbol("incomingKey");
114
- var urlKey = Symbol("urlKey");
115
- var headersKey = Symbol("headersKey");
116
- var abortControllerKey = Symbol("abortControllerKey");
117
- var requestPrototype = {
118
- get method() {
119
- return this[incomingKey].method || "GET";
120
- },
121
- get url() {
122
- return this[urlKey];
123
- },
124
- get headers() {
125
- return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
126
- },
127
- [Symbol("getAbortController")]() {
128
- this[getRequestCache]();
129
- return this[abortControllerKey];
130
- },
131
- [getRequestCache]() {
132
- this[abortControllerKey] ||= new AbortController();
133
- return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
146
+
147
+ //#endregion
148
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
149
+ const ZodIssueCode = util.arrayToEnum([
150
+ "invalid_type",
151
+ "invalid_literal",
152
+ "custom",
153
+ "invalid_union",
154
+ "invalid_union_discriminator",
155
+ "invalid_enum_value",
156
+ "unrecognized_keys",
157
+ "invalid_arguments",
158
+ "invalid_return_type",
159
+ "invalid_date",
160
+ "invalid_string",
161
+ "too_small",
162
+ "too_big",
163
+ "invalid_intersection_types",
164
+ "not_multiple_of",
165
+ "not_finite"
166
+ ]);
167
+ var ZodError = class ZodError extends Error {
168
+ get errors() {
169
+ return this.issues;
134
170
  }
135
- };
136
- [
137
- "body",
138
- "bodyUsed",
139
- "cache",
140
- "credentials",
141
- "destination",
142
- "integrity",
143
- "mode",
144
- "redirect",
145
- "referrer",
146
- "referrerPolicy",
147
- "signal",
148
- "keepalive"
149
- ].forEach((k) => {
150
- Object.defineProperty(requestPrototype, k, { get() {
151
- return this[getRequestCache]()[k];
152
- } });
153
- });
154
- [
155
- "arrayBuffer",
156
- "blob",
157
- "clone",
158
- "formData",
159
- "json",
160
- "text"
161
- ].forEach((k) => {
162
- Object.defineProperty(requestPrototype, k, { value: function() {
163
- return this[getRequestCache]()[k]();
164
- } });
165
- });
166
- Object.setPrototypeOf(requestPrototype, Request$1.prototype);
167
- var newRequest = (incoming, defaultHostname) => {
168
- const req = Object.create(requestPrototype);
169
- req[incomingKey] = incoming;
170
- const incomingUrl = incoming.url || "";
171
- if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
172
- if (incoming instanceof Http2ServerRequest) throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
173
- try {
174
- req[urlKey] = new URL(incomingUrl).href;
175
- } catch (e) {
176
- throw new RequestError("Invalid absolute URL", { cause: e });
177
- }
178
- return req;
171
+ constructor(issues) {
172
+ super();
173
+ this.issues = [];
174
+ this.addIssue = (sub) => {
175
+ this.issues = [...this.issues, sub];
176
+ };
177
+ this.addIssues = (subs = []) => {
178
+ this.issues = [...this.issues, ...subs];
179
+ };
180
+ const actualProto = new.target.prototype;
181
+ if (Object.setPrototypeOf) Object.setPrototypeOf(this, actualProto);
182
+ else this.__proto__ = actualProto;
183
+ this.name = "ZodError";
184
+ this.issues = issues;
179
185
  }
180
- const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
181
- if (!host) throw new RequestError("Missing host header");
182
- let scheme;
183
- if (incoming instanceof Http2ServerRequest) {
184
- scheme = incoming.scheme;
185
- if (!(scheme === "http" || scheme === "https")) throw new RequestError("Unsupported scheme");
186
- } else scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
187
- const url = new URL(`${scheme}://${host}${incomingUrl}`);
188
- if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) throw new RequestError("Invalid host header");
189
- req[urlKey] = url.href;
190
- return req;
191
- };
192
- var responseCache = Symbol("responseCache");
193
- var getResponseCache = Symbol("getResponseCache");
194
- var cacheKey = Symbol("cache");
195
- var GlobalResponse = global.Response;
196
- var Response2 = class _Response {
197
- #body;
198
- #init;
199
- [getResponseCache]() {
200
- delete this[cacheKey];
201
- return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
202
- }
203
- constructor(body, init) {
204
- let headers;
205
- this.#body = body;
206
- if (init instanceof _Response) {
207
- const cachedGlobalResponse = init[responseCache];
208
- if (cachedGlobalResponse) {
209
- this.#init = cachedGlobalResponse;
210
- this[getResponseCache]();
211
- return;
212
- } else {
213
- this.#init = init.#init;
214
- headers = new Headers(init.#init.headers);
186
+ format(_mapper) {
187
+ const mapper = _mapper || function(issue) {
188
+ return issue.message;
189
+ };
190
+ const fieldErrors = { _errors: [] };
191
+ const processError = (error) => {
192
+ for (const issue of error.issues) if (issue.code === "invalid_union") issue.unionErrors.map(processError);
193
+ else if (issue.code === "invalid_return_type") processError(issue.returnTypeError);
194
+ else if (issue.code === "invalid_arguments") processError(issue.argumentsError);
195
+ else if (issue.path.length === 0) fieldErrors._errors.push(mapper(issue));
196
+ else {
197
+ let curr = fieldErrors;
198
+ let i = 0;
199
+ while (i < issue.path.length) {
200
+ const el = issue.path[i];
201
+ if (!(i === issue.path.length - 1)) curr[el] = curr[el] || { _errors: [] };
202
+ else {
203
+ curr[el] = curr[el] || { _errors: [] };
204
+ curr[el]._errors.push(mapper(issue));
205
+ }
206
+ curr = curr[el];
207
+ i++;
208
+ }
215
209
  }
216
- } else this.#init = init;
217
- if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
218
- headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
219
- this[cacheKey] = [
220
- init?.status || 200,
221
- body,
222
- headers
223
- ];
224
- }
225
- }
226
- get headers() {
227
- const cache = this[cacheKey];
228
- if (cache) {
229
- if (!(cache[2] instanceof Headers)) cache[2] = new Headers(cache[2]);
230
- return cache[2];
231
- }
232
- return this[getResponseCache]().headers;
210
+ };
211
+ processError(this);
212
+ return fieldErrors;
233
213
  }
234
- get status() {
235
- return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
214
+ static assert(value) {
215
+ if (!(value instanceof ZodError)) throw new Error(`Not a ZodError: ${value}`);
236
216
  }
237
- get ok() {
238
- const status = this.status;
239
- return status >= 200 && status < 300;
217
+ toString() {
218
+ return this.message;
240
219
  }
241
- };
242
- [
243
- "body",
244
- "bodyUsed",
245
- "redirected",
246
- "statusText",
247
- "trailers",
248
- "type",
249
- "url"
250
- ].forEach((k) => {
251
- Object.defineProperty(Response2.prototype, k, { get() {
252
- return this[getResponseCache]()[k];
253
- } });
254
- });
255
- [
256
- "arrayBuffer",
257
- "blob",
258
- "clone",
259
- "formData",
260
- "json",
261
- "text"
262
- ].forEach((k) => {
263
- Object.defineProperty(Response2.prototype, k, { value: function() {
264
- return this[getResponseCache]()[k]();
265
- } });
266
- });
267
- Object.setPrototypeOf(Response2, GlobalResponse);
268
- Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
269
- async function readWithoutBlocking(readPromise) {
270
- return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
271
- }
272
- function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
273
- const cancel = (error) => {
274
- reader.cancel(error).catch(() => {});
275
- };
276
- writable.on("close", cancel);
277
- writable.on("error", cancel);
278
- (currentReadPromise ?? reader.read()).then(flow, handleStreamError);
279
- return reader.closed.finally(() => {
280
- writable.off("close", cancel);
281
- writable.off("error", cancel);
282
- });
283
- function handleStreamError(error) {
284
- if (error) writable.destroy(error);
220
+ get message() {
221
+ return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
285
222
  }
286
- function onDrain() {
287
- reader.read().then(flow, handleStreamError);
223
+ get isEmpty() {
224
+ return this.issues.length === 0;
288
225
  }
289
- function flow({ done, value }) {
290
- try {
291
- if (done) writable.end();
292
- else if (!writable.write(value)) writable.once("drain", onDrain);
293
- else return reader.read().then(flow, handleStreamError);
294
- } catch (e) {
295
- handleStreamError(e);
296
- }
226
+ flatten(mapper = (issue) => issue.message) {
227
+ const fieldErrors = {};
228
+ const formErrors = [];
229
+ for (const sub of this.issues) if (sub.path.length > 0) {
230
+ const firstEl = sub.path[0];
231
+ fieldErrors[firstEl] = fieldErrors[firstEl] || [];
232
+ fieldErrors[firstEl].push(mapper(sub));
233
+ } else formErrors.push(mapper(sub));
234
+ return {
235
+ formErrors,
236
+ fieldErrors
237
+ };
297
238
  }
298
- }
299
- function writeFromReadableStream(stream, writable) {
300
- if (stream.locked) throw new TypeError("ReadableStream is locked.");
301
- else if (writable.destroyed) return;
302
- return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
303
- }
304
- var buildOutgoingHttpHeaders = (headers) => {
305
- const res = {};
306
- if (!(headers instanceof Headers)) headers = new Headers(headers ?? void 0);
307
- const cookies = [];
308
- for (const [k, v] of headers) if (k === "set-cookie") cookies.push(v);
309
- else res[k] = v;
310
- if (cookies.length > 0) res["set-cookie"] = cookies;
311
- res["content-type"] ??= "text/plain; charset=UTF-8";
312
- return res;
313
- };
314
- var X_ALREADY_SENT = "x-hono-already-sent";
315
- var webFetch = global.fetch;
316
- if (typeof global.crypto === "undefined") global.crypto = crypto;
317
- global.fetch = (info, init) => {
318
- init = {
319
- compress: false,
320
- ...init
321
- };
322
- return webFetch(info, init);
323
- };
324
- var outgoingEnded = Symbol("outgoingEnded");
325
- var handleRequestError = () => new Response(null, { status: 400 });
326
- var handleFetchError = (e) => new Response(null, { status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500 });
327
- var handleResponseError = (e, outgoing) => {
328
- const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
329
- if (err.code === "ERR_STREAM_PREMATURE_CLOSE") console.info("The user aborted a request.");
330
- else {
331
- console.error(e);
332
- if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "text/plain" });
333
- outgoing.end(`Error: ${err.message}`);
334
- outgoing.destroy(err);
239
+ get formErrors() {
240
+ return this.flatten();
335
241
  }
336
242
  };
337
- var flushHeaders = (outgoing) => {
338
- if ("flushHeaders" in outgoing && outgoing.writable) outgoing.flushHeaders();
339
- };
340
- var responseViaCache = async (res, outgoing) => {
341
- let [status, body, header] = res[cacheKey];
342
- if (header instanceof Headers) header = buildOutgoingHttpHeaders(header);
343
- if (typeof body === "string") header["Content-Length"] = Buffer.byteLength(body);
344
- else if (body instanceof Uint8Array) header["Content-Length"] = body.byteLength;
345
- else if (body instanceof Blob) header["Content-Length"] = body.size;
346
- outgoing.writeHead(status, header);
347
- if (typeof body === "string" || body instanceof Uint8Array) outgoing.end(body);
348
- else if (body instanceof Blob) outgoing.end(new Uint8Array(await body.arrayBuffer()));
349
- else {
350
- flushHeaders(outgoing);
351
- await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
352
- }
353
- outgoing[outgoingEnded]?.();
243
+ ZodError.create = (issues) => {
244
+ return new ZodError(issues);
354
245
  };
355
- var isPromise$1 = (res) => typeof res.then === "function";
356
- var responseViaResponseObject = async (res, outgoing, options = {}) => {
357
- if (isPromise$1(res)) if (options.errorHandler) try {
358
- res = await res;
359
- } catch (err) {
360
- const errRes = await options.errorHandler(err);
361
- if (!errRes) return;
362
- res = errRes;
363
- }
364
- else res = await res.catch(handleFetchError);
365
- if (cacheKey in res) return responseViaCache(res, outgoing);
366
- const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
367
- if (res.body) {
368
- const reader = res.body.getReader();
369
- const values = [];
370
- let done = false;
371
- let currentReadPromise = void 0;
372
- if (resHeaderRecord["transfer-encoding"] !== "chunked") {
373
- let maxReadCount = 2;
374
- for (let i = 0; i < maxReadCount; i++) {
375
- currentReadPromise ||= reader.read();
376
- const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
377
- console.error(e);
378
- done = true;
379
- });
380
- if (!chunk) {
381
- if (i === 1) {
382
- await new Promise((resolve$2) => setTimeout(resolve$2));
383
- maxReadCount = 3;
384
- continue;
385
- }
386
- break;
387
- }
388
- currentReadPromise = void 0;
389
- if (chunk.value) values.push(chunk.value);
390
- if (chunk.done) {
391
- done = true;
392
- break;
393
- }
394
- }
395
- if (done && !("content-length" in resHeaderRecord)) resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
396
- }
397
- outgoing.writeHead(res.status, resHeaderRecord);
398
- values.forEach((value) => {
399
- outgoing.write(value);
400
- });
401
- if (done) outgoing.end();
402
- else {
403
- if (values.length === 0) flushHeaders(outgoing);
404
- await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
405
- }
406
- } else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
407
- outgoing.writeHead(res.status, resHeaderRecord);
408
- outgoing.end();
409
- }
410
- outgoing[outgoingEnded]?.();
411
- };
412
- var getRequestListener = (fetchCallback, options = {}) => {
413
- const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
414
- if (options.overrideGlobalObjects !== false && global.Request !== Request$1) {
415
- Object.defineProperty(global, "Request", { value: Request$1 });
416
- Object.defineProperty(global, "Response", { value: Response2 });
246
+
247
+ //#endregion
248
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
249
+ const errorMap = (issue, _ctx) => {
250
+ let message;
251
+ switch (issue.code) {
252
+ case ZodIssueCode.invalid_type:
253
+ if (issue.received === ZodParsedType.undefined) message = "Required";
254
+ else message = `Expected ${issue.expected}, received ${issue.received}`;
255
+ break;
256
+ case ZodIssueCode.invalid_literal:
257
+ message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`;
258
+ break;
259
+ case ZodIssueCode.unrecognized_keys:
260
+ message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`;
261
+ break;
262
+ case ZodIssueCode.invalid_union:
263
+ message = `Invalid input`;
264
+ break;
265
+ case ZodIssueCode.invalid_union_discriminator:
266
+ message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`;
267
+ break;
268
+ case ZodIssueCode.invalid_enum_value:
269
+ message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`;
270
+ break;
271
+ case ZodIssueCode.invalid_arguments:
272
+ message = `Invalid function arguments`;
273
+ break;
274
+ case ZodIssueCode.invalid_return_type:
275
+ message = `Invalid function return type`;
276
+ break;
277
+ case ZodIssueCode.invalid_date:
278
+ message = `Invalid date`;
279
+ break;
280
+ case ZodIssueCode.invalid_string:
281
+ if (typeof issue.validation === "object") if ("includes" in issue.validation) {
282
+ message = `Invalid input: must include "${issue.validation.includes}"`;
283
+ if (typeof issue.validation.position === "number") message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`;
284
+ } else if ("startsWith" in issue.validation) message = `Invalid input: must start with "${issue.validation.startsWith}"`;
285
+ else if ("endsWith" in issue.validation) message = `Invalid input: must end with "${issue.validation.endsWith}"`;
286
+ else util.assertNever(issue.validation);
287
+ else if (issue.validation !== "regex") message = `Invalid ${issue.validation}`;
288
+ else message = "Invalid";
289
+ break;
290
+ case ZodIssueCode.too_small:
291
+ if (issue.type === "array") message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;
292
+ else if (issue.type === "string") message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
293
+ else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
294
+ else if (issue.type === "bigint") message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
295
+ else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;
296
+ else message = "Invalid input";
297
+ break;
298
+ case ZodIssueCode.too_big:
299
+ if (issue.type === "array") message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;
300
+ else if (issue.type === "string") message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;
301
+ else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
302
+ else if (issue.type === "bigint") message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
303
+ else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;
304
+ else message = "Invalid input";
305
+ break;
306
+ case ZodIssueCode.custom:
307
+ message = `Invalid input`;
308
+ break;
309
+ case ZodIssueCode.invalid_intersection_types:
310
+ message = `Intersection results could not be merged`;
311
+ break;
312
+ case ZodIssueCode.not_multiple_of:
313
+ message = `Number must be a multiple of ${issue.multipleOf}`;
314
+ break;
315
+ case ZodIssueCode.not_finite:
316
+ message = "Number must be finite";
317
+ break;
318
+ default:
319
+ message = _ctx.defaultError;
320
+ util.assertNever(issue);
417
321
  }
418
- return async (incoming, outgoing) => {
419
- let res, req;
420
- try {
421
- req = newRequest(incoming, options.hostname);
422
- let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
423
- if (!incomingEnded) {
424
- incoming[wrapBodyStream] = true;
425
- incoming.on("end", () => {
426
- incomingEnded = true;
427
- });
428
- if (incoming instanceof Http2ServerRequest) outgoing[outgoingEnded] = () => {
429
- if (!incomingEnded) setTimeout(() => {
430
- if (!incomingEnded) setTimeout(() => {
431
- incoming.destroy();
432
- outgoing.destroy();
433
- });
434
- });
435
- };
436
- }
437
- outgoing.on("close", () => {
438
- if (req[abortControllerKey]) {
439
- if (incoming.errored) req[abortControllerKey].abort(incoming.errored.toString());
440
- else if (!outgoing.writableFinished) req[abortControllerKey].abort("Client connection prematurely closed.");
441
- }
442
- if (!incomingEnded) setTimeout(() => {
443
- if (!incomingEnded) setTimeout(() => {
444
- incoming.destroy();
445
- });
446
- });
447
- });
448
- res = fetchCallback(req, {
449
- incoming,
450
- outgoing
451
- });
452
- if (cacheKey in res) return responseViaCache(res, outgoing);
453
- } catch (e) {
454
- if (!res) if (options.errorHandler) {
455
- res = await options.errorHandler(req ? e : toRequestError(e));
456
- if (!res) return;
457
- } else if (!req) res = handleRequestError();
458
- else res = handleFetchError(e);
459
- else return handleResponseError(e, outgoing);
460
- }
461
- try {
462
- return await responseViaResponseObject(res, outgoing, options);
463
- } catch (e) {
464
- return handleResponseError(e, outgoing);
465
- }
466
- };
467
- };
468
- var createAdaptorServer = (options) => {
469
- const fetchCallback = options.fetch;
470
- const requestListener = getRequestListener(fetchCallback, {
471
- hostname: options.hostname,
472
- overrideGlobalObjects: options.overrideGlobalObjects,
473
- autoCleanupIncoming: options.autoCleanupIncoming
474
- });
475
- return (options.createServer || createServer)(options.serverOptions || {}, requestListener);
476
- };
477
- var serve = (options, listeningListener) => {
478
- const server = createAdaptorServer(options);
479
- server.listen(options?.port ?? 3e3, options.hostname, () => {
480
- const serverInfo = server.address();
481
- listeningListener && listeningListener(serverInfo);
482
- });
483
- return server;
322
+ return { message };
484
323
  };
324
+ var en_default = errorMap;
485
325
 
486
326
  //#endregion
487
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
488
- var util;
489
- (function(util$1) {
490
- util$1.assertEqual = (_) => {};
491
- function assertIs(_arg) {}
492
- util$1.assertIs = assertIs;
493
- function assertNever(_x) {
494
- throw new Error();
495
- }
496
- util$1.assertNever = assertNever;
497
- util$1.arrayToEnum = (items) => {
498
- const obj = {};
499
- for (const item of items) obj[item] = item;
500
- return obj;
501
- };
502
- util$1.getValidEnumValues = (obj) => {
503
- const validKeys = util$1.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
504
- const filtered = {};
505
- for (const k of validKeys) filtered[k] = obj[k];
506
- return util$1.objectValues(filtered);
507
- };
508
- util$1.objectValues = (obj) => {
509
- return util$1.objectKeys(obj).map(function(e) {
510
- return obj[e];
511
- });
327
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
328
+ let overrideErrorMap = en_default;
329
+ function getErrorMap() {
330
+ return overrideErrorMap;
331
+ }
332
+
333
+ //#endregion
334
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
335
+ const makeIssue = (params) => {
336
+ const { data, path: path$1, errorMaps, issueData } = params;
337
+ const fullPath = [...path$1, ...issueData.path || []];
338
+ const fullIssue = {
339
+ ...issueData,
340
+ path: fullPath
512
341
  };
513
- util$1.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => {
514
- const keys = [];
515
- for (const key in object) if (Object.prototype.hasOwnProperty.call(object, key)) keys.push(key);
516
- return keys;
342
+ if (issueData.message !== void 0) return {
343
+ ...issueData,
344
+ path: fullPath,
345
+ message: issueData.message
517
346
  };
518
- util$1.find = (arr, checker) => {
519
- for (const item of arr) if (checker(item)) return item;
347
+ let errorMessage = "";
348
+ const maps = errorMaps.filter((m) => !!m).slice().reverse();
349
+ for (const map of maps) errorMessage = map(fullIssue, {
350
+ data,
351
+ defaultError: errorMessage
352
+ }).message;
353
+ return {
354
+ ...issueData,
355
+ path: fullPath,
356
+ message: errorMessage
520
357
  };
521
- util$1.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
522
- function joinValues(array, separator = " | ") {
523
- return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator);
358
+ };
359
+ function addIssueToContext(ctx, issueData) {
360
+ const overrideMap = getErrorMap();
361
+ const issue = makeIssue({
362
+ issueData,
363
+ data: ctx.data,
364
+ path: ctx.path,
365
+ errorMaps: [
366
+ ctx.common.contextualErrorMap,
367
+ ctx.schemaErrorMap,
368
+ overrideMap,
369
+ overrideMap === en_default ? void 0 : en_default
370
+ ].filter((x) => !!x)
371
+ });
372
+ ctx.common.issues.push(issue);
373
+ }
374
+ var ParseStatus = class ParseStatus {
375
+ constructor() {
376
+ this.value = "valid";
524
377
  }
525
- util$1.joinValues = joinValues;
526
- util$1.jsonStringifyReplacer = (_, value) => {
527
- if (typeof value === "bigint") return value.toString();
528
- return value;
529
- };
530
- })(util || (util = {}));
531
- var objectUtil;
532
- (function(objectUtil$1) {
533
- objectUtil$1.mergeShapes = (first, second) => {
534
- return {
535
- ...first,
536
- ...second
537
- };
538
- };
539
- })(objectUtil || (objectUtil = {}));
540
- const ZodParsedType = util.arrayToEnum([
541
- "string",
542
- "nan",
543
- "number",
544
- "integer",
545
- "float",
546
- "boolean",
547
- "date",
548
- "bigint",
549
- "symbol",
550
- "function",
551
- "undefined",
552
- "null",
553
- "array",
554
- "object",
555
- "unknown",
556
- "promise",
557
- "void",
558
- "never",
559
- "map",
560
- "set"
561
- ]);
562
- const getParsedType = (data) => {
563
- switch (typeof data) {
564
- case "undefined": return ZodParsedType.undefined;
565
- case "string": return ZodParsedType.string;
566
- case "number": return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number;
567
- case "boolean": return ZodParsedType.boolean;
568
- case "function": return ZodParsedType.function;
569
- case "bigint": return ZodParsedType.bigint;
570
- case "symbol": return ZodParsedType.symbol;
571
- case "object":
572
- if (Array.isArray(data)) return ZodParsedType.array;
573
- if (data === null) return ZodParsedType.null;
574
- if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") return ZodParsedType.promise;
575
- if (typeof Map !== "undefined" && data instanceof Map) return ZodParsedType.map;
576
- if (typeof Set !== "undefined" && data instanceof Set) return ZodParsedType.set;
577
- if (typeof Date !== "undefined" && data instanceof Date) return ZodParsedType.date;
578
- return ZodParsedType.object;
579
- default: return ZodParsedType.unknown;
378
+ dirty() {
379
+ if (this.value === "valid") this.value = "dirty";
380
+ }
381
+ abort() {
382
+ if (this.value !== "aborted") this.value = "aborted";
383
+ }
384
+ static mergeArray(status, results) {
385
+ const arrayValue = [];
386
+ for (const s of results) {
387
+ if (s.status === "aborted") return INVALID;
388
+ if (s.status === "dirty") status.dirty();
389
+ arrayValue.push(s.value);
390
+ }
391
+ return {
392
+ status: status.value,
393
+ value: arrayValue
394
+ };
395
+ }
396
+ static async mergeObjectAsync(status, pairs) {
397
+ const syncPairs = [];
398
+ for (const pair of pairs) {
399
+ const key = await pair.key;
400
+ const value = await pair.value;
401
+ syncPairs.push({
402
+ key,
403
+ value
404
+ });
405
+ }
406
+ return ParseStatus.mergeObjectSync(status, syncPairs);
407
+ }
408
+ static mergeObjectSync(status, pairs) {
409
+ const finalObject = {};
410
+ for (const pair of pairs) {
411
+ const { key, value } = pair;
412
+ if (key.status === "aborted") return INVALID;
413
+ if (value.status === "aborted") return INVALID;
414
+ if (key.status === "dirty") status.dirty();
415
+ if (value.status === "dirty") status.dirty();
416
+ if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) finalObject[key.value] = value.value;
417
+ }
418
+ return {
419
+ status: status.value,
420
+ value: finalObject
421
+ };
580
422
  }
581
423
  };
424
+ const INVALID = Object.freeze({ status: "aborted" });
425
+ const DIRTY = (value) => ({
426
+ status: "dirty",
427
+ value
428
+ });
429
+ const OK = (value) => ({
430
+ status: "valid",
431
+ value
432
+ });
433
+ const isAborted = (x) => x.status === "aborted";
434
+ const isDirty = (x) => x.status === "dirty";
435
+ const isValid = (x) => x.status === "valid";
436
+ const isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
582
437
 
583
438
  //#endregion
584
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
585
- const ZodIssueCode = util.arrayToEnum([
586
- "invalid_type",
587
- "invalid_literal",
588
- "custom",
589
- "invalid_union",
590
- "invalid_union_discriminator",
591
- "invalid_enum_value",
592
- "unrecognized_keys",
593
- "invalid_arguments",
594
- "invalid_return_type",
595
- "invalid_date",
596
- "invalid_string",
597
- "too_small",
598
- "too_big",
599
- "invalid_intersection_types",
600
- "not_multiple_of",
601
- "not_finite"
602
- ]);
603
- var ZodError = class ZodError extends Error {
604
- get errors() {
605
- return this.issues;
439
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
440
+ var errorUtil;
441
+ (function(errorUtil$1) {
442
+ errorUtil$1.errToObj = (message) => typeof message === "string" ? { message } : message || {};
443
+ errorUtil$1.toString = (message) => typeof message === "string" ? message : message?.message;
444
+ })(errorUtil || (errorUtil = {}));
445
+
446
+ //#endregion
447
+ //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
448
+ var ParseInputLazyPath = class {
449
+ constructor(parent, value, path$1, key) {
450
+ this._cachedPath = [];
451
+ this.parent = parent;
452
+ this.data = value;
453
+ this._path = path$1;
454
+ this._key = key;
606
455
  }
607
- constructor(issues) {
608
- super();
609
- this.issues = [];
610
- this.addIssue = (sub) => {
611
- this.issues = [...this.issues, sub];
612
- };
613
- this.addIssues = (subs = []) => {
614
- this.issues = [...this.issues, ...subs];
615
- };
616
- const actualProto = new.target.prototype;
617
- if (Object.setPrototypeOf) Object.setPrototypeOf(this, actualProto);
618
- else this.__proto__ = actualProto;
619
- this.name = "ZodError";
620
- this.issues = issues;
456
+ get path() {
457
+ if (!this._cachedPath.length) if (Array.isArray(this._key)) this._cachedPath.push(...this._path, ...this._key);
458
+ else this._cachedPath.push(...this._path, this._key);
459
+ return this._cachedPath;
621
460
  }
622
- format(_mapper) {
623
- const mapper = _mapper || function(issue) {
624
- return issue.message;
625
- };
626
- const fieldErrors = { _errors: [] };
627
- const processError = (error) => {
628
- for (const issue of error.issues) if (issue.code === "invalid_union") issue.unionErrors.map(processError);
629
- else if (issue.code === "invalid_return_type") processError(issue.returnTypeError);
630
- else if (issue.code === "invalid_arguments") processError(issue.argumentsError);
631
- else if (issue.path.length === 0) fieldErrors._errors.push(mapper(issue));
632
- else {
633
- let curr = fieldErrors;
634
- let i = 0;
635
- while (i < issue.path.length) {
636
- const el = issue.path[i];
637
- if (!(i === issue.path.length - 1)) curr[el] = curr[el] || { _errors: [] };
638
- else {
639
- curr[el] = curr[el] || { _errors: [] };
640
- curr[el]._errors.push(mapper(issue));
641
- }
642
- curr = curr[el];
643
- i++;
644
- }
461
+ };
462
+ const handleResult = (ctx, result) => {
463
+ if (isValid(result)) return {
464
+ success: true,
465
+ data: result.value
466
+ };
467
+ else {
468
+ if (!ctx.common.issues.length) throw new Error("Validation failed but no issues detected.");
469
+ return {
470
+ success: false,
471
+ get error() {
472
+ if (this._error) return this._error;
473
+ this._error = new ZodError(ctx.common.issues);
474
+ return this._error;
645
475
  }
646
476
  };
647
- processError(this);
648
- return fieldErrors;
649
477
  }
650
- static assert(value) {
651
- if (!(value instanceof ZodError)) throw new Error(`Not a ZodError: ${value}`);
652
- }
653
- toString() {
654
- return this.message;
478
+ };
479
+ function processCreateParams(params) {
480
+ if (!params) return {};
481
+ const { errorMap: errorMap$1, invalid_type_error, required_error, description } = params;
482
+ if (errorMap$1 && (invalid_type_error || required_error)) throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
483
+ if (errorMap$1) return {
484
+ errorMap: errorMap$1,
485
+ description
486
+ };
487
+ const customMap = (iss, ctx) => {
488
+ const { message } = params;
489
+ if (iss.code === "invalid_enum_value") return { message: message ?? ctx.defaultError };
490
+ if (typeof ctx.data === "undefined") return { message: message ?? required_error ?? ctx.defaultError };
491
+ if (iss.code !== "invalid_type") return { message: ctx.defaultError };
492
+ return { message: message ?? invalid_type_error ?? ctx.defaultError };
493
+ };
494
+ return {
495
+ errorMap: customMap,
496
+ description
497
+ };
498
+ }
499
+ var ZodType = class {
500
+ get description() {
501
+ return this._def.description;
655
502
  }
656
- get message() {
657
- return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
503
+ _getType(input) {
504
+ return getParsedType(input.data);
658
505
  }
659
- get isEmpty() {
660
- return this.issues.length === 0;
506
+ _getOrReturnCtx(input, ctx) {
507
+ return ctx || {
508
+ common: input.parent.common,
509
+ data: input.data,
510
+ parsedType: getParsedType(input.data),
511
+ schemaErrorMap: this._def.errorMap,
512
+ path: input.path,
513
+ parent: input.parent
514
+ };
661
515
  }
662
- flatten(mapper = (issue) => issue.message) {
663
- const fieldErrors = {};
664
- const formErrors = [];
665
- for (const sub of this.issues) if (sub.path.length > 0) {
666
- const firstEl = sub.path[0];
667
- fieldErrors[firstEl] = fieldErrors[firstEl] || [];
668
- fieldErrors[firstEl].push(mapper(sub));
669
- } else formErrors.push(mapper(sub));
516
+ _processInputParams(input) {
670
517
  return {
671
- formErrors,
672
- fieldErrors
518
+ status: new ParseStatus(),
519
+ ctx: {
520
+ common: input.parent.common,
521
+ data: input.data,
522
+ parsedType: getParsedType(input.data),
523
+ schemaErrorMap: this._def.errorMap,
524
+ path: input.path,
525
+ parent: input.parent
526
+ }
673
527
  };
674
528
  }
675
- get formErrors() {
676
- return this.flatten();
677
- }
678
- };
679
- ZodError.create = (issues) => {
680
- return new ZodError(issues);
681
- };
682
-
683
- //#endregion
684
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
685
- const errorMap = (issue, _ctx) => {
686
- let message;
687
- switch (issue.code) {
688
- case ZodIssueCode.invalid_type:
689
- if (issue.received === ZodParsedType.undefined) message = "Required";
690
- else message = `Expected ${issue.expected}, received ${issue.received}`;
691
- break;
692
- case ZodIssueCode.invalid_literal:
693
- message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`;
694
- break;
695
- case ZodIssueCode.unrecognized_keys:
696
- message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`;
697
- break;
698
- case ZodIssueCode.invalid_union:
699
- message = `Invalid input`;
700
- break;
701
- case ZodIssueCode.invalid_union_discriminator:
702
- message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`;
703
- break;
704
- case ZodIssueCode.invalid_enum_value:
705
- message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`;
706
- break;
707
- case ZodIssueCode.invalid_arguments:
708
- message = `Invalid function arguments`;
709
- break;
710
- case ZodIssueCode.invalid_return_type:
711
- message = `Invalid function return type`;
712
- break;
713
- case ZodIssueCode.invalid_date:
714
- message = `Invalid date`;
715
- break;
716
- case ZodIssueCode.invalid_string:
717
- if (typeof issue.validation === "object") if ("includes" in issue.validation) {
718
- message = `Invalid input: must include "${issue.validation.includes}"`;
719
- if (typeof issue.validation.position === "number") message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`;
720
- } else if ("startsWith" in issue.validation) message = `Invalid input: must start with "${issue.validation.startsWith}"`;
721
- else if ("endsWith" in issue.validation) message = `Invalid input: must end with "${issue.validation.endsWith}"`;
722
- else util.assertNever(issue.validation);
723
- else if (issue.validation !== "regex") message = `Invalid ${issue.validation}`;
724
- else message = "Invalid";
725
- break;
726
- case ZodIssueCode.too_small:
727
- if (issue.type === "array") message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;
728
- else if (issue.type === "string") message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
729
- else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
730
- else if (issue.type === "bigint") message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;
731
- else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;
732
- else message = "Invalid input";
733
- break;
734
- case ZodIssueCode.too_big:
735
- if (issue.type === "array") message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;
736
- else if (issue.type === "string") message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;
737
- else if (issue.type === "number") message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
738
- else if (issue.type === "bigint") message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
739
- else if (issue.type === "date") message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;
740
- else message = "Invalid input";
741
- break;
742
- case ZodIssueCode.custom:
743
- message = `Invalid input`;
744
- break;
745
- case ZodIssueCode.invalid_intersection_types:
746
- message = `Intersection results could not be merged`;
747
- break;
748
- case ZodIssueCode.not_multiple_of:
749
- message = `Number must be a multiple of ${issue.multipleOf}`;
750
- break;
751
- case ZodIssueCode.not_finite:
752
- message = "Number must be finite";
753
- break;
754
- default:
755
- message = _ctx.defaultError;
756
- util.assertNever(issue);
757
- }
758
- return { message };
759
- };
760
- var en_default = errorMap;
761
-
762
- //#endregion
763
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
764
- let overrideErrorMap = en_default;
765
- function getErrorMap() {
766
- return overrideErrorMap;
767
- }
768
-
769
- //#endregion
770
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
771
- const makeIssue = (params) => {
772
- const { data, path: path$1, errorMaps, issueData } = params;
773
- const fullPath = [...path$1, ...issueData.path || []];
774
- const fullIssue = {
775
- ...issueData,
776
- path: fullPath
777
- };
778
- if (issueData.message !== void 0) return {
779
- ...issueData,
780
- path: fullPath,
781
- message: issueData.message
782
- };
783
- let errorMessage = "";
784
- const maps = errorMaps.filter((m) => !!m).slice().reverse();
785
- for (const map of maps) errorMessage = map(fullIssue, {
786
- data,
787
- defaultError: errorMessage
788
- }).message;
789
- return {
790
- ...issueData,
791
- path: fullPath,
792
- message: errorMessage
793
- };
794
- };
795
- function addIssueToContext(ctx, issueData) {
796
- const overrideMap = getErrorMap();
797
- const issue = makeIssue({
798
- issueData,
799
- data: ctx.data,
800
- path: ctx.path,
801
- errorMaps: [
802
- ctx.common.contextualErrorMap,
803
- ctx.schemaErrorMap,
804
- overrideMap,
805
- overrideMap === en_default ? void 0 : en_default
806
- ].filter((x) => !!x)
807
- });
808
- ctx.common.issues.push(issue);
809
- }
810
- var ParseStatus = class ParseStatus {
811
- constructor() {
812
- this.value = "valid";
529
+ _parseSync(input) {
530
+ const result = this._parse(input);
531
+ if (isAsync(result)) throw new Error("Synchronous parse encountered promise.");
532
+ return result;
813
533
  }
814
- dirty() {
815
- if (this.value === "valid") this.value = "dirty";
534
+ _parseAsync(input) {
535
+ const result = this._parse(input);
536
+ return Promise.resolve(result);
816
537
  }
817
- abort() {
818
- if (this.value !== "aborted") this.value = "aborted";
538
+ parse(data, params) {
539
+ const result = this.safeParse(data, params);
540
+ if (result.success) return result.data;
541
+ throw result.error;
819
542
  }
820
- static mergeArray(status, results) {
821
- const arrayValue = [];
822
- for (const s of results) {
823
- if (s.status === "aborted") return INVALID;
824
- if (s.status === "dirty") status.dirty();
825
- arrayValue.push(s.value);
826
- }
827
- return {
828
- status: status.value,
829
- value: arrayValue
543
+ safeParse(data, params) {
544
+ const ctx = {
545
+ common: {
546
+ issues: [],
547
+ async: params?.async ?? false,
548
+ contextualErrorMap: params?.errorMap
549
+ },
550
+ path: params?.path || [],
551
+ schemaErrorMap: this._def.errorMap,
552
+ parent: null,
553
+ data,
554
+ parsedType: getParsedType(data)
830
555
  };
556
+ return handleResult(ctx, this._parseSync({
557
+ data,
558
+ path: ctx.path,
559
+ parent: ctx
560
+ }));
831
561
  }
832
- static async mergeObjectAsync(status, pairs) {
833
- const syncPairs = [];
834
- for (const pair of pairs) {
835
- const key = await pair.key;
836
- const value = await pair.value;
837
- syncPairs.push({
838
- key,
839
- value
562
+ "~validate"(data) {
563
+ const ctx = {
564
+ common: {
565
+ issues: [],
566
+ async: !!this["~standard"].async
567
+ },
568
+ path: [],
569
+ schemaErrorMap: this._def.errorMap,
570
+ parent: null,
571
+ data,
572
+ parsedType: getParsedType(data)
573
+ };
574
+ if (!this["~standard"].async) try {
575
+ const result = this._parseSync({
576
+ data,
577
+ path: [],
578
+ parent: ctx
840
579
  });
580
+ return isValid(result) ? { value: result.value } : { issues: ctx.common.issues };
581
+ } catch (err) {
582
+ if (err?.message?.toLowerCase()?.includes("encountered")) this["~standard"].async = true;
583
+ ctx.common = {
584
+ issues: [],
585
+ async: true
586
+ };
841
587
  }
842
- return ParseStatus.mergeObjectSync(status, syncPairs);
588
+ return this._parseAsync({
589
+ data,
590
+ path: [],
591
+ parent: ctx
592
+ }).then((result) => isValid(result) ? { value: result.value } : { issues: ctx.common.issues });
843
593
  }
844
- static mergeObjectSync(status, pairs) {
845
- const finalObject = {};
846
- for (const pair of pairs) {
847
- const { key, value } = pair;
848
- if (key.status === "aborted") return INVALID;
849
- if (value.status === "aborted") return INVALID;
850
- if (key.status === "dirty") status.dirty();
851
- if (value.status === "dirty") status.dirty();
852
- if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) finalObject[key.value] = value.value;
853
- }
854
- return {
855
- status: status.value,
856
- value: finalObject
594
+ async parseAsync(data, params) {
595
+ const result = await this.safeParseAsync(data, params);
596
+ if (result.success) return result.data;
597
+ throw result.error;
598
+ }
599
+ async safeParseAsync(data, params) {
600
+ const ctx = {
601
+ common: {
602
+ issues: [],
603
+ contextualErrorMap: params?.errorMap,
604
+ async: true
605
+ },
606
+ path: params?.path || [],
607
+ schemaErrorMap: this._def.errorMap,
608
+ parent: null,
609
+ data,
610
+ parsedType: getParsedType(data)
857
611
  };
858
- }
859
- };
860
- const INVALID = Object.freeze({ status: "aborted" });
861
- const DIRTY = (value) => ({
862
- status: "dirty",
863
- value
864
- });
865
- const OK = (value) => ({
866
- status: "valid",
867
- value
868
- });
869
- const isAborted = (x) => x.status === "aborted";
870
- const isDirty = (x) => x.status === "dirty";
871
- const isValid = (x) => x.status === "valid";
872
- const isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
873
-
874
- //#endregion
875
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
876
- var errorUtil;
877
- (function(errorUtil$1) {
878
- errorUtil$1.errToObj = (message) => typeof message === "string" ? { message } : message || {};
879
- errorUtil$1.toString = (message) => typeof message === "string" ? message : message?.message;
880
- })(errorUtil || (errorUtil = {}));
881
-
882
- //#endregion
883
- //#region ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
884
- var ParseInputLazyPath = class {
885
- constructor(parent, value, path$1, key) {
886
- this._cachedPath = [];
887
- this.parent = parent;
888
- this.data = value;
889
- this._path = path$1;
890
- this._key = key;
891
- }
892
- get path() {
893
- if (!this._cachedPath.length) if (Array.isArray(this._key)) this._cachedPath.push(...this._path, ...this._key);
894
- else this._cachedPath.push(...this._path, this._key);
895
- return this._cachedPath;
896
- }
897
- };
898
- const handleResult = (ctx, result) => {
899
- if (isValid(result)) return {
900
- success: true,
901
- data: result.value
902
- };
903
- else {
904
- if (!ctx.common.issues.length) throw new Error("Validation failed but no issues detected.");
905
- return {
906
- success: false,
907
- get error() {
908
- if (this._error) return this._error;
909
- this._error = new ZodError(ctx.common.issues);
910
- return this._error;
911
- }
912
- };
913
- }
914
- };
915
- function processCreateParams(params) {
916
- if (!params) return {};
917
- const { errorMap: errorMap$1, invalid_type_error, required_error, description } = params;
918
- if (errorMap$1 && (invalid_type_error || required_error)) throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
919
- if (errorMap$1) return {
920
- errorMap: errorMap$1,
921
- description
922
- };
923
- const customMap = (iss, ctx) => {
924
- const { message } = params;
925
- if (iss.code === "invalid_enum_value") return { message: message ?? ctx.defaultError };
926
- if (typeof ctx.data === "undefined") return { message: message ?? required_error ?? ctx.defaultError };
927
- if (iss.code !== "invalid_type") return { message: ctx.defaultError };
928
- return { message: message ?? invalid_type_error ?? ctx.defaultError };
929
- };
930
- return {
931
- errorMap: customMap,
932
- description
933
- };
934
- }
935
- var ZodType = class {
936
- get description() {
937
- return this._def.description;
938
- }
939
- _getType(input) {
940
- return getParsedType(input.data);
941
- }
942
- _getOrReturnCtx(input, ctx) {
943
- return ctx || {
944
- common: input.parent.common,
945
- data: input.data,
946
- parsedType: getParsedType(input.data),
947
- schemaErrorMap: this._def.errorMap,
948
- path: input.path,
949
- parent: input.parent
950
- };
951
- }
952
- _processInputParams(input) {
953
- return {
954
- status: new ParseStatus(),
955
- ctx: {
956
- common: input.parent.common,
957
- data: input.data,
958
- parsedType: getParsedType(input.data),
959
- schemaErrorMap: this._def.errorMap,
960
- path: input.path,
961
- parent: input.parent
962
- }
963
- };
964
- }
965
- _parseSync(input) {
966
- const result = this._parse(input);
967
- if (isAsync(result)) throw new Error("Synchronous parse encountered promise.");
968
- return result;
969
- }
970
- _parseAsync(input) {
971
- const result = this._parse(input);
972
- return Promise.resolve(result);
973
- }
974
- parse(data, params) {
975
- const result = this.safeParse(data, params);
976
- if (result.success) return result.data;
977
- throw result.error;
978
- }
979
- safeParse(data, params) {
980
- const ctx = {
981
- common: {
982
- issues: [],
983
- async: params?.async ?? false,
984
- contextualErrorMap: params?.errorMap
985
- },
986
- path: params?.path || [],
987
- schemaErrorMap: this._def.errorMap,
988
- parent: null,
989
- data,
990
- parsedType: getParsedType(data)
991
- };
992
- return handleResult(ctx, this._parseSync({
993
- data,
994
- path: ctx.path,
995
- parent: ctx
996
- }));
997
- }
998
- "~validate"(data) {
999
- const ctx = {
1000
- common: {
1001
- issues: [],
1002
- async: !!this["~standard"].async
1003
- },
1004
- path: [],
1005
- schemaErrorMap: this._def.errorMap,
1006
- parent: null,
1007
- data,
1008
- parsedType: getParsedType(data)
1009
- };
1010
- if (!this["~standard"].async) try {
1011
- const result = this._parseSync({
1012
- data,
1013
- path: [],
1014
- parent: ctx
1015
- });
1016
- return isValid(result) ? { value: result.value } : { issues: ctx.common.issues };
1017
- } catch (err) {
1018
- if (err?.message?.toLowerCase()?.includes("encountered")) this["~standard"].async = true;
1019
- ctx.common = {
1020
- issues: [],
1021
- async: true
1022
- };
1023
- }
1024
- return this._parseAsync({
1025
- data,
1026
- path: [],
1027
- parent: ctx
1028
- }).then((result) => isValid(result) ? { value: result.value } : { issues: ctx.common.issues });
1029
- }
1030
- async parseAsync(data, params) {
1031
- const result = await this.safeParseAsync(data, params);
1032
- if (result.success) return result.data;
1033
- throw result.error;
1034
- }
1035
- async safeParseAsync(data, params) {
1036
- const ctx = {
1037
- common: {
1038
- issues: [],
1039
- contextualErrorMap: params?.errorMap,
1040
- async: true
1041
- },
1042
- path: params?.path || [],
1043
- schemaErrorMap: this._def.errorMap,
1044
- parent: null,
1045
- data,
1046
- parsedType: getParsedType(data)
1047
- };
1048
- const maybeAsyncResult = this._parse({
1049
- data,
1050
- path: ctx.path,
1051
- parent: ctx
1052
- });
1053
- return handleResult(ctx, await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)));
612
+ const maybeAsyncResult = this._parse({
613
+ data,
614
+ path: ctx.path,
615
+ parent: ctx
616
+ });
617
+ return handleResult(ctx, await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)));
1054
618
  }
1055
619
  refine(check, message) {
1056
620
  const getIssueProperties = (val) => {
@@ -4165,186 +3729,624 @@ var APIProvider = class {
4165
3729
  reader.releaseLock();
4166
3730
  }
4167
3731
  }
4168
- async isAvailable() {
4169
- try {
4170
- return (await fetch(`${this.config.baseUrl}/models`, { headers: {
4171
- ...this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` },
4172
- ...this.config.headers
4173
- } })).ok;
4174
- } catch {
4175
- return false;
3732
+ async isAvailable() {
3733
+ try {
3734
+ return (await fetch(`${this.config.baseUrl}/models`, { headers: {
3735
+ ...this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` },
3736
+ ...this.config.headers
3737
+ } })).ok;
3738
+ } catch {
3739
+ return false;
3740
+ }
3741
+ }
3742
+ };
3743
+
3744
+ //#endregion
3745
+ //#region ../ai-provider/src/acp-provider.ts
3746
+ /**
3747
+ * ACP-based AI provider (Agent Client Protocol)
3748
+ * Communicates with coding agents via standardized protocol
3749
+ *
3750
+ * Note: ACP support is experimental and not yet fully implemented.
3751
+ * The ACP SDK API is still evolving.
3752
+ *
3753
+ * @see https://agentclientprotocol.com/
3754
+ */
3755
+ var ACPProvider = class {
3756
+ name;
3757
+ type = "acp";
3758
+ constructor(config) {
3759
+ this.config = config;
3760
+ this.name = config.name;
3761
+ }
3762
+ async complete(_options) {
3763
+ throw new Error(`ACP provider "${this.name}" is not yet implemented. Command: ${this.config.command} ${this.config.args.join(" ")}`);
3764
+ }
3765
+ async isAvailable() {
3766
+ return false;
3767
+ }
3768
+ async dispose() {}
3769
+ };
3770
+ /**
3771
+ * Predefined ACP agent configurations
3772
+ *
3773
+ * Native ACP support:
3774
+ * - iFlow: `iflow --experimental-acp`
3775
+ * - Gemini: `gemini --experimental-acp`
3776
+ *
3777
+ * ACP adapters (by Zed team):
3778
+ * - Claude Code: https://github.com/zed-industries/claude-code-acp
3779
+ * - Codex: https://github.com/zed-industries/codex-acp
3780
+ */
3781
+ const ACPAgents = {
3782
+ iflow: {
3783
+ type: "acp",
3784
+ name: "iFlow",
3785
+ command: "iflow",
3786
+ args: ["--experimental-acp"]
3787
+ },
3788
+ gemini: {
3789
+ type: "acp",
3790
+ name: "Gemini",
3791
+ command: "gemini",
3792
+ args: ["--experimental-acp"]
3793
+ },
3794
+ claude: {
3795
+ type: "acp",
3796
+ name: "Claude Code",
3797
+ command: "claude-code-acp",
3798
+ args: []
3799
+ },
3800
+ codex: {
3801
+ type: "acp",
3802
+ name: "Codex",
3803
+ command: "codex-acp",
3804
+ args: []
3805
+ }
3806
+ };
3807
+
3808
+ //#endregion
3809
+ //#region ../ai-provider/src/manager.ts
3810
+ /**
3811
+ * Factory function to create a provider from configuration
3812
+ */
3813
+ function createProvider(config) {
3814
+ switch (config.type) {
3815
+ case "api": return new APIProvider(config);
3816
+ case "acp": return new ACPProvider(config);
3817
+ default: throw new Error(`Unknown provider type: ${config.type}`);
3818
+ }
3819
+ }
3820
+ /**
3821
+ * Provider manager for handling multiple AI providers
3822
+ */
3823
+ var ProviderManager = class {
3824
+ providers = /* @__PURE__ */ new Map();
3825
+ defaultApiName = null;
3826
+ defaultAcpName = null;
3827
+ constructor(registry) {
3828
+ if (registry) this.loadRegistry(registry);
3829
+ }
3830
+ /**
3831
+ * Load providers from a registry configuration
3832
+ */
3833
+ loadRegistry(registry) {
3834
+ for (const [name, config] of Object.entries(registry.providers)) {
3835
+ const provider = createProvider(config);
3836
+ this.providers.set(name, provider);
3837
+ }
3838
+ if (registry.defaultApi) this.defaultApiName = registry.defaultApi;
3839
+ if (registry.defaultAcp) this.defaultAcpName = registry.defaultAcp;
3840
+ }
3841
+ /**
3842
+ * Register a provider
3843
+ */
3844
+ register(name, provider) {
3845
+ this.providers.set(name, provider);
3846
+ }
3847
+ /**
3848
+ * Get a provider by name
3849
+ */
3850
+ get(name) {
3851
+ return this.providers.get(name);
3852
+ }
3853
+ /**
3854
+ * Get the default API provider
3855
+ */
3856
+ getDefaultApi() {
3857
+ if (!this.defaultApiName) return void 0;
3858
+ return this.providers.get(this.defaultApiName);
3859
+ }
3860
+ /**
3861
+ * Get the default ACP provider
3862
+ */
3863
+ getDefaultAcp() {
3864
+ if (!this.defaultAcpName) return void 0;
3865
+ return this.providers.get(this.defaultAcpName);
3866
+ }
3867
+ /**
3868
+ * Set the default API provider
3869
+ */
3870
+ setDefaultApi(name) {
3871
+ const provider = this.providers.get(name);
3872
+ if (!provider) throw new Error(`Provider '${name}' not found`);
3873
+ if (provider.type !== "api") throw new Error(`Provider '${name}' is not an API provider`);
3874
+ this.defaultApiName = name;
3875
+ }
3876
+ /**
3877
+ * Set the default ACP provider
3878
+ */
3879
+ setDefaultAcp(name) {
3880
+ const provider = this.providers.get(name);
3881
+ if (!provider) throw new Error(`Provider '${name}' not found`);
3882
+ if (provider.type !== "acp") throw new Error(`Provider '${name}' is not an ACP provider`);
3883
+ this.defaultAcpName = name;
3884
+ }
3885
+ /**
3886
+ * List all registered providers
3887
+ */
3888
+ list() {
3889
+ return Array.from(this.providers.entries()).map(([name, provider]) => ({
3890
+ name,
3891
+ type: provider.type
3892
+ }));
3893
+ }
3894
+ /**
3895
+ * Check availability of all providers
3896
+ */
3897
+ async checkAvailability() {
3898
+ const results = /* @__PURE__ */ new Map();
3899
+ await Promise.all(Array.from(this.providers.entries()).map(async ([name, provider]) => {
3900
+ const available = await provider.isAvailable();
3901
+ results.set(name, available);
3902
+ }));
3903
+ return results;
3904
+ }
3905
+ /**
3906
+ * Dispose all providers
3907
+ */
3908
+ async dispose() {
3909
+ await Promise.all(Array.from(this.providers.values()).map((provider) => provider.dispose?.()));
3910
+ this.providers.clear();
3911
+ }
3912
+ };
3913
+
3914
+ //#endregion
3915
+ //#region ../../node_modules/.pnpm/@hono+node-server@1.19.6_hono@4.10.6/node_modules/@hono/node-server/dist/index.mjs
3916
+ var RequestError = class extends Error {
3917
+ constructor(message, options) {
3918
+ super(message, options);
3919
+ this.name = "RequestError";
3920
+ }
3921
+ };
3922
+ var toRequestError = (e) => {
3923
+ if (e instanceof RequestError) return e;
3924
+ return new RequestError(e.message, { cause: e });
3925
+ };
3926
+ var GlobalRequest = global.Request;
3927
+ var Request$1 = class extends GlobalRequest {
3928
+ constructor(input, options) {
3929
+ if (typeof input === "object" && getRequestCache in input) input = input[getRequestCache]();
3930
+ if (typeof options?.body?.getReader !== "undefined") options.duplex ??= "half";
3931
+ super(input, options);
3932
+ }
3933
+ };
3934
+ var newHeadersFromIncoming = (incoming) => {
3935
+ const headerRecord = [];
3936
+ const rawHeaders = incoming.rawHeaders;
3937
+ for (let i = 0; i < rawHeaders.length; i += 2) {
3938
+ const { [i]: key, [i + 1]: value } = rawHeaders;
3939
+ if (key.charCodeAt(0) !== 58) headerRecord.push([key, value]);
3940
+ }
3941
+ return new Headers(headerRecord);
3942
+ };
3943
+ var wrapBodyStream = Symbol("wrapBodyStream");
3944
+ var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
3945
+ const init = {
3946
+ method,
3947
+ headers,
3948
+ signal: abortController.signal
3949
+ };
3950
+ if (method === "TRACE") {
3951
+ init.method = "GET";
3952
+ const req = new Request$1(url, init);
3953
+ Object.defineProperty(req, "method", { get() {
3954
+ return "TRACE";
3955
+ } });
3956
+ return req;
3957
+ }
3958
+ if (!(method === "GET" || method === "HEAD")) if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) init.body = new ReadableStream({ start(controller) {
3959
+ controller.enqueue(incoming.rawBody);
3960
+ controller.close();
3961
+ } });
3962
+ else if (incoming[wrapBodyStream]) {
3963
+ let reader;
3964
+ init.body = new ReadableStream({ async pull(controller) {
3965
+ try {
3966
+ reader ||= Readable.toWeb(incoming).getReader();
3967
+ const { done, value } = await reader.read();
3968
+ if (done) controller.close();
3969
+ else controller.enqueue(value);
3970
+ } catch (error) {
3971
+ controller.error(error);
3972
+ }
3973
+ } });
3974
+ } else init.body = Readable.toWeb(incoming);
3975
+ return new Request$1(url, init);
3976
+ };
3977
+ var getRequestCache = Symbol("getRequestCache");
3978
+ var requestCache = Symbol("requestCache");
3979
+ var incomingKey = Symbol("incomingKey");
3980
+ var urlKey = Symbol("urlKey");
3981
+ var headersKey = Symbol("headersKey");
3982
+ var abortControllerKey = Symbol("abortControllerKey");
3983
+ var requestPrototype = {
3984
+ get method() {
3985
+ return this[incomingKey].method || "GET";
3986
+ },
3987
+ get url() {
3988
+ return this[urlKey];
3989
+ },
3990
+ get headers() {
3991
+ return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
3992
+ },
3993
+ [Symbol("getAbortController")]() {
3994
+ this[getRequestCache]();
3995
+ return this[abortControllerKey];
3996
+ },
3997
+ [getRequestCache]() {
3998
+ this[abortControllerKey] ||= new AbortController();
3999
+ return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
4000
+ }
4001
+ };
4002
+ [
4003
+ "body",
4004
+ "bodyUsed",
4005
+ "cache",
4006
+ "credentials",
4007
+ "destination",
4008
+ "integrity",
4009
+ "mode",
4010
+ "redirect",
4011
+ "referrer",
4012
+ "referrerPolicy",
4013
+ "signal",
4014
+ "keepalive"
4015
+ ].forEach((k) => {
4016
+ Object.defineProperty(requestPrototype, k, { get() {
4017
+ return this[getRequestCache]()[k];
4018
+ } });
4019
+ });
4020
+ [
4021
+ "arrayBuffer",
4022
+ "blob",
4023
+ "clone",
4024
+ "formData",
4025
+ "json",
4026
+ "text"
4027
+ ].forEach((k) => {
4028
+ Object.defineProperty(requestPrototype, k, { value: function() {
4029
+ return this[getRequestCache]()[k]();
4030
+ } });
4031
+ });
4032
+ Object.setPrototypeOf(requestPrototype, Request$1.prototype);
4033
+ var newRequest = (incoming, defaultHostname) => {
4034
+ const req = Object.create(requestPrototype);
4035
+ req[incomingKey] = incoming;
4036
+ const incomingUrl = incoming.url || "";
4037
+ if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
4038
+ if (incoming instanceof Http2ServerRequest) throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
4039
+ try {
4040
+ req[urlKey] = new URL(incomingUrl).href;
4041
+ } catch (e) {
4042
+ throw new RequestError("Invalid absolute URL", { cause: e });
4043
+ }
4044
+ return req;
4045
+ }
4046
+ const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
4047
+ if (!host) throw new RequestError("Missing host header");
4048
+ let scheme;
4049
+ if (incoming instanceof Http2ServerRequest) {
4050
+ scheme = incoming.scheme;
4051
+ if (!(scheme === "http" || scheme === "https")) throw new RequestError("Unsupported scheme");
4052
+ } else scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
4053
+ const url = new URL(`${scheme}://${host}${incomingUrl}`);
4054
+ if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) throw new RequestError("Invalid host header");
4055
+ req[urlKey] = url.href;
4056
+ return req;
4057
+ };
4058
+ var responseCache = Symbol("responseCache");
4059
+ var getResponseCache = Symbol("getResponseCache");
4060
+ var cacheKey = Symbol("cache");
4061
+ var GlobalResponse = global.Response;
4062
+ var Response2 = class _Response {
4063
+ #body;
4064
+ #init;
4065
+ [getResponseCache]() {
4066
+ delete this[cacheKey];
4067
+ return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
4068
+ }
4069
+ constructor(body, init) {
4070
+ let headers;
4071
+ this.#body = body;
4072
+ if (init instanceof _Response) {
4073
+ const cachedGlobalResponse = init[responseCache];
4074
+ if (cachedGlobalResponse) {
4075
+ this.#init = cachedGlobalResponse;
4076
+ this[getResponseCache]();
4077
+ return;
4078
+ } else {
4079
+ this.#init = init.#init;
4080
+ headers = new Headers(init.#init.headers);
4081
+ }
4082
+ } else this.#init = init;
4083
+ if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
4084
+ headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
4085
+ this[cacheKey] = [
4086
+ init?.status || 200,
4087
+ body,
4088
+ headers
4089
+ ];
4090
+ }
4091
+ }
4092
+ get headers() {
4093
+ const cache = this[cacheKey];
4094
+ if (cache) {
4095
+ if (!(cache[2] instanceof Headers)) cache[2] = new Headers(cache[2]);
4096
+ return cache[2];
4176
4097
  }
4098
+ return this[getResponseCache]().headers;
4177
4099
  }
4178
- };
4179
-
4180
- //#endregion
4181
- //#region ../ai-provider/src/acp-provider.ts
4182
- /**
4183
- * ACP-based AI provider (Agent Client Protocol)
4184
- * Communicates with coding agents via standardized protocol
4185
- *
4186
- * Note: ACP support is experimental and not yet fully implemented.
4187
- * The ACP SDK API is still evolving.
4188
- *
4189
- * @see https://agentclientprotocol.com/
4190
- */
4191
- var ACPProvider = class {
4192
- name;
4193
- type = "acp";
4194
- constructor(config) {
4195
- this.config = config;
4196
- this.name = config.name;
4197
- }
4198
- async complete(_options) {
4199
- throw new Error(`ACP provider "${this.name}" is not yet implemented. Command: ${this.config.command} ${this.config.args.join(" ")}`);
4200
- }
4201
- async isAvailable() {
4202
- return false;
4100
+ get status() {
4101
+ return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
4203
4102
  }
4204
- async dispose() {}
4205
- };
4206
- /**
4207
- * Predefined ACP agent configurations
4208
- *
4209
- * Native ACP support:
4210
- * - iFlow: `iflow --experimental-acp`
4211
- * - Gemini: `gemini --experimental-acp`
4212
- *
4213
- * ACP adapters (by Zed team):
4214
- * - Claude Code: https://github.com/zed-industries/claude-code-acp
4215
- * - Codex: https://github.com/zed-industries/codex-acp
4216
- */
4217
- const ACPAgents = {
4218
- iflow: {
4219
- type: "acp",
4220
- name: "iFlow",
4221
- command: "iflow",
4222
- args: ["--experimental-acp"]
4223
- },
4224
- gemini: {
4225
- type: "acp",
4226
- name: "Gemini",
4227
- command: "gemini",
4228
- args: ["--experimental-acp"]
4229
- },
4230
- claude: {
4231
- type: "acp",
4232
- name: "Claude Code",
4233
- command: "claude-code-acp",
4234
- args: []
4235
- },
4236
- codex: {
4237
- type: "acp",
4238
- name: "Codex",
4239
- command: "codex-acp",
4240
- args: []
4103
+ get ok() {
4104
+ const status = this.status;
4105
+ return status >= 200 && status < 300;
4241
4106
  }
4242
4107
  };
4243
-
4244
- //#endregion
4245
- //#region ../ai-provider/src/manager.ts
4246
- /**
4247
- * Factory function to create a provider from configuration
4248
- */
4249
- function createProvider(config) {
4250
- switch (config.type) {
4251
- case "api": return new APIProvider(config);
4252
- case "acp": return new ACPProvider(config);
4253
- default: throw new Error(`Unknown provider type: ${config.type}`);
4254
- }
4108
+ [
4109
+ "body",
4110
+ "bodyUsed",
4111
+ "redirected",
4112
+ "statusText",
4113
+ "trailers",
4114
+ "type",
4115
+ "url"
4116
+ ].forEach((k) => {
4117
+ Object.defineProperty(Response2.prototype, k, { get() {
4118
+ return this[getResponseCache]()[k];
4119
+ } });
4120
+ });
4121
+ [
4122
+ "arrayBuffer",
4123
+ "blob",
4124
+ "clone",
4125
+ "formData",
4126
+ "json",
4127
+ "text"
4128
+ ].forEach((k) => {
4129
+ Object.defineProperty(Response2.prototype, k, { value: function() {
4130
+ return this[getResponseCache]()[k]();
4131
+ } });
4132
+ });
4133
+ Object.setPrototypeOf(Response2, GlobalResponse);
4134
+ Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
4135
+ async function readWithoutBlocking(readPromise) {
4136
+ return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
4255
4137
  }
4256
- /**
4257
- * Provider manager for handling multiple AI providers
4258
- */
4259
- var ProviderManager = class {
4260
- providers = /* @__PURE__ */ new Map();
4261
- defaultApiName = null;
4262
- defaultAcpName = null;
4263
- constructor(registry) {
4264
- if (registry) this.loadRegistry(registry);
4265
- }
4266
- /**
4267
- * Load providers from a registry configuration
4268
- */
4269
- loadRegistry(registry) {
4270
- for (const [name, config] of Object.entries(registry.providers)) {
4271
- const provider = createProvider(config);
4272
- this.providers.set(name, provider);
4273
- }
4274
- if (registry.defaultApi) this.defaultApiName = registry.defaultApi;
4275
- if (registry.defaultAcp) this.defaultAcpName = registry.defaultAcp;
4276
- }
4277
- /**
4278
- * Register a provider
4279
- */
4280
- register(name, provider) {
4281
- this.providers.set(name, provider);
4282
- }
4283
- /**
4284
- * Get a provider by name
4285
- */
4286
- get(name) {
4287
- return this.providers.get(name);
4138
+ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
4139
+ const cancel = (error) => {
4140
+ reader.cancel(error).catch(() => {});
4141
+ };
4142
+ writable.on("close", cancel);
4143
+ writable.on("error", cancel);
4144
+ (currentReadPromise ?? reader.read()).then(flow, handleStreamError);
4145
+ return reader.closed.finally(() => {
4146
+ writable.off("close", cancel);
4147
+ writable.off("error", cancel);
4148
+ });
4149
+ function handleStreamError(error) {
4150
+ if (error) writable.destroy(error);
4288
4151
  }
4289
- /**
4290
- * Get the default API provider
4291
- */
4292
- getDefaultApi() {
4293
- if (!this.defaultApiName) return void 0;
4294
- return this.providers.get(this.defaultApiName);
4152
+ function onDrain() {
4153
+ reader.read().then(flow, handleStreamError);
4295
4154
  }
4296
- /**
4297
- * Get the default ACP provider
4298
- */
4299
- getDefaultAcp() {
4300
- if (!this.defaultAcpName) return void 0;
4301
- return this.providers.get(this.defaultAcpName);
4155
+ function flow({ done, value }) {
4156
+ try {
4157
+ if (done) writable.end();
4158
+ else if (!writable.write(value)) writable.once("drain", onDrain);
4159
+ else return reader.read().then(flow, handleStreamError);
4160
+ } catch (e) {
4161
+ handleStreamError(e);
4162
+ }
4302
4163
  }
4303
- /**
4304
- * Set the default API provider
4305
- */
4306
- setDefaultApi(name) {
4307
- const provider = this.providers.get(name);
4308
- if (!provider) throw new Error(`Provider '${name}' not found`);
4309
- if (provider.type !== "api") throw new Error(`Provider '${name}' is not an API provider`);
4310
- this.defaultApiName = name;
4164
+ }
4165
+ function writeFromReadableStream(stream, writable) {
4166
+ if (stream.locked) throw new TypeError("ReadableStream is locked.");
4167
+ else if (writable.destroyed) return;
4168
+ return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
4169
+ }
4170
+ var buildOutgoingHttpHeaders = (headers) => {
4171
+ const res = {};
4172
+ if (!(headers instanceof Headers)) headers = new Headers(headers ?? void 0);
4173
+ const cookies = [];
4174
+ for (const [k, v] of headers) if (k === "set-cookie") cookies.push(v);
4175
+ else res[k] = v;
4176
+ if (cookies.length > 0) res["set-cookie"] = cookies;
4177
+ res["content-type"] ??= "text/plain; charset=UTF-8";
4178
+ return res;
4179
+ };
4180
+ var X_ALREADY_SENT = "x-hono-already-sent";
4181
+ var webFetch = global.fetch;
4182
+ if (typeof global.crypto === "undefined") global.crypto = crypto;
4183
+ global.fetch = (info, init) => {
4184
+ init = {
4185
+ compress: false,
4186
+ ...init
4187
+ };
4188
+ return webFetch(info, init);
4189
+ };
4190
+ var outgoingEnded = Symbol("outgoingEnded");
4191
+ var handleRequestError = () => new Response(null, { status: 400 });
4192
+ var handleFetchError = (e) => new Response(null, { status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500 });
4193
+ var handleResponseError = (e, outgoing) => {
4194
+ const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
4195
+ if (err.code === "ERR_STREAM_PREMATURE_CLOSE") console.info("The user aborted a request.");
4196
+ else {
4197
+ console.error(e);
4198
+ if (!outgoing.headersSent) outgoing.writeHead(500, { "Content-Type": "text/plain" });
4199
+ outgoing.end(`Error: ${err.message}`);
4200
+ outgoing.destroy(err);
4311
4201
  }
4312
- /**
4313
- * Set the default ACP provider
4314
- */
4315
- setDefaultAcp(name) {
4316
- const provider = this.providers.get(name);
4317
- if (!provider) throw new Error(`Provider '${name}' not found`);
4318
- if (provider.type !== "acp") throw new Error(`Provider '${name}' is not an ACP provider`);
4319
- this.defaultAcpName = name;
4202
+ };
4203
+ var flushHeaders = (outgoing) => {
4204
+ if ("flushHeaders" in outgoing && outgoing.writable) outgoing.flushHeaders();
4205
+ };
4206
+ var responseViaCache = async (res, outgoing) => {
4207
+ let [status, body, header] = res[cacheKey];
4208
+ if (header instanceof Headers) header = buildOutgoingHttpHeaders(header);
4209
+ if (typeof body === "string") header["Content-Length"] = Buffer.byteLength(body);
4210
+ else if (body instanceof Uint8Array) header["Content-Length"] = body.byteLength;
4211
+ else if (body instanceof Blob) header["Content-Length"] = body.size;
4212
+ outgoing.writeHead(status, header);
4213
+ if (typeof body === "string" || body instanceof Uint8Array) outgoing.end(body);
4214
+ else if (body instanceof Blob) outgoing.end(new Uint8Array(await body.arrayBuffer()));
4215
+ else {
4216
+ flushHeaders(outgoing);
4217
+ await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
4320
4218
  }
4321
- /**
4322
- * List all registered providers
4323
- */
4324
- list() {
4325
- return Array.from(this.providers.entries()).map(([name, provider]) => ({
4326
- name,
4327
- type: provider.type
4328
- }));
4219
+ outgoing[outgoingEnded]?.();
4220
+ };
4221
+ var isPromise$1 = (res) => typeof res.then === "function";
4222
+ var responseViaResponseObject = async (res, outgoing, options = {}) => {
4223
+ if (isPromise$1(res)) if (options.errorHandler) try {
4224
+ res = await res;
4225
+ } catch (err) {
4226
+ const errRes = await options.errorHandler(err);
4227
+ if (!errRes) return;
4228
+ res = errRes;
4329
4229
  }
4330
- /**
4331
- * Check availability of all providers
4332
- */
4333
- async checkAvailability() {
4334
- const results = /* @__PURE__ */ new Map();
4335
- await Promise.all(Array.from(this.providers.entries()).map(async ([name, provider]) => {
4336
- const available = await provider.isAvailable();
4337
- results.set(name, available);
4338
- }));
4339
- return results;
4230
+ else res = await res.catch(handleFetchError);
4231
+ if (cacheKey in res) return responseViaCache(res, outgoing);
4232
+ const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
4233
+ if (res.body) {
4234
+ const reader = res.body.getReader();
4235
+ const values = [];
4236
+ let done = false;
4237
+ let currentReadPromise = void 0;
4238
+ if (resHeaderRecord["transfer-encoding"] !== "chunked") {
4239
+ let maxReadCount = 2;
4240
+ for (let i = 0; i < maxReadCount; i++) {
4241
+ currentReadPromise ||= reader.read();
4242
+ const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
4243
+ console.error(e);
4244
+ done = true;
4245
+ });
4246
+ if (!chunk) {
4247
+ if (i === 1) {
4248
+ await new Promise((resolve$2) => setTimeout(resolve$2));
4249
+ maxReadCount = 3;
4250
+ continue;
4251
+ }
4252
+ break;
4253
+ }
4254
+ currentReadPromise = void 0;
4255
+ if (chunk.value) values.push(chunk.value);
4256
+ if (chunk.done) {
4257
+ done = true;
4258
+ break;
4259
+ }
4260
+ }
4261
+ if (done && !("content-length" in resHeaderRecord)) resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
4262
+ }
4263
+ outgoing.writeHead(res.status, resHeaderRecord);
4264
+ values.forEach((value) => {
4265
+ outgoing.write(value);
4266
+ });
4267
+ if (done) outgoing.end();
4268
+ else {
4269
+ if (values.length === 0) flushHeaders(outgoing);
4270
+ await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
4271
+ }
4272
+ } else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
4273
+ outgoing.writeHead(res.status, resHeaderRecord);
4274
+ outgoing.end();
4340
4275
  }
4341
- /**
4342
- * Dispose all providers
4343
- */
4344
- async dispose() {
4345
- await Promise.all(Array.from(this.providers.values()).map((provider) => provider.dispose?.()));
4346
- this.providers.clear();
4276
+ outgoing[outgoingEnded]?.();
4277
+ };
4278
+ var getRequestListener = (fetchCallback, options = {}) => {
4279
+ const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
4280
+ if (options.overrideGlobalObjects !== false && global.Request !== Request$1) {
4281
+ Object.defineProperty(global, "Request", { value: Request$1 });
4282
+ Object.defineProperty(global, "Response", { value: Response2 });
4347
4283
  }
4284
+ return async (incoming, outgoing) => {
4285
+ let res, req;
4286
+ try {
4287
+ req = newRequest(incoming, options.hostname);
4288
+ let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
4289
+ if (!incomingEnded) {
4290
+ incoming[wrapBodyStream] = true;
4291
+ incoming.on("end", () => {
4292
+ incomingEnded = true;
4293
+ });
4294
+ if (incoming instanceof Http2ServerRequest) outgoing[outgoingEnded] = () => {
4295
+ if (!incomingEnded) setTimeout(() => {
4296
+ if (!incomingEnded) setTimeout(() => {
4297
+ incoming.destroy();
4298
+ outgoing.destroy();
4299
+ });
4300
+ });
4301
+ };
4302
+ }
4303
+ outgoing.on("close", () => {
4304
+ if (req[abortControllerKey]) {
4305
+ if (incoming.errored) req[abortControllerKey].abort(incoming.errored.toString());
4306
+ else if (!outgoing.writableFinished) req[abortControllerKey].abort("Client connection prematurely closed.");
4307
+ }
4308
+ if (!incomingEnded) setTimeout(() => {
4309
+ if (!incomingEnded) setTimeout(() => {
4310
+ incoming.destroy();
4311
+ });
4312
+ });
4313
+ });
4314
+ res = fetchCallback(req, {
4315
+ incoming,
4316
+ outgoing
4317
+ });
4318
+ if (cacheKey in res) return responseViaCache(res, outgoing);
4319
+ } catch (e) {
4320
+ if (!res) if (options.errorHandler) {
4321
+ res = await options.errorHandler(req ? e : toRequestError(e));
4322
+ if (!res) return;
4323
+ } else if (!req) res = handleRequestError();
4324
+ else res = handleFetchError(e);
4325
+ else return handleResponseError(e, outgoing);
4326
+ }
4327
+ try {
4328
+ return await responseViaResponseObject(res, outgoing, options);
4329
+ } catch (e) {
4330
+ return handleResponseError(e, outgoing);
4331
+ }
4332
+ };
4333
+ };
4334
+ var createAdaptorServer = (options) => {
4335
+ const fetchCallback = options.fetch;
4336
+ const requestListener = getRequestListener(fetchCallback, {
4337
+ hostname: options.hostname,
4338
+ overrideGlobalObjects: options.overrideGlobalObjects,
4339
+ autoCleanupIncoming: options.autoCleanupIncoming
4340
+ });
4341
+ return (options.createServer || createServer)(options.serverOptions || {}, requestListener);
4342
+ };
4343
+ var serve = (options, listeningListener) => {
4344
+ const server = createAdaptorServer(options);
4345
+ server.listen(options?.port ?? 3e3, options.hostname, () => {
4346
+ const serverInfo = server.address();
4347
+ listeningListener && listeningListener(serverInfo);
4348
+ });
4349
+ return server;
4348
4350
  };
4349
4351
 
4350
4352
  //#endregion
@@ -10938,7 +10940,7 @@ var require_extension = /* @__PURE__ */ __commonJS$1({ "../../node_modules/.pnpm
10938
10940
  * @return {String} A string representing the given object
10939
10941
  * @public
10940
10942
  */
10941
- function format$1(extensions) {
10943
+ function format$2(extensions) {
10942
10944
  return Object.keys(extensions).map((extension$1) => {
10943
10945
  let configurations = extensions[extension$1];
10944
10946
  if (!Array.isArray(configurations)) configurations = [configurations];
@@ -10952,7 +10954,7 @@ var require_extension = /* @__PURE__ */ __commonJS$1({ "../../node_modules/.pnpm
10952
10954
  }).join(", ");
10953
10955
  }
10954
10956
  module.exports = {
10955
- format: format$1,
10957
+ format: format$2,
10956
10958
  parse: parse$2
10957
10959
  };
10958
10960
  }) });
@@ -10974,7 +10976,7 @@ var require_websocket = /* @__PURE__ */ __commonJS$1({ "../../node_modules/.pnpm
10974
10976
  const { isBlob } = require_validation();
10975
10977
  const { BINARY_TYPES, EMPTY_BUFFER, GUID: GUID$1, kForOnEventAttribute, kListener, kStatusCode, kWebSocket: kWebSocket$1, NOOP } = require_constants();
10976
10978
  const { EventTarget: { addEventListener: addEventListener$1, removeEventListener } } = require_event_target();
10977
- const { format, parse: parse$1 } = require_extension();
10979
+ const { format: format$1, parse: parse$1 } = require_extension();
10978
10980
  const { toBuffer } = require_buffer_util();
10979
10981
  const closeTimeout = 30 * 1e3;
10980
10982
  const kAborted = Symbol("kAborted");
@@ -11535,7 +11537,7 @@ var require_websocket = /* @__PURE__ */ __commonJS$1({ "../../node_modules/.pnpm
11535
11537
  opts.timeout = opts.handshakeTimeout;
11536
11538
  if (opts.perMessageDeflate) {
11537
11539
  perMessageDeflate = new PerMessageDeflate$1(opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, false, opts.maxPayload);
11538
- opts.headers["Sec-WebSocket-Extensions"] = format({ [PerMessageDeflate$1.extensionName]: perMessageDeflate.offer() });
11540
+ opts.headers["Sec-WebSocket-Extensions"] = format$1({ [PerMessageDeflate$1.extensionName]: perMessageDeflate.offer() });
11539
11541
  }
11540
11542
  if (protocols.length) {
11541
11543
  for (const protocol of protocols) {
@@ -12810,93 +12812,423 @@ var ReactiveContext = class {
12810
12812
  /** 是否已销毁 */
12811
12813
  destroyed = false;
12812
12814
  /**
12813
- * 追踪依赖
12814
- * 由 ReactiveState.get() 调用
12815
+ * 追踪依赖
12816
+ * 由 ReactiveState.get() 调用
12817
+ */
12818
+ track(state) {
12819
+ if (!this.destroyed) this.dependencies.add(state);
12820
+ }
12821
+ /**
12822
+ * 通知变更
12823
+ * 由 ReactiveState.set() 调用
12824
+ */
12825
+ notifyChange() {
12826
+ if (!this.destroyed && this.changePromise) this.changePromise.resolve();
12827
+ }
12828
+ /**
12829
+ * 运行响应式任务流
12830
+ * 每次依赖变更时重新执行任务并 yield 结果
12831
+ *
12832
+ * @param task 要执行的异步任务
12833
+ * @param signal 用于取消的 AbortSignal
12834
+ */
12835
+ async *stream(task, signal) {
12836
+ try {
12837
+ while (!signal?.aborted && !this.destroyed) {
12838
+ this.clearDependencies();
12839
+ this.changePromise = createPromiseWithResolvers();
12840
+ yield await contextStorage.run(this, task);
12841
+ if (this.dependencies.size === 0) break;
12842
+ await Promise.race([this.changePromise.promise, signal ? this.waitForAbort(signal) : new Promise(() => {})]);
12843
+ if (signal?.aborted) break;
12844
+ }
12845
+ } finally {
12846
+ this.destroy();
12847
+ }
12848
+ }
12849
+ /**
12850
+ * 执行一次任务(非响应式)
12851
+ * 用于初始数据获取
12852
+ */
12853
+ async runOnce(task) {
12854
+ return contextStorage.run(this, task);
12855
+ }
12856
+ /**
12857
+ * 清理依赖
12858
+ */
12859
+ clearDependencies() {
12860
+ for (const state of this.dependencies) state.unsubscribe(this);
12861
+ this.dependencies.clear();
12862
+ }
12863
+ /**
12864
+ * 销毁上下文
12865
+ * @param reason 可选的销毁原因,如果提供则 reject changePromise
12866
+ */
12867
+ destroy(reason) {
12868
+ this.destroyed = true;
12869
+ this.clearDependencies();
12870
+ if (reason && this.changePromise) this.changePromise.reject(reason);
12871
+ this.changePromise = void 0;
12872
+ }
12873
+ /**
12874
+ * 等待 AbortSignal
12875
+ */
12876
+ waitForAbort(signal) {
12877
+ return new Promise((_, reject) => {
12878
+ if (signal.aborted) {
12879
+ reject(new DOMException("Aborted", "AbortError"));
12880
+ return;
12881
+ }
12882
+ signal.addEventListener("abort", () => {
12883
+ reject(new DOMException("Aborted", "AbortError"));
12884
+ });
12885
+ });
12886
+ }
12887
+ };
12888
+
12889
+ //#endregion
12890
+ //#region ../core/src/reactive-fs/project-watcher.ts
12891
+ /**
12892
+ * 获取路径的真实路径(解析符号链接)
12893
+ * 在 macOS 上,/var 是 /private/var 的符号链接
12894
+ */
12895
+ function getRealPath$1(path$1) {
12896
+ try {
12897
+ return realpathSync(resolve$1(path$1));
12898
+ } catch {
12899
+ return resolve$1(path$1);
12900
+ }
12901
+ }
12902
+ /** 默认防抖时间 (ms) */
12903
+ const DEBOUNCE_MS$1 = 50;
12904
+ /** 默认忽略模式 */
12905
+ const DEFAULT_IGNORE = [
12906
+ "node_modules",
12907
+ ".git",
12908
+ "**/.DS_Store"
12909
+ ];
12910
+ /** 健康检查间隔 (ms) - 3秒 */
12911
+ const HEALTH_CHECK_INTERVAL_MS = 3e3;
12912
+ /**
12913
+ * 项目监听器
12914
+ *
12915
+ * 使用 @parcel/watcher 监听项目根目录,
12916
+ * 然后通过路径前缀匹配分发事件给订阅者。
12917
+ *
12918
+ * 特性:
12919
+ * - 单个 watcher 监听整个项目
12920
+ * - 自动处理新创建的目录
12921
+ * - 内置防抖机制
12922
+ * - 高性能原生实现
12923
+ */
12924
+ var ProjectWatcher = class {
12925
+ projectDir;
12926
+ subscription = null;
12927
+ pathSubscriptions = /* @__PURE__ */ new Map();
12928
+ pendingEvents = [];
12929
+ debounceTimer = null;
12930
+ debounceMs;
12931
+ ignore;
12932
+ initialized = false;
12933
+ initPromise = null;
12934
+ healthCheckTimer = null;
12935
+ lastEventTime = 0;
12936
+ healthCheckPending = false;
12937
+ enableHealthCheck;
12938
+ constructor(projectDir, options = {}) {
12939
+ this.projectDir = getRealPath$1(projectDir);
12940
+ this.debounceMs = options.debounceMs ?? DEBOUNCE_MS$1;
12941
+ this.ignore = options.ignore ?? DEFAULT_IGNORE;
12942
+ this.enableHealthCheck = options.enableHealthCheck ?? true;
12943
+ }
12944
+ /**
12945
+ * 初始化 watcher
12946
+ * 懒加载,首次订阅时自动调用
12947
+ */
12948
+ async init() {
12949
+ if (this.initialized) return;
12950
+ if (this.initPromise) return this.initPromise;
12951
+ this.initPromise = this.doInit();
12952
+ await this.initPromise;
12953
+ }
12954
+ async doInit() {
12955
+ this.subscription = await (await import("@parcel/watcher")).subscribe(this.projectDir, (err, events) => {
12956
+ if (err) {
12957
+ console.error("[ProjectWatcher] Error:", err);
12958
+ return;
12959
+ }
12960
+ this.handleEvents(events);
12961
+ }, { ignore: this.ignore });
12962
+ this.initialized = true;
12963
+ this.lastEventTime = Date.now();
12964
+ if (this.enableHealthCheck) this.startHealthCheck();
12965
+ }
12966
+ /**
12967
+ * 处理原始事件
12968
+ */
12969
+ handleEvents(events) {
12970
+ this.lastEventTime = Date.now();
12971
+ this.healthCheckPending = false;
12972
+ const watchEvents = events.map((e) => ({
12973
+ type: e.type,
12974
+ path: e.path
12975
+ }));
12976
+ this.pendingEvents.push(...watchEvents);
12977
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
12978
+ this.debounceTimer = setTimeout(() => {
12979
+ this.flushEvents();
12980
+ }, this.debounceMs);
12981
+ }
12982
+ /**
12983
+ * 分发事件给订阅者
12815
12984
  */
12816
- track(state) {
12817
- if (!this.destroyed) this.dependencies.add(state);
12985
+ flushEvents() {
12986
+ const events = this.pendingEvents;
12987
+ this.pendingEvents = [];
12988
+ this.debounceTimer = null;
12989
+ if (events.length === 0) return;
12990
+ for (const sub of this.pathSubscriptions.values()) {
12991
+ const matchedEvents = events.filter((e) => this.matchPath(e, sub));
12992
+ if (matchedEvents.length > 0) try {
12993
+ sub.callback(matchedEvents);
12994
+ } catch (err) {
12995
+ console.error(`[ProjectWatcher] Callback error for ${sub.path}:`, err);
12996
+ }
12997
+ }
12818
12998
  }
12819
12999
  /**
12820
- * 通知变更
12821
- * 由 ReactiveState.set() 调用
13000
+ * 检查事件是否匹配订阅
12822
13001
  */
12823
- notifyChange() {
12824
- if (!this.destroyed && this.changePromise) this.changePromise.resolve();
13002
+ matchPath(event, sub) {
13003
+ const eventPath = event.path;
13004
+ if (sub.watchChildren) return eventPath.startsWith(sub.path + "/") || eventPath === sub.path;
13005
+ else {
13006
+ const eventDir = dirname$1(eventPath);
13007
+ return eventPath === sub.path || eventDir === sub.path;
13008
+ }
12825
13009
  }
12826
13010
  /**
12827
- * 运行响应式任务流
12828
- * 每次依赖变更时重新执行任务并 yield 结果
13011
+ * 同步订阅路径变更(watcher 必须已初始化)
12829
13012
  *
12830
- * @param task 要执行的异步任务
12831
- * @param signal 用于取消的 AbortSignal
13013
+ * 这是同步版本,用于在 watcher 已初始化后快速注册订阅。
13014
+ * 如果 watcher 未初始化,抛出错误。
13015
+ *
13016
+ * @param path 要监听的路径
13017
+ * @param callback 变更回调
13018
+ * @param options 订阅选项
13019
+ * @returns 取消订阅函数
13020
+ */
13021
+ subscribeSync(path$1, callback, options = {}) {
13022
+ if (!this.initialized) throw new Error("ProjectWatcher not initialized. Call init() first.");
13023
+ const normalizedPath = getRealPath$1(path$1);
13024
+ const id = Symbol();
13025
+ this.pathSubscriptions.set(id, {
13026
+ path: normalizedPath,
13027
+ watchChildren: options.watchChildren ?? false,
13028
+ callback
13029
+ });
13030
+ return () => {
13031
+ this.pathSubscriptions.delete(id);
13032
+ };
13033
+ }
13034
+ /**
13035
+ * 订阅路径变更(异步版本,自动初始化)
13036
+ *
13037
+ * @param path 要监听的路径
13038
+ * @param callback 变更回调
13039
+ * @param options 订阅选项
13040
+ * @returns 取消订阅函数
12832
13041
  */
12833
- async *stream(task, signal) {
12834
- try {
12835
- while (!signal?.aborted && !this.destroyed) {
12836
- this.clearDependencies();
12837
- this.changePromise = createPromiseWithResolvers();
12838
- yield await contextStorage.run(this, task);
12839
- if (this.dependencies.size === 0) break;
12840
- await Promise.race([this.changePromise.promise, signal ? this.waitForAbort(signal) : new Promise(() => {})]);
12841
- if (signal?.aborted) break;
12842
- }
12843
- } finally {
12844
- this.destroy();
12845
- }
13042
+ async subscribe(path$1, callback, options = {}) {
13043
+ await this.init();
13044
+ return this.subscribeSync(path$1, callback, options);
12846
13045
  }
12847
13046
  /**
12848
- * 执行一次任务(非响应式)
12849
- * 用于初始数据获取
13047
+ * 获取当前订阅数量(用于调试)
12850
13048
  */
12851
- async runOnce(task) {
12852
- return contextStorage.run(this, task);
13049
+ get subscriptionCount() {
13050
+ return this.pathSubscriptions.size;
12853
13051
  }
12854
13052
  /**
12855
- * 清理依赖
13053
+ * 检查是否已初始化
12856
13054
  */
12857
- clearDependencies() {
12858
- for (const state of this.dependencies) state.unsubscribe(this);
12859
- this.dependencies.clear();
13055
+ get isInitialized() {
13056
+ return this.initialized;
12860
13057
  }
12861
13058
  /**
12862
- * 销毁上下文
12863
- * @param reason 可选的销毁原因,如果提供则 reject changePromise
13059
+ * 启动健康检查定时器
12864
13060
  */
12865
- destroy(reason) {
12866
- this.destroyed = true;
12867
- this.clearDependencies();
12868
- if (reason && this.changePromise) this.changePromise.reject(reason);
12869
- this.changePromise = void 0;
13061
+ startHealthCheck() {
13062
+ this.stopHealthCheck();
13063
+ this.healthCheckTimer = setInterval(() => {
13064
+ this.performHealthCheck();
13065
+ }, HEALTH_CHECK_INTERVAL_MS);
13066
+ this.healthCheckTimer.unref();
12870
13067
  }
12871
13068
  /**
12872
- * 等待 AbortSignal
13069
+ * 停止健康检查定时器
12873
13070
  */
12874
- waitForAbort(signal) {
12875
- return new Promise((_, reject) => {
12876
- if (signal.aborted) {
12877
- reject(new DOMException("Aborted", "AbortError"));
12878
- return;
13071
+ stopHealthCheck() {
13072
+ if (this.healthCheckTimer) {
13073
+ clearInterval(this.healthCheckTimer);
13074
+ this.healthCheckTimer = null;
13075
+ }
13076
+ this.healthCheckPending = false;
13077
+ }
13078
+ /**
13079
+ * 执行健康检查
13080
+ *
13081
+ * 工作流程:
13082
+ * 1. 如果最近有事件,无需检查
13083
+ * 2. 如果上次探测还在等待中,说明 watcher 可能失效,尝试重建
13084
+ * 3. 否则,创建临时文件触发事件,等待下次检查验证
13085
+ */
13086
+ async performHealthCheck() {
13087
+ if (Date.now() - this.lastEventTime < HEALTH_CHECK_INTERVAL_MS) {
13088
+ this.healthCheckPending = false;
13089
+ return;
13090
+ }
13091
+ if (this.healthCheckPending) {
13092
+ console.warn("[ProjectWatcher] Health check failed, watcher appears stale. Reinitializing...");
13093
+ await this.reinitialize();
13094
+ return;
13095
+ }
13096
+ this.healthCheckPending = true;
13097
+ this.sendProbe();
13098
+ }
13099
+ /**
13100
+ * 发送探测:通过 utimesSync 修改项目目录的时间戳来触发 watcher 事件
13101
+ */
13102
+ sendProbe() {
13103
+ try {
13104
+ const now = /* @__PURE__ */ new Date();
13105
+ utimesSync(this.projectDir, now, now);
13106
+ } catch {}
13107
+ }
13108
+ /**
13109
+ * 重新初始化 watcher
13110
+ */
13111
+ async reinitialize() {
13112
+ this.stopHealthCheck();
13113
+ if (this.subscription) {
13114
+ try {
13115
+ await this.subscription.unsubscribe();
13116
+ } catch {}
13117
+ this.subscription = null;
13118
+ }
13119
+ this.initialized = false;
13120
+ this.initPromise = null;
13121
+ this.healthCheckPending = false;
13122
+ if (!existsSync(this.projectDir)) {
13123
+ console.warn("[ProjectWatcher] Project directory does not exist, waiting for it to be created...");
13124
+ this.waitForProjectDir();
13125
+ return;
13126
+ }
13127
+ try {
13128
+ await this.init();
13129
+ console.log("[ProjectWatcher] Reinitialized successfully");
13130
+ } catch (err) {
13131
+ console.error("[ProjectWatcher] Failed to reinitialize:", err);
13132
+ setTimeout(() => this.reinitialize(), HEALTH_CHECK_INTERVAL_MS);
13133
+ }
13134
+ }
13135
+ /**
13136
+ * 等待项目目录被创建
13137
+ */
13138
+ waitForProjectDir() {
13139
+ const checkInterval = setInterval(() => {
13140
+ if (existsSync(this.projectDir)) {
13141
+ clearInterval(checkInterval);
13142
+ console.log("[ProjectWatcher] Project directory created, reinitializing...");
13143
+ this.reinitialize();
12879
13144
  }
12880
- signal.addEventListener("abort", () => {
12881
- reject(new DOMException("Aborted", "AbortError"));
12882
- });
12883
- });
13145
+ }, HEALTH_CHECK_INTERVAL_MS);
13146
+ checkInterval.unref();
13147
+ }
13148
+ /**
13149
+ * 关闭 watcher
13150
+ */
13151
+ async close() {
13152
+ this.stopHealthCheck();
13153
+ if (this.debounceTimer) {
13154
+ clearTimeout(this.debounceTimer);
13155
+ this.debounceTimer = null;
13156
+ }
13157
+ if (this.subscription) {
13158
+ await this.subscription.unsubscribe();
13159
+ this.subscription = null;
13160
+ }
13161
+ this.pathSubscriptions.clear();
13162
+ this.pendingEvents = [];
13163
+ this.initialized = false;
13164
+ this.initPromise = null;
12884
13165
  }
12885
13166
  };
13167
+ /**
13168
+ * 全局 ProjectWatcher 实例缓存
13169
+ * key: 项目目录路径
13170
+ */
13171
+ const watcherCache = /* @__PURE__ */ new Map();
13172
+ /**
13173
+ * 获取或创建项目监听器
13174
+ */
13175
+ function getProjectWatcher(projectDir, options) {
13176
+ const normalizedDir = getRealPath$1(projectDir);
13177
+ let watcher = watcherCache.get(normalizedDir);
13178
+ if (!watcher) {
13179
+ watcher = new ProjectWatcher(normalizedDir, options);
13180
+ watcherCache.set(normalizedDir, watcher);
13181
+ }
13182
+ return watcher;
13183
+ }
12886
13184
 
12887
13185
  //#endregion
12888
13186
  //#region ../core/src/reactive-fs/watcher-pool.ts
12889
- /** 全局监听器池,共享同一路径的监听器 */
12890
- const watcherPool = /* @__PURE__ */ new Map();
12891
- /** 防抖定时器 */
12892
- const debounceTimers = /* @__PURE__ */ new Map();
13187
+ /**
13188
+ * 获取路径的真实路径(解析符号链接)
13189
+ */
13190
+ function getRealPath(path$1) {
13191
+ try {
13192
+ return realpathSync(resolve$1(path$1));
13193
+ } catch {
13194
+ return resolve$1(path$1);
13195
+ }
13196
+ }
13197
+ /**
13198
+ * 全局 ProjectWatcher 实例
13199
+ * 通过 initWatcherPool 初始化
13200
+ */
13201
+ let globalProjectWatcher = null;
13202
+ let globalProjectDir = null;
12893
13203
  /** 默认防抖时间 (ms) */
12894
13204
  const DEBOUNCE_MS = 100;
13205
+ /** 路径订阅缓存 */
13206
+ const subscriptionCache = /* @__PURE__ */ new Map();
13207
+ /** 防抖定时器 */
13208
+ const debounceTimers = /* @__PURE__ */ new Map();
13209
+ /**
13210
+ * 初始化 watcher pool
13211
+ *
13212
+ * 必须在使用 acquireWatcher 之前调用。
13213
+ * 通常由 server 在启动时调用。
13214
+ *
13215
+ * @param projectDir 项目根目录
13216
+ */
13217
+ async function initWatcherPool(projectDir) {
13218
+ const normalizedDir = getRealPath(projectDir);
13219
+ if (globalProjectWatcher && globalProjectDir === normalizedDir) return;
13220
+ if (globalProjectWatcher) await globalProjectWatcher.close();
13221
+ globalProjectDir = normalizedDir;
13222
+ globalProjectWatcher = getProjectWatcher(normalizedDir);
13223
+ await globalProjectWatcher.init();
13224
+ }
12895
13225
  /**
12896
13226
  * 获取或创建文件/目录监听器
12897
13227
  *
12898
13228
  * 特性:
12899
- * - 同一路径共享监听器
13229
+ * - 使用 @parcel/watcher 监听项目根目录
13230
+ * - 自动处理新创建的目录(解决 init 后无法监听的问题)
13231
+ * - 同一路径共享订阅
12900
13232
  * - 引用计数管理生命周期
12901
13233
  * - 内置防抖机制
12902
13234
  *
@@ -12906,51 +13238,50 @@ const DEBOUNCE_MS = 100;
12906
13238
  * @returns 释放函数,调用后取消订阅
12907
13239
  */
12908
13240
  function acquireWatcher(path$1, onChange, options = {}) {
12909
- const normalizedPath = resolve$1(path$1);
13241
+ if (!globalProjectWatcher || !globalProjectWatcher.isInitialized) {
13242
+ console.warn("[watcher-pool] ProjectWatcher not initialized. Call initWatcherPool first.");
13243
+ return () => {};
13244
+ }
13245
+ const normalizedPath = getRealPath(path$1);
12910
13246
  const debounceMs = options.debounceMs ?? DEBOUNCE_MS;
12911
- let entry = watcherPool.get(normalizedPath);
12912
- if (!entry) {
12913
- const watcher = watch(normalizedPath, {
12914
- recursive: options.recursive ?? false,
12915
- persistent: false
12916
- }, () => {
12917
- const existingTimer = debounceTimers.get(normalizedPath);
13247
+ const isRecursive = options.recursive ?? false;
13248
+ const cacheKey$1 = `${normalizedPath}:${isRecursive}`;
13249
+ let subscription = subscriptionCache.get(cacheKey$1);
13250
+ if (!subscription) {
13251
+ const unsubscribe = globalProjectWatcher.subscribeSync(normalizedPath, () => {
13252
+ const existingTimer = debounceTimers.get(cacheKey$1);
12918
13253
  if (existingTimer) clearTimeout(existingTimer);
12919
13254
  const timer = setTimeout(() => {
12920
- debounceTimers.delete(normalizedPath);
12921
- const currentEntry = watcherPool.get(normalizedPath);
12922
- if (currentEntry) for (const cb of currentEntry.callbacks) try {
13255
+ debounceTimers.delete(cacheKey$1);
13256
+ const currentSub = subscriptionCache.get(cacheKey$1);
13257
+ if (currentSub) for (const cb of currentSub.callbacks) try {
12923
13258
  cb();
12924
13259
  } catch (err) {
12925
- console.error(`Watcher callback error for ${normalizedPath}:`, err);
13260
+ console.error(`[watcher-pool] Callback error for ${normalizedPath}:`, err);
12926
13261
  }
12927
13262
  }, debounceMs);
12928
- debounceTimers.set(normalizedPath, timer);
12929
- });
12930
- watcher.on("error", (err) => {
12931
- console.error(`Watcher error for ${normalizedPath}:`, err);
12932
- });
12933
- entry = {
12934
- watcher,
12935
- refCount: 0,
12936
- callbacks: /* @__PURE__ */ new Set()
13263
+ debounceTimers.set(cacheKey$1, timer);
13264
+ }, { watchChildren: isRecursive });
13265
+ subscription = {
13266
+ path: normalizedPath,
13267
+ callbacks: /* @__PURE__ */ new Set(),
13268
+ unsubscribe,
13269
+ onError: options.onError
12937
13270
  };
12938
- watcherPool.set(normalizedPath, entry);
13271
+ subscriptionCache.set(cacheKey$1, subscription);
12939
13272
  }
12940
- entry.refCount++;
12941
- entry.callbacks.add(onChange);
13273
+ subscription.callbacks.add(onChange);
12942
13274
  return () => {
12943
- const currentEntry = watcherPool.get(normalizedPath);
12944
- if (!currentEntry) return;
12945
- currentEntry.callbacks.delete(onChange);
12946
- currentEntry.refCount--;
12947
- if (currentEntry.refCount === 0) {
12948
- currentEntry.watcher.close();
12949
- watcherPool.delete(normalizedPath);
12950
- const timer = debounceTimers.get(normalizedPath);
13275
+ const currentSub = subscriptionCache.get(cacheKey$1);
13276
+ if (!currentSub) return;
13277
+ currentSub.callbacks.delete(onChange);
13278
+ if (currentSub.callbacks.size === 0) {
13279
+ currentSub.unsubscribe();
13280
+ subscriptionCache.delete(cacheKey$1);
13281
+ const timer = debounceTimers.get(cacheKey$1);
12951
13282
  if (timer) {
12952
13283
  clearTimeout(timer);
12953
- debounceTimers.delete(normalizedPath);
13284
+ debounceTimers.delete(cacheKey$1);
12954
13285
  }
12955
13286
  }
12956
13287
  };
@@ -12959,9 +13290,9 @@ function acquireWatcher(path$1, onChange, options = {}) {
12959
13290
  //#endregion
12960
13291
  //#region ../core/src/reactive-fs/reactive-fs.ts
12961
13292
  /** 状态缓存:路径 -> ReactiveState */
12962
- const stateCache = /* @__PURE__ */ new Map();
13293
+ const stateCache$1 = /* @__PURE__ */ new Map();
12963
13294
  /** 监听器释放函数缓存 */
12964
- const releaseCache = /* @__PURE__ */ new Map();
13295
+ const releaseCache$1 = /* @__PURE__ */ new Map();
12965
13296
  /**
12966
13297
  * 响应式读取文件内容
12967
13298
  *
@@ -12969,6 +13300,7 @@ const releaseCache = /* @__PURE__ */ new Map();
12969
13300
  * - 自动注册文件监听
12970
13301
  * - 文件变更时自动更新状态
12971
13302
  * - 在 ReactiveContext 中调用时自动追踪依赖
13303
+ * - 支持监听尚未创建的文件(通过 @parcel/watcher)
12972
13304
  *
12973
13305
  * @param filepath 文件路径
12974
13306
  * @returns 文件内容,文件不存在时返回 null
@@ -12983,15 +13315,18 @@ async function reactiveReadFile(filepath) {
12983
13315
  return null;
12984
13316
  }
12985
13317
  };
12986
- let state = stateCache.get(key);
13318
+ let state = stateCache$1.get(key);
12987
13319
  if (!state) {
12988
13320
  state = new ReactiveState(await getValue());
12989
- stateCache.set(key, state);
13321
+ stateCache$1.set(key, state);
12990
13322
  const release = acquireWatcher(dirname$1(normalizedPath), async () => {
12991
13323
  const newValue = await getValue();
12992
13324
  state.set(newValue);
12993
- });
12994
- releaseCache.set(key, release);
13325
+ }, { onError: () => {
13326
+ stateCache$1.delete(key);
13327
+ releaseCache$1.delete(key);
13328
+ } });
13329
+ releaseCache$1.set(key, release);
12995
13330
  }
12996
13331
  return state.get();
12997
13332
  }
@@ -13002,6 +13337,7 @@ async function reactiveReadFile(filepath) {
13002
13337
  * - 自动注册目录监听
13003
13338
  * - 目录变更时自动更新状态
13004
13339
  * - 在 ReactiveContext 中调用时自动追踪依赖
13340
+ * - 支持监听尚未创建的目录(通过 @parcel/watcher)
13005
13341
  *
13006
13342
  * @param dirpath 目录路径
13007
13343
  * @param options 选项
@@ -13023,44 +13359,21 @@ async function reactiveReadDir(dirpath, options = {}) {
13023
13359
  return [];
13024
13360
  }
13025
13361
  };
13026
- let state = stateCache.get(key);
13362
+ let state = stateCache$1.get(key);
13027
13363
  if (!state) {
13028
13364
  state = new ReactiveState(await getValue(), { equals: (a, b) => a.length === b.length && a.every((v, i) => v === b[i]) });
13029
- stateCache.set(key, state);
13365
+ stateCache$1.set(key, state);
13030
13366
  const release = acquireWatcher(normalizedPath, async () => {
13031
13367
  const newValue = await getValue();
13032
13368
  state.set(newValue);
13033
- }, { recursive: true });
13034
- releaseCache.set(key, release);
13035
- }
13036
- return state.get();
13037
- }
13038
- /**
13039
- * 响应式检查路径是否存在
13040
- *
13041
- * @param path 路径
13042
- * @returns 是否存在
13043
- */
13044
- async function reactiveExists(path$1) {
13045
- const normalizedPath = resolve$1(path$1);
13046
- const key = `exists:${normalizedPath}`;
13047
- const getValue = async () => {
13048
- try {
13049
- await stat(normalizedPath);
13050
- return true;
13051
- } catch {
13052
- return false;
13053
- }
13054
- };
13055
- let state = stateCache.get(key);
13056
- if (!state) {
13057
- state = new ReactiveState(await getValue());
13058
- stateCache.set(key, state);
13059
- const release = acquireWatcher(dirname$1(normalizedPath), async () => {
13060
- const newValue = await getValue();
13061
- state.set(newValue);
13369
+ }, {
13370
+ recursive: true,
13371
+ onError: () => {
13372
+ stateCache$1.delete(key);
13373
+ releaseCache$1.delete(key);
13374
+ }
13062
13375
  });
13063
- releaseCache.set(key, release);
13376
+ releaseCache$1.set(key, release);
13064
13377
  }
13065
13378
  return state.get();
13066
13379
  }
@@ -13086,19 +13399,22 @@ async function reactiveStat(path$1) {
13086
13399
  return null;
13087
13400
  }
13088
13401
  };
13089
- let state = stateCache.get(key);
13402
+ let state = stateCache$1.get(key);
13090
13403
  if (!state) {
13091
13404
  state = new ReactiveState(await getValue(), { equals: (a, b) => {
13092
13405
  if (a === null && b === null) return true;
13093
13406
  if (a === null || b === null) return false;
13094
13407
  return a.isDirectory === b.isDirectory && a.isFile === b.isFile && a.mtime === b.mtime && a.birthtime === b.birthtime;
13095
13408
  } });
13096
- stateCache.set(key, state);
13409
+ stateCache$1.set(key, state);
13097
13410
  const release = acquireWatcher(dirname$1(normalizedPath), async () => {
13098
13411
  const newValue = await getValue();
13099
13412
  state.set(newValue);
13100
- });
13101
- releaseCache.set(key, release);
13413
+ }, { onError: () => {
13414
+ stateCache$1.delete(key);
13415
+ releaseCache$1.delete(key);
13416
+ } });
13417
+ releaseCache$1.set(key, release);
13102
13418
  }
13103
13419
  return state.get();
13104
13420
  }
@@ -13607,7 +13923,7 @@ var OpenSpecWatcher = class extends EventEmitter {
13607
13923
  */
13608
13924
  watchDir(dir, callback) {
13609
13925
  try {
13610
- const watcher = watch$1(dir, { recursive: true }, (eventType, filename) => {
13926
+ const watcher = watch(dir, { recursive: true }, (eventType, filename) => {
13611
13927
  if (filename) callback(filename, eventType);
13612
13928
  });
13613
13929
  watcher.on("error", (error) => {
@@ -13634,22 +13950,143 @@ var OpenSpecWatcher = class extends EventEmitter {
13634
13950
 
13635
13951
  //#endregion
13636
13952
  //#region ../core/src/config.ts
13953
+ const execAsync = promisify(exec);
13954
+ /** 默认的 fallback CLI 命令(数组形式) */
13955
+ const FALLBACK_CLI_COMMAND = ["npx", "@fission-ai/openspec"];
13956
+ /** 全局 openspec 命令(数组形式) */
13957
+ const GLOBAL_CLI_COMMAND = ["openspec"];
13958
+ /** 缓存检测到的 CLI 命令 */
13959
+ let detectedCliCommand = null;
13960
+ /**
13961
+ * 解析 CLI 命令字符串为数组
13962
+ *
13963
+ * 支持两种格式:
13964
+ * 1. JSON 数组:以 `[` 开头,如 `["npx", "@fission-ai/openspec"]`
13965
+ * 2. 简单字符串:用空格分割,如 `npx @fission-ai/openspec`
13966
+ *
13967
+ * 注意:简单字符串解析不支持带引号的参数,如需复杂命令请使用 JSON 数组格式
13968
+ */
13969
+ function parseCliCommand(command) {
13970
+ const trimmed = command.trim();
13971
+ if (trimmed.startsWith("[")) try {
13972
+ const parsed = JSON.parse(trimmed);
13973
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) return parsed;
13974
+ throw new Error("Invalid JSON array: expected array of strings");
13975
+ } catch (err) {
13976
+ throw new Error(`Failed to parse CLI command as JSON array: ${err instanceof Error ? err.message : err}`);
13977
+ }
13978
+ return trimmed.split(/\s+/).filter(Boolean);
13979
+ }
13980
+ /**
13981
+ * 比较两个语义化版本号
13982
+ * @returns 正数表示 a > b,负数表示 a < b,0 表示相等
13983
+ */
13984
+ function compareVersions(a, b) {
13985
+ const parseVersion = (v) => {
13986
+ return v.replace(/^v/, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
13987
+ };
13988
+ const aParts = parseVersion(a);
13989
+ const bParts = parseVersion(b);
13990
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
13991
+ const aVal = aParts[i] ?? 0;
13992
+ const bVal = bParts[i] ?? 0;
13993
+ if (aVal !== bVal) return aVal - bVal;
13994
+ }
13995
+ return 0;
13996
+ }
13997
+ /**
13998
+ * 获取 npx 可用的最新版本
13999
+ *
14000
+ * 使用 `npx @fission-ai/openspec --version` 获取最新版本
14001
+ * 这会下载并执行最新版本,所以超时时间较长
14002
+ */
14003
+ async function fetchLatestVersion() {
14004
+ try {
14005
+ const { stdout } = await execAsync("npx @fission-ai/openspec --version", { timeout: 6e4 });
14006
+ return stdout.trim();
14007
+ } catch {
14008
+ return;
14009
+ }
14010
+ }
14011
+ /**
14012
+ * 嗅探全局 openspec 命令(无缓存)
14013
+ *
14014
+ * 使用 `openspec --version` 检测是否有全局命令可用。
14015
+ * 同时检查 npm registry 上的最新版本。
14016
+ * 每次调用都会重新检测,不使用缓存。
14017
+ */
14018
+ async function sniffGlobalCli() {
14019
+ const [localResult, latestVersion] = await Promise.all([execAsync("openspec --version", { timeout: 1e4 }).catch((err) => ({ error: err })), fetchLatestVersion()]);
14020
+ if ("error" in localResult) {
14021
+ const error = localResult.error instanceof Error ? localResult.error.message : String(localResult.error);
14022
+ if (error.includes("not found") || error.includes("ENOENT") || error.includes("not recognized")) return {
14023
+ hasGlobal: false,
14024
+ latestVersion,
14025
+ hasUpdate: !!latestVersion
14026
+ };
14027
+ return {
14028
+ hasGlobal: false,
14029
+ latestVersion,
14030
+ hasUpdate: !!latestVersion,
14031
+ error
14032
+ };
14033
+ }
14034
+ const version = localResult.stdout.trim();
14035
+ detectedCliCommand = GLOBAL_CLI_COMMAND;
14036
+ return {
14037
+ hasGlobal: true,
14038
+ version,
14039
+ latestVersion,
14040
+ hasUpdate: latestVersion ? compareVersions(latestVersion, version) > 0 : false
14041
+ };
14042
+ }
14043
+ /**
14044
+ * 检测全局安装的 openspec 命令
14045
+ * 优先使用全局命令,fallback 到 npx
14046
+ *
14047
+ * @returns CLI 命令数组
14048
+ */
14049
+ async function detectCliCommand() {
14050
+ if (detectedCliCommand !== null) return detectedCliCommand;
14051
+ try {
14052
+ await execAsync(`${process.platform === "win32" ? "where" : "which"} openspec`);
14053
+ detectedCliCommand = GLOBAL_CLI_COMMAND;
14054
+ return detectedCliCommand;
14055
+ } catch {
14056
+ detectedCliCommand = FALLBACK_CLI_COMMAND;
14057
+ return detectedCliCommand;
14058
+ }
14059
+ }
14060
+ /**
14061
+ * 获取默认 CLI 命令(异步,带检测)
14062
+ *
14063
+ * @returns CLI 命令数组,如 `['openspec']` 或 `['npx', '@fission-ai/openspec']`
14064
+ */
14065
+ async function getDefaultCliCommand() {
14066
+ return detectCliCommand();
14067
+ }
14068
+ /**
14069
+ * 获取默认 CLI 命令的字符串形式(用于 UI 显示)
14070
+ */
14071
+ async function getDefaultCliCommandString() {
14072
+ return (await detectCliCommand()).join(" ");
14073
+ }
13637
14074
  /**
13638
14075
  * OpenSpecUI 配置 Schema
13639
14076
  *
13640
14077
  * 存储在 openspec/.openspecui.json 中,利用文件监听实现响应式更新
13641
14078
  */
13642
14079
  const OpenSpecUIConfigSchema = objectType({
13643
- cli: objectType({ command: stringType().default("npx @fission-ai/openspec") }).default({}),
14080
+ cli: objectType({ command: stringType().optional() }).default({}),
13644
14081
  ui: objectType({ theme: enumType([
13645
14082
  "light",
13646
14083
  "dark",
13647
14084
  "system"
13648
14085
  ]).default("system") }).default({})
13649
14086
  });
13650
- /** 默认配置 */
14087
+ /** 默认配置(静态,用于测试和类型) */
13651
14088
  const DEFAULT_CONFIG = {
13652
- cli: { command: "npx @fission-ai/openspec" },
14089
+ cli: {},
13653
14090
  ui: { theme: "system" }
13654
14091
  };
13655
14092
  /**
@@ -13705,10 +14142,22 @@ var ConfigManager = class {
13705
14142
  await writeFile(this.configPath, JSON.stringify(merged, null, 2), "utf-8");
13706
14143
  }
13707
14144
  /**
13708
- * 获取 CLI 命令
14145
+ * 获取 CLI 命令(数组形式)
14146
+ *
14147
+ * 优先级:配置文件 > 全局 openspec 命令 > npx fallback
14148
+ *
14149
+ * @returns CLI 命令数组,如 `['openspec']` 或 `['npx', '@fission-ai/openspec']`
13709
14150
  */
13710
14151
  async getCliCommand() {
13711
- return (await this.readConfig()).cli.command;
14152
+ const config = await this.readConfig();
14153
+ if (config.cli.command) return parseCliCommand(config.cli.command);
14154
+ return getDefaultCliCommand();
14155
+ }
14156
+ /**
14157
+ * 获取 CLI 命令的字符串形式(用于 UI 显示)
14158
+ */
14159
+ async getCliCommandString() {
14160
+ return (await this.getCliCommand()).join(" ");
13712
14161
  }
13713
14162
  /**
13714
14163
  * 设置 CLI 命令
@@ -13725,10 +14174,11 @@ var ConfigManager = class {
13725
14174
  *
13726
14175
  * 负责调用外部 openspec CLI 命令。
13727
14176
  * 命令前缀从 ConfigManager 获取,支持:
13728
- * - npx @fission-ai/openspec (默认)
13729
- * - bunx openspec
13730
- * - openspec (本地安装)
13731
- * - 自定义命令 (如 xspec)
14177
+ * - ['npx', '@fission-ai/openspec'] (默认)
14178
+ * - ['openspec'] (全局安装)
14179
+ * - 自定义数组或字符串
14180
+ *
14181
+ * 注意:所有命令都使用 shell: false 执行,避免 shell 注入风险
13732
14182
  */
13733
14183
  var CliExecutor = class {
13734
14184
  constructor(configManager, projectDir) {
@@ -13745,19 +14195,26 @@ var CliExecutor = class {
13745
14195
  return env;
13746
14196
  }
13747
14197
  /**
14198
+ * 构建完整命令数组
14199
+ *
14200
+ * @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
14201
+ * @returns [command, ...commandArgs, ...args]
14202
+ */
14203
+ async buildCommandArray(args) {
14204
+ return [...await this.configManager.getCliCommand(), ...args];
14205
+ }
14206
+ /**
13748
14207
  * 执行 CLI 命令
13749
14208
  *
13750
14209
  * @param args CLI 参数,如 ['init'] 或 ['archive', 'change-id']
13751
14210
  * @returns 执行结果
13752
14211
  */
13753
14212
  async execute(args) {
13754
- const parts = (await this.configManager.getCliCommand()).split(/\s+/);
13755
- const cmd = parts[0];
13756
- const cmdArgs = [...parts.slice(1), ...args];
14213
+ const [cmd, ...cmdArgs] = await this.buildCommandArray(args);
13757
14214
  return new Promise((resolve$2) => {
13758
14215
  const child = spawn(cmd, cmdArgs, {
13759
14216
  cwd: this.projectDir,
13760
- shell: true,
14217
+ shell: false,
13761
14218
  env: this.getCleanEnv()
13762
14219
  });
13763
14220
  let stdout = "";
@@ -13793,7 +14250,11 @@ var CliExecutor = class {
13793
14250
  */
13794
14251
  async init(tools = "all") {
13795
14252
  const toolsArg = Array.isArray(tools) ? tools.join(",") : tools;
13796
- return this.execute(["init", `--tools=${toolsArg}`]);
14253
+ return this.execute([
14254
+ "init",
14255
+ "--tools",
14256
+ toolsArg
14257
+ ]);
13797
14258
  }
13798
14259
  /**
13799
14260
  * 执行 openspec archive <changeId>(非交互式)
@@ -13821,25 +14282,154 @@ var CliExecutor = class {
13821
14282
  return this.execute(args);
13822
14283
  }
13823
14284
  /**
13824
- * 检查 CLI 是否可用
13825
- */
13826
- async checkAvailability() {
13827
- try {
13828
- const result = await this.execute(["--version"]);
13829
- if (result.success) return {
13830
- available: true,
13831
- version: result.stdout.trim()
13832
- };
13833
- return {
13834
- available: false,
13835
- error: result.stderr || "Unknown error"
13836
- };
13837
- } catch (err) {
13838
- return {
13839
- available: false,
13840
- error: err instanceof Error ? err.message : "Unknown error"
13841
- };
13842
- }
14285
+ * 检查 CLI 是否可用
14286
+ * @param timeout 超时时间(毫秒),默认 10 秒
14287
+ */
14288
+ async checkAvailability(timeout = 1e4) {
14289
+ try {
14290
+ const result = await Promise.race([this.execute(["--version"]), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("CLI check timed out")), timeout))]);
14291
+ if (result.success) return {
14292
+ available: true,
14293
+ version: result.stdout.trim()
14294
+ };
14295
+ return {
14296
+ available: false,
14297
+ error: result.stderr || "Unknown error"
14298
+ };
14299
+ } catch (err) {
14300
+ return {
14301
+ available: false,
14302
+ error: err instanceof Error ? err.message : "Unknown error"
14303
+ };
14304
+ }
14305
+ }
14306
+ /**
14307
+ * 流式执行 CLI 命令
14308
+ *
14309
+ * @param args CLI 参数
14310
+ * @param onEvent 事件回调
14311
+ * @returns 取消函数
14312
+ */
14313
+ async executeStream(args, onEvent) {
14314
+ const fullCommand = await this.buildCommandArray(args);
14315
+ const [cmd, ...cmdArgs] = fullCommand;
14316
+ onEvent({
14317
+ type: "command",
14318
+ data: fullCommand.join(" ")
14319
+ });
14320
+ const child = spawn(cmd, cmdArgs, {
14321
+ cwd: this.projectDir,
14322
+ shell: false,
14323
+ env: this.getCleanEnv()
14324
+ });
14325
+ child.stdout?.on("data", (data) => {
14326
+ onEvent({
14327
+ type: "stdout",
14328
+ data: data.toString()
14329
+ });
14330
+ });
14331
+ child.stderr?.on("data", (data) => {
14332
+ onEvent({
14333
+ type: "stderr",
14334
+ data: data.toString()
14335
+ });
14336
+ });
14337
+ child.on("close", (exitCode) => {
14338
+ onEvent({
14339
+ type: "exit",
14340
+ exitCode
14341
+ });
14342
+ });
14343
+ child.on("error", (err) => {
14344
+ onEvent({
14345
+ type: "stderr",
14346
+ data: err.message
14347
+ });
14348
+ onEvent({
14349
+ type: "exit",
14350
+ exitCode: null
14351
+ });
14352
+ });
14353
+ return () => {
14354
+ child.kill();
14355
+ };
14356
+ }
14357
+ /**
14358
+ * 流式执行 openspec init
14359
+ */
14360
+ initStream(tools, onEvent) {
14361
+ const toolsArg = Array.isArray(tools) ? tools.join(",") : tools;
14362
+ return this.executeStream([
14363
+ "init",
14364
+ "--tools",
14365
+ toolsArg
14366
+ ], onEvent);
14367
+ }
14368
+ /**
14369
+ * 流式执行 openspec archive
14370
+ */
14371
+ archiveStream(changeId, options, onEvent) {
14372
+ const args = [
14373
+ "archive",
14374
+ "-y",
14375
+ changeId
14376
+ ];
14377
+ if (options.skipSpecs) args.push("--skip-specs");
14378
+ if (options.noValidate) args.push("--no-validate");
14379
+ return this.executeStream(args, onEvent);
14380
+ }
14381
+ /**
14382
+ * 流式执行任意命令(数组形式)
14383
+ *
14384
+ * 用于执行不需要 openspec CLI 前缀的命令,如 npm install。
14385
+ * 使用 shell: false 避免 shell 注入风险。
14386
+ *
14387
+ * @param command 命令数组,如 ['npm', 'install', '-g', '@fission-ai/openspec']
14388
+ * @param onEvent 事件回调
14389
+ * @returns 取消函数
14390
+ */
14391
+ executeCommandStream(command, onEvent) {
14392
+ const [cmd, ...cmdArgs] = command;
14393
+ onEvent({
14394
+ type: "command",
14395
+ data: command.join(" ")
14396
+ });
14397
+ const child = spawn(cmd, cmdArgs, {
14398
+ cwd: this.projectDir,
14399
+ shell: false,
14400
+ env: this.getCleanEnv()
14401
+ });
14402
+ child.stdout?.on("data", (data) => {
14403
+ onEvent({
14404
+ type: "stdout",
14405
+ data: data.toString()
14406
+ });
14407
+ });
14408
+ child.stderr?.on("data", (data) => {
14409
+ onEvent({
14410
+ type: "stderr",
14411
+ data: data.toString()
14412
+ });
14413
+ });
14414
+ child.on("close", (exitCode) => {
14415
+ onEvent({
14416
+ type: "exit",
14417
+ exitCode
14418
+ });
14419
+ });
14420
+ child.on("error", (err) => {
14421
+ onEvent({
14422
+ type: "stderr",
14423
+ data: err.message
14424
+ });
14425
+ onEvent({
14426
+ type: "exit",
14427
+ exitCode: null
14428
+ });
14429
+ });
14430
+ return () => {
14431
+ child.kill();
14432
+ };
13843
14433
  }
13844
14434
  };
13845
14435
 
@@ -13848,121 +14438,312 @@ var CliExecutor = class {
13848
14438
  /**
13849
14439
  * 工具配置检测模块
13850
14440
  *
13851
- * 基于 @fission-ai/openspec 的 configurators 实现
14441
+ * 完全对齐 @fission-ai/openspec 的官方实现
13852
14442
  * 用于检测项目中已配置的 AI 工具
13853
14443
  *
13854
- * 重要:使用响应式文件系统 (reactiveExists) 实现,
14444
+ * 重要:使用响应式文件系统实现,监听配置目录,
13855
14445
  * 当配置文件变化时会自动触发更新。
13856
14446
  *
14447
+ * @see references/openspec/src/core/config.ts (AI_TOOLS)
13857
14448
  * @see references/openspec/src/core/configurators/slash/
14449
+ * @see references/openspec/src/core/init.ts (isToolConfigured)
13858
14450
  */
13859
14451
  /**
13860
- * 所有支持的工具配置
14452
+ * 获取 Codex 全局 prompts 目录
14453
+ * 优先使用 $CODEX_HOME 环境变量,否则使用 ~/.codex
14454
+ * @see references/openspec/src/core/configurators/slash/codex.ts
14455
+ */
14456
+ function getCodexGlobalPromptsDir() {
14457
+ return join$1(process.env.CODEX_HOME?.trim() || join$1(homedir(), ".codex"), "prompts");
14458
+ }
14459
+ /**
14460
+ * 所有支持的 AI 工具配置
14461
+ *
14462
+ * 完全对齐官方 OpenSpec CLI 的 AI_TOOLS
14463
+ * 按字母顺序排序(与官方一致)
13861
14464
  *
13862
- * 检测路径使用 proposal 命令文件,因为这是 openspec init 创建的第一个文件
13863
- * 如果该文件存在,说明工具已配置
14465
+ * @see references/openspec/src/core/config.ts
14466
+ * @see references/openspec/src/core/configurators/slash/registry.ts
13864
14467
  */
13865
- const TOOL_CONFIGS = [
14468
+ const AI_TOOLS = [
13866
14469
  {
13867
- toolId: "claude",
13868
- detectionPath: ".claude/commands/openspec/proposal.md"
14470
+ name: "Amazon Q Developer",
14471
+ value: "amazon-q",
14472
+ available: true,
14473
+ successLabel: "Amazon Q Developer",
14474
+ scope: "project",
14475
+ detectionPath: ".amazonq/prompts/openspec-proposal.md"
13869
14476
  },
13870
14477
  {
13871
- toolId: "cursor",
13872
- detectionPath: ".cursor/commands/openspec-proposal.md"
14478
+ name: "Antigravity",
14479
+ value: "antigravity",
14480
+ available: true,
14481
+ successLabel: "Antigravity",
14482
+ scope: "project",
14483
+ detectionPath: ".agent/workflows/openspec-proposal.md"
13873
14484
  },
13874
14485
  {
13875
- toolId: "windsurf",
13876
- detectionPath: ".windsurf/workflows/openspec-proposal.md"
14486
+ name: "Auggie (Augment CLI)",
14487
+ value: "auggie",
14488
+ available: true,
14489
+ successLabel: "Auggie",
14490
+ scope: "project",
14491
+ detectionPath: ".augment/commands/openspec-proposal.md"
13877
14492
  },
13878
14493
  {
13879
- toolId: "cline",
13880
- detectionPath: ".clinerules/workflows/openspec-proposal.md"
14494
+ name: "Claude Code",
14495
+ value: "claude",
14496
+ available: true,
14497
+ successLabel: "Claude Code",
14498
+ scope: "project",
14499
+ detectionPath: ".claude/commands/openspec/proposal.md"
13881
14500
  },
13882
14501
  {
13883
- toolId: "github-copilot",
13884
- detectionPath: ".github/prompts/openspec-proposal.prompt.md"
14502
+ name: "Cline",
14503
+ value: "cline",
14504
+ available: true,
14505
+ successLabel: "Cline",
14506
+ scope: "project",
14507
+ detectionPath: ".clinerules/workflows/openspec-proposal.md"
13885
14508
  },
13886
14509
  {
13887
- toolId: "amazon-q",
13888
- detectionPath: ".amazonq/prompts/openspec-proposal.md"
14510
+ name: "Codex",
14511
+ value: "codex",
14512
+ available: true,
14513
+ successLabel: "Codex",
14514
+ scope: "global",
14515
+ detectionPath: () => join$1(getCodexGlobalPromptsDir(), "openspec-proposal.md")
13889
14516
  },
13890
14517
  {
13891
- toolId: "codex",
13892
- detectionPath: ".codex/prompts/openspec-proposal.md"
14518
+ name: "CodeBuddy Code (CLI)",
14519
+ value: "codebuddy",
14520
+ available: true,
14521
+ successLabel: "CodeBuddy Code",
14522
+ scope: "project",
14523
+ detectionPath: ".codebuddy/commands/openspec/proposal.md"
13893
14524
  },
13894
14525
  {
13895
- toolId: "gemini",
13896
- detectionPath: ".gemini/commands/openspec/proposal.toml"
14526
+ name: "CoStrict",
14527
+ value: "costrict",
14528
+ available: true,
14529
+ successLabel: "CoStrict",
14530
+ scope: "project",
14531
+ detectionPath: ".cospec/openspec/commands/openspec-proposal.md"
13897
14532
  },
13898
14533
  {
13899
- toolId: "auggie",
13900
- detectionPath: ".augment/commands/openspec-proposal.md"
14534
+ name: "Crush",
14535
+ value: "crush",
14536
+ available: true,
14537
+ successLabel: "Crush",
14538
+ scope: "project",
14539
+ detectionPath: ".crush/commands/openspec/proposal.md"
13901
14540
  },
13902
14541
  {
13903
- toolId: "codebuddy",
13904
- detectionPath: ".codebuddy/commands/openspec/proposal.md"
14542
+ name: "Cursor",
14543
+ value: "cursor",
14544
+ available: true,
14545
+ successLabel: "Cursor",
14546
+ scope: "project",
14547
+ detectionPath: ".cursor/commands/openspec-proposal.md"
13905
14548
  },
13906
14549
  {
13907
- toolId: "qoder",
13908
- detectionPath: ".qoder/commands/openspec/proposal.md"
14550
+ name: "Factory Droid",
14551
+ value: "factory",
14552
+ available: true,
14553
+ successLabel: "Factory Droid",
14554
+ scope: "project",
14555
+ detectionPath: ".factory/commands/openspec-proposal.md"
13909
14556
  },
13910
14557
  {
13911
- toolId: "roocode",
13912
- detectionPath: ".roo/commands/openspec-proposal.md"
14558
+ name: "Gemini CLI",
14559
+ value: "gemini",
14560
+ available: true,
14561
+ successLabel: "Gemini CLI",
14562
+ scope: "project",
14563
+ detectionPath: ".gemini/commands/openspec/proposal.toml"
13913
14564
  },
13914
14565
  {
13915
- toolId: "kilocode",
13916
- detectionPath: ".kilocode/workflows/openspec-proposal.md"
14566
+ name: "GitHub Copilot",
14567
+ value: "github-copilot",
14568
+ available: true,
14569
+ successLabel: "GitHub Copilot",
14570
+ scope: "project",
14571
+ detectionPath: ".github/prompts/openspec-proposal.prompt.md"
13917
14572
  },
13918
14573
  {
13919
- toolId: "opencode",
13920
- detectionPath: ".opencode/command/openspec-proposal.md"
14574
+ name: "iFlow",
14575
+ value: "iflow",
14576
+ available: true,
14577
+ successLabel: "iFlow",
14578
+ scope: "project",
14579
+ detectionPath: ".iflow/commands/openspec-proposal.md"
13921
14580
  },
13922
14581
  {
13923
- toolId: "factory",
13924
- detectionPath: ".factory/commands/openspec-proposal.md"
14582
+ name: "Kilo Code",
14583
+ value: "kilocode",
14584
+ available: true,
14585
+ successLabel: "Kilo Code",
14586
+ scope: "project",
14587
+ detectionPath: ".kilocode/workflows/openspec-proposal.md"
13925
14588
  },
13926
14589
  {
13927
- toolId: "crush",
13928
- detectionPath: ".crush/commands/openspec/proposal.md"
14590
+ name: "OpenCode",
14591
+ value: "opencode",
14592
+ available: true,
14593
+ successLabel: "OpenCode",
14594
+ scope: "project",
14595
+ detectionPath: ".opencode/command/openspec-proposal.md"
13929
14596
  },
13930
14597
  {
13931
- toolId: "costrict",
13932
- detectionPath: ".cospec/openspec/commands/openspec-proposal.md"
14598
+ name: "Qoder (CLI)",
14599
+ value: "qoder",
14600
+ available: true,
14601
+ successLabel: "Qoder",
14602
+ scope: "project",
14603
+ detectionPath: ".qoder/commands/openspec/proposal.md"
13933
14604
  },
13934
14605
  {
13935
- toolId: "qwen",
14606
+ name: "Qwen Code",
14607
+ value: "qwen",
14608
+ available: true,
14609
+ successLabel: "Qwen Code",
14610
+ scope: "project",
13936
14611
  detectionPath: ".qwen/commands/openspec-proposal.toml"
13937
14612
  },
13938
14613
  {
13939
- toolId: "iflow",
13940
- detectionPath: ".iflow/commands/openspec-proposal.md"
14614
+ name: "RooCode",
14615
+ value: "roocode",
14616
+ available: true,
14617
+ successLabel: "RooCode",
14618
+ scope: "project",
14619
+ detectionPath: ".roo/commands/openspec-proposal.md"
13941
14620
  },
13942
14621
  {
13943
- toolId: "antigravity",
13944
- detectionPath: ".agent/workflows/openspec-proposal.md"
14622
+ name: "Windsurf",
14623
+ value: "windsurf",
14624
+ available: true,
14625
+ successLabel: "Windsurf",
14626
+ scope: "project",
14627
+ detectionPath: ".windsurf/workflows/openspec-proposal.md"
14628
+ },
14629
+ {
14630
+ name: "AGENTS.md (works with Amp, VS Code, …)",
14631
+ value: "agents",
14632
+ available: false,
14633
+ successLabel: "your AGENTS.md-compatible assistant",
14634
+ scope: "project",
14635
+ detectionPath: "AGENTS.md"
13945
14636
  }
13946
14637
  ];
13947
14638
  /**
13948
- * 获取所有可用的工具 ID 列表
14639
+ * 获取所有可用的工具(available: true)
14640
+ */
14641
+ function getAvailableTools() {
14642
+ return AI_TOOLS.filter((tool) => tool.available);
14643
+ }
14644
+ /**
14645
+ * 获取所有工具(包括 available: false 的)
14646
+ */
14647
+ function getAllTools() {
14648
+ return AI_TOOLS;
14649
+ }
14650
+ /** 状态缓存:projectDir -> ReactiveState */
14651
+ const stateCache = /* @__PURE__ */ new Map();
14652
+ /** 监听器释放函数缓存 */
14653
+ const releaseCache = /* @__PURE__ */ new Map();
14654
+ /**
14655
+ * 检查文件是否存在
14656
+ */
14657
+ async function fileExists(filePath) {
14658
+ try {
14659
+ await stat(filePath);
14660
+ return true;
14661
+ } catch {
14662
+ return false;
14663
+ }
14664
+ }
14665
+ /**
14666
+ * 解析工具的检测路径
14667
+ * @param config 工具配置
14668
+ * @param projectDir 项目根目录
14669
+ * @returns 绝对路径,如果无检测路径则返回 undefined
14670
+ */
14671
+ function resolveDetectionPath(config, projectDir) {
14672
+ if (config.scope === "none" || !config.detectionPath) return;
14673
+ if (config.scope === "global") return config.detectionPath();
14674
+ return join$1(projectDir, config.detectionPath);
14675
+ }
14676
+ /**
14677
+ * 扫描已配置的工具(并行检查)
14678
+ */
14679
+ async function scanConfiguredTools(projectDir) {
14680
+ return (await Promise.all(AI_TOOLS.map(async (config) => {
14681
+ const filePath = resolveDetectionPath(config, projectDir);
14682
+ if (!filePath) return null;
14683
+ return await fileExists(filePath) ? config.value : null;
14684
+ }))).filter((id) => id !== null);
14685
+ }
14686
+ /**
14687
+ * 获取需要监听的项目级目录列表
14688
+ * 只监听包含工具配置的一级隐藏目录
14689
+ */
14690
+ function getProjectWatchDirs(projectDir) {
14691
+ const dirs = /* @__PURE__ */ new Set();
14692
+ for (const config of AI_TOOLS) if (config.scope === "project" && config.detectionPath) {
14693
+ const firstDir = config.detectionPath.split("/")[0];
14694
+ if (firstDir) dirs.add(join$1(projectDir, firstDir));
14695
+ }
14696
+ return Array.from(dirs);
14697
+ }
14698
+ /**
14699
+ * 获取需要监听的全局目录列表
14700
+ * 如 Codex 的 ~/.codex/prompts/
13949
14701
  */
13950
- function getAvailableToolIds() {
13951
- return TOOL_CONFIGS.map((config) => config.toolId);
14702
+ function getGlobalWatchDirs() {
14703
+ const dirs = /* @__PURE__ */ new Set();
14704
+ for (const config of AI_TOOLS) if (config.scope === "global" && config.detectionPath) {
14705
+ const filePath = config.detectionPath();
14706
+ dirs.add(dirname$1(filePath));
14707
+ }
14708
+ return Array.from(dirs);
13952
14709
  }
13953
14710
  /**
13954
14711
  * 检测项目中已配置的工具(响应式)
13955
14712
  *
13956
- * 使用 reactiveExists 检测文件,当文件变化时会自动触发更新。
13957
- * 必须在 ReactiveContext 中调用才能获得响应式能力。
14713
+ * 监听两类目录:
14714
+ * 1. 项目级配置目录(如 .claude, .cursor 等)
14715
+ * 2. 全局配置目录(如 ~/.codex/prompts/)
13958
14716
  *
13959
14717
  * @param projectDir 项目根目录
13960
14718
  * @returns 已配置的工具 ID 列表
13961
14719
  */
13962
14720
  async function getConfiguredTools(projectDir) {
13963
- const configured = [];
13964
- for (const config of TOOL_CONFIGS) if (await reactiveExists(join(projectDir, config.detectionPath))) configured.push(config.toolId);
13965
- return configured;
14721
+ const normalizedPath = resolve$1(projectDir);
14722
+ const key = `tools:${normalizedPath}`;
14723
+ let state = stateCache.get(key);
14724
+ if (!state) {
14725
+ state = new ReactiveState(await scanConfiguredTools(normalizedPath), { equals: (a, b) => a.length === b.length && a.every((v, i) => v === b[i]) });
14726
+ stateCache.set(key, state);
14727
+ const releases = [];
14728
+ const onUpdate = async () => {
14729
+ const newValue = await scanConfiguredTools(normalizedPath);
14730
+ state.set(newValue);
14731
+ };
14732
+ const projectWatchDirs = getProjectWatchDirs(normalizedPath);
14733
+ for (const dir of projectWatchDirs) {
14734
+ const release = acquireWatcher(dir, onUpdate, { recursive: true });
14735
+ releases.push(release);
14736
+ }
14737
+ const globalWatchDirs = getGlobalWatchDirs();
14738
+ for (const dir of globalWatchDirs) {
14739
+ const release = acquireWatcher(dir, onUpdate, { recursive: false });
14740
+ releases.push(release);
14741
+ }
14742
+ const rootRelease = acquireWatcher(normalizedPath, onUpdate, { recursive: false });
14743
+ releases.push(rootRelease);
14744
+ releaseCache.set(key, () => releases.forEach((r) => r()));
14745
+ }
14746
+ return state.get();
13966
14747
  }
13967
14748
 
13968
14749
  //#endregion
@@ -14023,6 +14804,63 @@ function createReactiveSubscriptionWithInput(task) {
14023
14804
  };
14024
14805
  }
14025
14806
 
14807
+ //#endregion
14808
+ //#region ../server/src/cli-stream-observable.ts
14809
+ /**
14810
+ * 创建安全的 CLI 流式 observable
14811
+ *
14812
+ * 解决的问题:
14813
+ * 1. 防止在 emit.complete() 之后调用 emit.next()(会导致 "Controller is already closed" 错误)
14814
+ * 2. 统一的错误处理,防止未捕获的异常导致服务器崩溃
14815
+ * 3. 确保取消时正确清理资源
14816
+ *
14817
+ * @param startStream 启动流的函数,接收 onEvent 回调,返回取消函数的 Promise
14818
+ */
14819
+ function createCliStreamObservable(startStream) {
14820
+ return observable((emit) => {
14821
+ let cancel;
14822
+ let completed = false;
14823
+ /**
14824
+ * 安全的事件处理器
14825
+ * - 检查是否已完成,防止重复调用
14826
+ * - 使用 try-catch 防止异常导致服务器崩溃
14827
+ */
14828
+ const safeEventHandler = (event) => {
14829
+ if (completed) return;
14830
+ try {
14831
+ emit.next(event);
14832
+ if (event.type === "exit") {
14833
+ completed = true;
14834
+ emit.complete();
14835
+ }
14836
+ } catch (err) {
14837
+ console.error("[CLI Stream] Error emitting event:", err);
14838
+ if (!completed) {
14839
+ completed = true;
14840
+ try {
14841
+ emit.error(err instanceof Error ? err : new Error(String(err)));
14842
+ } catch {}
14843
+ }
14844
+ }
14845
+ };
14846
+ startStream(safeEventHandler).then((cancelFn) => {
14847
+ cancel = cancelFn;
14848
+ }).catch((err) => {
14849
+ console.error("[CLI Stream] Error starting stream:", err);
14850
+ if (!completed) {
14851
+ completed = true;
14852
+ try {
14853
+ emit.error(err instanceof Error ? err : new Error(String(err)));
14854
+ } catch {}
14855
+ }
14856
+ });
14857
+ return () => {
14858
+ completed = true;
14859
+ cancel?.();
14860
+ };
14861
+ });
14862
+ }
14863
+
14026
14864
  //#endregion
14027
14865
  //#region ../server/src/router.ts
14028
14866
  const t = initTRPC.context().create();
@@ -14335,6 +15173,12 @@ const configRouter = router({
14335
15173
  get: publicProcedure.query(async ({ ctx }) => {
14336
15174
  return ctx.configManager.readConfig();
14337
15175
  }),
15176
+ getEffectiveCliCommand: publicProcedure.query(async ({ ctx }) => {
15177
+ return ctx.configManager.getCliCommandString();
15178
+ }),
15179
+ getDefaultCliCommand: publicProcedure.query(async () => {
15180
+ return getDefaultCliCommandString();
15181
+ }),
14338
15182
  update: publicProcedure.input(objectType({
14339
15183
  cli: objectType({ command: stringType() }).optional(),
14340
15184
  ui: objectType({ theme: enumType([
@@ -14361,8 +15205,40 @@ const cliRouter = router({
14361
15205
  checkAvailability: publicProcedure.query(async ({ ctx }) => {
14362
15206
  return ctx.cliExecutor.checkAvailability();
14363
15207
  }),
15208
+ sniffGlobalCli: publicProcedure.query(async () => {
15209
+ return sniffGlobalCli();
15210
+ }),
15211
+ installGlobalCliStream: publicProcedure.subscription(({ ctx }) => {
15212
+ return observable((emit) => {
15213
+ const cancel = ctx.cliExecutor.executeCommandStream([
15214
+ "npm",
15215
+ "install",
15216
+ "-g",
15217
+ "@fission-ai/openspec"
15218
+ ], (event) => {
15219
+ emit.next(event);
15220
+ if (event.type === "exit") emit.complete();
15221
+ });
15222
+ return () => {
15223
+ cancel();
15224
+ };
15225
+ });
15226
+ }),
14364
15227
  getAvailableTools: publicProcedure.query(() => {
14365
- return getAvailableToolIds();
15228
+ return getAvailableTools().map((tool) => ({
15229
+ name: tool.name,
15230
+ value: tool.value,
15231
+ available: tool.available,
15232
+ successLabel: tool.successLabel
15233
+ }));
15234
+ }),
15235
+ getAllTools: publicProcedure.query(() => {
15236
+ return getAllTools().map((tool) => ({
15237
+ name: tool.name,
15238
+ value: tool.value,
15239
+ available: tool.available,
15240
+ successLabel: tool.successLabel
15241
+ }));
14366
15242
  }),
14367
15243
  getConfiguredTools: publicProcedure.query(async ({ ctx }) => {
14368
15244
  return getConfiguredTools(ctx.projectDir);
@@ -14395,6 +15271,23 @@ const cliRouter = router({
14395
15271
  }),
14396
15272
  execute: publicProcedure.input(objectType({ args: arrayType(stringType()) })).mutation(async ({ ctx, input }) => {
14397
15273
  return ctx.cliExecutor.execute(input.args);
15274
+ }),
15275
+ initStream: publicProcedure.input(objectType({ tools: unionType([
15276
+ arrayType(stringType()),
15277
+ literalType("all"),
15278
+ literalType("none")
15279
+ ]).optional() }).optional()).subscription(({ ctx, input }) => {
15280
+ return createCliStreamObservable((onEvent) => ctx.cliExecutor.initStream(input?.tools ?? "all", onEvent));
15281
+ }),
15282
+ archiveStream: publicProcedure.input(objectType({
15283
+ changeId: stringType(),
15284
+ skipSpecs: booleanType().optional(),
15285
+ noValidate: booleanType().optional()
15286
+ })).subscription(({ ctx, input }) => {
15287
+ return createCliStreamObservable((onEvent) => ctx.cliExecutor.archiveStream(input.changeId, {
15288
+ skipSpecs: input.skipSpecs,
15289
+ noValidate: input.noValidate
15290
+ }, onEvent));
14398
15291
  })
14399
15292
  });
14400
15293
  /**
@@ -14413,6 +15306,41 @@ const appRouter = router({
14413
15306
  cli: cliRouter
14414
15307
  });
14415
15308
 
15309
+ //#endregion
15310
+ //#region ../server/src/port-utils.ts
15311
+ /**
15312
+ * Check if a port is available by trying to listen on it.
15313
+ * Uses default binding (both IPv4 and IPv6) to detect conflicts.
15314
+ */
15315
+ function isPortAvailable(port) {
15316
+ return new Promise((resolve$2) => {
15317
+ const server = createServer$1();
15318
+ server.once("error", () => {
15319
+ resolve$2(false);
15320
+ });
15321
+ server.once("listening", () => {
15322
+ server.close(() => resolve$2(true));
15323
+ });
15324
+ server.listen(port);
15325
+ });
15326
+ }
15327
+ /**
15328
+ * Find an available port starting from the given port.
15329
+ * Will try up to maxAttempts ports sequentially.
15330
+ *
15331
+ * @param startPort - The preferred port to start checking from
15332
+ * @param maxAttempts - Maximum number of ports to try (default: 10)
15333
+ * @returns The first available port found
15334
+ * @throws Error if no available port is found in the range
15335
+ */
15336
+ async function findAvailablePort(startPort, maxAttempts = 10) {
15337
+ for (let i = 0; i < maxAttempts; i++) {
15338
+ const port = startPort + i;
15339
+ if (await isPortAvailable(port)) return port;
15340
+ }
15341
+ throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
15342
+ }
15343
+
14416
15344
  //#endregion
14417
15345
  //#region ../server/src/server.ts
14418
15346
  /**
@@ -14486,7 +15414,8 @@ function createServer$2(config) {
14486
15414
  /**
14487
15415
  * Create WebSocket server for tRPC subscriptions
14488
15416
  */
14489
- function createWebSocketServer(server, httpServer) {
15417
+ async function createWebSocketServer(server, httpServer, config) {
15418
+ await initWatcherPool(config.projectDir);
14490
15419
  const wss = new import_websocket_server.default({ noServer: true });
14491
15420
  const handler = applyWSSHandler({
14492
15421
  wss,
@@ -14510,45 +15439,42 @@ function createWebSocketServer(server, httpServer) {
14510
15439
  }
14511
15440
  };
14512
15441
  }
14513
-
14514
- //#endregion
14515
- //#region src/index.ts
14516
- const __dirname = dirname$1(fileURLToPath(import.meta.url));
14517
15442
  /**
14518
- * Check if a port is available by trying to listen on it.
14519
- * Tests both 127.0.0.1 and 0.0.0.0 to ensure the port is truly available.
15443
+ * Start the OpenSpec UI server with WebSocket support.
15444
+ * Automatically finds an available port if the preferred port is occupied.
15445
+ *
15446
+ * @param config - Server configuration
15447
+ * @param setupApp - Optional callback to configure the Hono app before starting (e.g., add static file middleware)
15448
+ * @returns Running server instance with actual port and close function
14520
15449
  */
14521
- function isPortAvailable(port) {
14522
- return new Promise((resolve$2) => {
14523
- const server1 = createServer$1();
14524
- server1.once("error", () => {
14525
- resolve$2(false);
14526
- });
14527
- server1.once("listening", () => {
14528
- server1.close(() => {
14529
- const server2 = createServer$1();
14530
- server2.once("error", () => {
14531
- resolve$2(false);
14532
- });
14533
- server2.once("listening", () => {
14534
- server2.close(() => resolve$2(true));
14535
- });
14536
- server2.listen(port, "0.0.0.0");
14537
- });
14538
- });
14539
- server1.listen(port, "127.0.0.1");
15450
+ async function startServer(config, setupApp) {
15451
+ const preferredPort = config.port ?? 3100;
15452
+ const port = await findAvailablePort(preferredPort);
15453
+ console.log("QAQ", preferredPort, port);
15454
+ const server = createServer$2({
15455
+ ...config,
15456
+ port
14540
15457
  });
15458
+ if (setupApp) setupApp(server.app);
15459
+ const httpServer = serve({
15460
+ fetch: server.app.fetch,
15461
+ port
15462
+ });
15463
+ const wsServer = await createWebSocketServer(server, httpServer, { projectDir: config.projectDir });
15464
+ return {
15465
+ url: `http://localhost:${port}`,
15466
+ port,
15467
+ preferredPort,
15468
+ close: async () => {
15469
+ wsServer.close();
15470
+ httpServer.close();
15471
+ }
15472
+ };
14541
15473
  }
14542
- /**
14543
- * Find an available port starting from the given port
14544
- */
14545
- async function findAvailablePort(startPort, maxAttempts = 10) {
14546
- for (let i = 0; i < maxAttempts; i++) {
14547
- const port = startPort + i;
14548
- if (await isPortAvailable(port)) return port;
14549
- }
14550
- throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
14551
- }
15474
+
15475
+ //#endregion
15476
+ //#region src/index.ts
15477
+ const __dirname = dirname$1(fileURLToPath(import.meta.url));
14552
15478
  /**
14553
15479
  * Get the path to the web assets directory
14554
15480
  */
@@ -14589,39 +15515,32 @@ function createDefaultProviders() {
14589
15515
  };
14590
15516
  }
14591
15517
  /**
14592
- * Start the OpenSpec UI server with WebSocket support for realtime updates
15518
+ * Setup static file serving middleware for the Hono app
14593
15519
  */
14594
- async function startServer(options = {}) {
14595
- const { projectDir = process.cwd(), port: preferredPort = 3100, providers = createDefaultProviders(), enableWatcher = true } = options;
14596
- const port = await findAvailablePort(preferredPort);
14597
- const server = createServer$2({
14598
- projectDir,
14599
- port,
14600
- providers,
14601
- enableWatcher
14602
- });
15520
+ function setupStaticFiles(app) {
14603
15521
  const webDir = getWebAssetsDir();
14604
- server.app.use("/*", async (c, next) => {
15522
+ const mimeTypes = {
15523
+ html: "text/html",
15524
+ js: "application/javascript",
15525
+ css: "text/css",
15526
+ json: "application/json",
15527
+ png: "image/png",
15528
+ jpg: "image/jpeg",
15529
+ jpeg: "image/jpeg",
15530
+ gif: "image/gif",
15531
+ svg: "image/svg+xml",
15532
+ ico: "image/x-icon",
15533
+ woff: "font/woff",
15534
+ woff2: "font/woff2",
15535
+ ttf: "font/ttf"
15536
+ };
15537
+ app.use("/*", async (c, next) => {
14605
15538
  const path$1 = c.req.path === "/" ? "/index.html" : c.req.path;
14606
15539
  if (path$1.startsWith("/trpc")) return next();
14607
15540
  const filePath = join$1(webDir, path$1);
14608
15541
  if (existsSync(filePath) && statSync(filePath).isFile()) {
14609
15542
  const content = readFileSync(filePath);
14610
- const contentType = {
14611
- html: "text/html",
14612
- js: "application/javascript",
14613
- css: "text/css",
14614
- json: "application/json",
14615
- png: "image/png",
14616
- jpg: "image/jpeg",
14617
- jpeg: "image/jpeg",
14618
- gif: "image/gif",
14619
- svg: "image/svg+xml",
14620
- ico: "image/x-icon",
14621
- woff: "font/woff",
14622
- woff2: "font/woff2",
14623
- ttf: "font/ttf"
14624
- }[path$1.split(".").pop() || ""] || "application/octet-stream";
15543
+ const contentType = mimeTypes[path$1.split(".").pop() || ""] || "application/octet-stream";
14625
15544
  return c.body(content, 200, { "Content-Type": contentType });
14626
15545
  }
14627
15546
  if (!path$1.includes(".")) {
@@ -14633,20 +15552,20 @@ async function startServer(options = {}) {
14633
15552
  }
14634
15553
  return c.notFound();
14635
15554
  });
14636
- const httpServer = serve({
14637
- fetch: server.app.fetch,
14638
- port
14639
- });
14640
- const wsServer = createWebSocketServer(server, httpServer);
14641
- return {
14642
- url: `http://localhost:${port}`,
15555
+ }
15556
+ /**
15557
+ * Start the OpenSpec UI server with WebSocket support for realtime updates.
15558
+ * Includes static file serving for the web UI.
15559
+ */
15560
+ async function startServer$1(options = {}) {
15561
+ const { projectDir = process.cwd(), port = 3100, providers = createDefaultProviders(), enableWatcher = true } = options;
15562
+ return await startServer({
15563
+ projectDir,
14643
15564
  port,
14644
- close: async () => {
14645
- wsServer.close();
14646
- httpServer.close();
14647
- }
14648
- };
15565
+ providers,
15566
+ enableWatcher
15567
+ }, setupStaticFiles);
14649
15568
  }
14650
15569
 
14651
15570
  //#endregion
14652
- export { __commonJS$1 as a, ACPAgents as i, createServer$2 as n, __toESM$1 as o, ProviderManager as r, startServer as t };
15571
+ export { __commonJS$1 as a, ACPAgents as i, createServer$2 as n, __toESM$1 as o, ProviderManager as r, startServer$1 as t };