@openspecui/server 3.2.3 → 3.3.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.
- package/dist/index.mjs +577 -24
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, MarkdownParser, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, sniffGlobalCli, subscribeWatcherRuntimeStatus } from "@openspecui/core";
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { access, mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1
5
|
import { createServer as createServer$1 } from "node:net";
|
|
2
6
|
import { serve } from "@hono/node-server";
|
|
3
|
-
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, sniffGlobalCli, subscribeWatcherRuntimeStatus } from "@openspecui/core";
|
|
4
7
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
5
8
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
6
9
|
import { Hono } from "hono";
|
|
7
10
|
import { cors } from "hono/cors";
|
|
8
11
|
import { readFileSync } from "node:fs";
|
|
9
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { WebSocketServer } from "ws";
|
|
12
13
|
import { EventEmitter } from "node:events";
|
|
13
14
|
import { execFile } from "node:child_process";
|
|
14
|
-
import { mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
16
|
import * as pty from "@lydell/node-pty";
|
|
17
17
|
import { EventEmitter as EventEmitter$1 } from "events";
|
|
@@ -21,6 +21,284 @@ import { observable } from "@trpc/server/observable";
|
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { NodeWorkerSearchProvider } from "@openspecui/search/node";
|
|
23
23
|
|
|
24
|
+
//#region src/document-service.ts
|
|
25
|
+
function toErrorDiagnostic$1(error) {
|
|
26
|
+
return {
|
|
27
|
+
level: "error",
|
|
28
|
+
message: error instanceof Error ? error.message : String(error)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isNotNull(value) {
|
|
32
|
+
return value !== null;
|
|
33
|
+
}
|
|
34
|
+
var DocumentService = class {
|
|
35
|
+
parser = new MarkdownParser();
|
|
36
|
+
constructor(projectDir, adapter, hookRuntime) {
|
|
37
|
+
this.projectDir = projectDir;
|
|
38
|
+
this.adapter = adapter;
|
|
39
|
+
this.hookRuntime = hookRuntime;
|
|
40
|
+
}
|
|
41
|
+
async readProjectMd(consumer = "view", mode = "processed") {
|
|
42
|
+
const source = await this.adapter.readProjectMd();
|
|
43
|
+
if (source === null) return null;
|
|
44
|
+
return this.processDocument({
|
|
45
|
+
consumer,
|
|
46
|
+
mode,
|
|
47
|
+
document: {
|
|
48
|
+
stage: "project",
|
|
49
|
+
kind: "project",
|
|
50
|
+
relativePath: "openspec/project.md",
|
|
51
|
+
absolutePath: join(this.projectDir, "openspec", "project.md")
|
|
52
|
+
},
|
|
53
|
+
source
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async readSpecRaw(specId, consumer = "view", mode = "processed") {
|
|
57
|
+
const source = await this.adapter.readSpecRaw(specId);
|
|
58
|
+
if (source === null) return null;
|
|
59
|
+
return this.processDocument({
|
|
60
|
+
consumer,
|
|
61
|
+
mode,
|
|
62
|
+
document: {
|
|
63
|
+
stage: "main",
|
|
64
|
+
kind: "spec",
|
|
65
|
+
specId,
|
|
66
|
+
relativePath: `openspec/specs/${specId}/spec.md`,
|
|
67
|
+
absolutePath: join(this.projectDir, "openspec", "specs", specId, "spec.md")
|
|
68
|
+
},
|
|
69
|
+
source
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async readSpec(specId, consumer = "view", mode = "processed") {
|
|
73
|
+
try {
|
|
74
|
+
const content = await this.readSpecRaw(specId, consumer, mode);
|
|
75
|
+
if (!content) return null;
|
|
76
|
+
return this.parser.parseSpec(specId, content.markdown);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async readChangeRaw(changeId, consumer = "view", mode = "processed") {
|
|
82
|
+
const raw = await this.adapter.readChangeRaw(changeId);
|
|
83
|
+
if (!raw) return null;
|
|
84
|
+
const process$1 = (kind, relativePath$1, source) => this.processDocument({
|
|
85
|
+
consumer,
|
|
86
|
+
mode,
|
|
87
|
+
document: {
|
|
88
|
+
stage: "change",
|
|
89
|
+
kind,
|
|
90
|
+
changeId,
|
|
91
|
+
relativePath: relativePath$1,
|
|
92
|
+
absolutePath: join(this.projectDir, relativePath$1)
|
|
93
|
+
},
|
|
94
|
+
source
|
|
95
|
+
});
|
|
96
|
+
const [proposal, tasks, design, deltaSpecs] = await Promise.all([
|
|
97
|
+
process$1("proposal", `openspec/changes/${changeId}/proposal.md`, raw.proposal),
|
|
98
|
+
process$1("tasks", `openspec/changes/${changeId}/tasks.md`, raw.tasks),
|
|
99
|
+
raw.design ? process$1("design", `openspec/changes/${changeId}/design.md`, raw.design) : Promise.resolve(void 0),
|
|
100
|
+
Promise.all(raw.deltaSpecs.map(async (deltaSpec) => {
|
|
101
|
+
const result = await process$1("delta-spec", `openspec/changes/${changeId}/specs/${deltaSpec.specId}/spec.md`, deltaSpec.content);
|
|
102
|
+
return {
|
|
103
|
+
specId: deltaSpec.specId,
|
|
104
|
+
content: result.markdown,
|
|
105
|
+
sourceContent: deltaSpec.content
|
|
106
|
+
};
|
|
107
|
+
}))
|
|
108
|
+
]);
|
|
109
|
+
return {
|
|
110
|
+
proposal,
|
|
111
|
+
tasks,
|
|
112
|
+
design,
|
|
113
|
+
deltaSpecs
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async readChange(changeId, consumer = "view", mode = "processed") {
|
|
117
|
+
try {
|
|
118
|
+
const raw = await this.readChangeRaw(changeId, consumer, mode);
|
|
119
|
+
if (!raw) return null;
|
|
120
|
+
return this.parser.parseChange(changeId, raw.proposal.markdown, raw.tasks.markdown, {
|
|
121
|
+
design: raw.design?.markdown,
|
|
122
|
+
deltaSpecs: raw.deltaSpecs
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async readArchivedChangeRaw(changeId, consumer = "view", mode = "processed") {
|
|
129
|
+
const raw = await this.adapter.readArchivedChangeRaw(changeId);
|
|
130
|
+
if (!raw) return null;
|
|
131
|
+
const process$1 = (kind, relativePath$1, source) => this.processDocument({
|
|
132
|
+
consumer,
|
|
133
|
+
mode,
|
|
134
|
+
document: {
|
|
135
|
+
stage: "archive",
|
|
136
|
+
kind,
|
|
137
|
+
changeId,
|
|
138
|
+
relativePath: relativePath$1,
|
|
139
|
+
absolutePath: join(this.projectDir, relativePath$1)
|
|
140
|
+
},
|
|
141
|
+
source
|
|
142
|
+
});
|
|
143
|
+
const [proposal, tasks, design, deltaSpecs] = await Promise.all([
|
|
144
|
+
process$1("proposal", `openspec/changes/archive/${changeId}/proposal.md`, raw.proposal),
|
|
145
|
+
process$1("tasks", `openspec/changes/archive/${changeId}/tasks.md`, raw.tasks),
|
|
146
|
+
raw.design ? process$1("design", `openspec/changes/archive/${changeId}/design.md`, raw.design) : Promise.resolve(void 0),
|
|
147
|
+
Promise.all(raw.deltaSpecs.map(async (deltaSpec) => {
|
|
148
|
+
const result = await process$1("delta-spec", `openspec/changes/archive/${changeId}/specs/${deltaSpec.specId}/spec.md`, deltaSpec.content);
|
|
149
|
+
return {
|
|
150
|
+
specId: deltaSpec.specId,
|
|
151
|
+
content: result.markdown,
|
|
152
|
+
sourceContent: deltaSpec.content
|
|
153
|
+
};
|
|
154
|
+
}))
|
|
155
|
+
]);
|
|
156
|
+
return {
|
|
157
|
+
proposal,
|
|
158
|
+
tasks,
|
|
159
|
+
design,
|
|
160
|
+
deltaSpecs
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async readArchivedChange(changeId, consumer = "view", mode = "processed") {
|
|
164
|
+
try {
|
|
165
|
+
const raw = await this.readArchivedChangeRaw(changeId, consumer, mode);
|
|
166
|
+
if (!raw) return null;
|
|
167
|
+
return this.parser.parseChange(changeId, raw.proposal.markdown, raw.tasks.markdown, {
|
|
168
|
+
design: raw.design?.markdown,
|
|
169
|
+
deltaSpecs: raw.deltaSpecs
|
|
170
|
+
});
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async readChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
176
|
+
const files = await this.adapter.readChangeFiles(changeId);
|
|
177
|
+
return this.processChangeFiles("change", changeId, files, consumer, mode);
|
|
178
|
+
}
|
|
179
|
+
async readArchivedChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
180
|
+
const files = await this.adapter.readArchivedChangeFiles(changeId);
|
|
181
|
+
return this.processChangeFiles("archive", changeId, files, consumer, mode);
|
|
182
|
+
}
|
|
183
|
+
async processChangeFiles(stage, changeId, files, consumer, mode) {
|
|
184
|
+
const root = stage === "change" ? `openspec/changes/${changeId}` : `openspec/changes/archive/${changeId}`;
|
|
185
|
+
return (await Promise.all(files.map(async (file) => {
|
|
186
|
+
if (file.type !== "file" || file.content === void 0 || !file.path.endsWith(".md")) return file;
|
|
187
|
+
const kind = this.inferChangeFileKind(file.path);
|
|
188
|
+
if (!kind) return file;
|
|
189
|
+
const result = await this.processDocument({
|
|
190
|
+
consumer,
|
|
191
|
+
mode,
|
|
192
|
+
document: {
|
|
193
|
+
stage,
|
|
194
|
+
kind,
|
|
195
|
+
changeId,
|
|
196
|
+
relativePath: `${root}/${file.path}`,
|
|
197
|
+
absolutePath: join(this.projectDir, root, file.path)
|
|
198
|
+
},
|
|
199
|
+
source: file.content
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
...file,
|
|
203
|
+
content: result.markdown
|
|
204
|
+
};
|
|
205
|
+
}))).filter(isNotNull);
|
|
206
|
+
}
|
|
207
|
+
inferChangeFileKind(path) {
|
|
208
|
+
if (path === "proposal.md") return "proposal";
|
|
209
|
+
if (path === "tasks.md") return "tasks";
|
|
210
|
+
if (path === "design.md") return "design";
|
|
211
|
+
if (/^specs\/[^/]+\/spec\.md$/.test(path)) return "delta-spec";
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
async processDocument(input) {
|
|
215
|
+
const read = async () => ({
|
|
216
|
+
markdown: input.source,
|
|
217
|
+
sourceMarkdown: input.source
|
|
218
|
+
});
|
|
219
|
+
if (input.mode === "source") return read();
|
|
220
|
+
const hooks = await this.hookRuntime.load();
|
|
221
|
+
if (!hooks.onReadDocument) return read();
|
|
222
|
+
try {
|
|
223
|
+
return {
|
|
224
|
+
...await hooks.onReadDocument({
|
|
225
|
+
version: OPENSPECUI_HOOKS_VERSION,
|
|
226
|
+
projectDir: this.projectDir,
|
|
227
|
+
consumer: input.consumer,
|
|
228
|
+
document: input.document,
|
|
229
|
+
signal: new AbortController().signal,
|
|
230
|
+
lifecycle: this.hookRuntime
|
|
231
|
+
}, read),
|
|
232
|
+
sourceMarkdown: input.source
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const fallback = await read();
|
|
236
|
+
return {
|
|
237
|
+
...fallback,
|
|
238
|
+
diagnostics: [...fallback.diagnostics ?? [], toErrorDiagnostic$1(error)]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/hook-runtime.ts
|
|
246
|
+
const OPENSPECUI_HOOKS_RELATIVE_PATH = "openspec/openspecui.hooks.ts";
|
|
247
|
+
function isOnReadDocumentHook(value) {
|
|
248
|
+
return typeof value === "function";
|
|
249
|
+
}
|
|
250
|
+
function isOnRunWorkflowHook(value) {
|
|
251
|
+
return typeof value === "function";
|
|
252
|
+
}
|
|
253
|
+
function normalizeHooksModule(moduleValue) {
|
|
254
|
+
if (!moduleValue || typeof moduleValue !== "object") return {};
|
|
255
|
+
const record = moduleValue;
|
|
256
|
+
const defaultRecord = record.default && typeof record.default === "object" ? record.default : {};
|
|
257
|
+
const moduleExportsRecord = record["module.exports"] && typeof record["module.exports"] === "object" ? record["module.exports"] : {};
|
|
258
|
+
return {
|
|
259
|
+
onReadDocument: isOnReadDocumentHook(record.onReadDocument) ? record.onReadDocument : isOnReadDocumentHook(defaultRecord.onReadDocument) ? defaultRecord.onReadDocument : isOnReadDocumentHook(moduleExportsRecord.onReadDocument) ? moduleExportsRecord.onReadDocument : void 0,
|
|
260
|
+
onRunWorkflow: isOnRunWorkflowHook(record.onRunWorkflow) ? record.onRunWorkflow : isOnRunWorkflowHook(defaultRecord.onRunWorkflow) ? defaultRecord.onRunWorkflow : isOnRunWorkflowHook(moduleExportsRecord.onRunWorkflow) ? moduleExportsRecord.onRunWorkflow : void 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function pathExists$1(path) {
|
|
264
|
+
try {
|
|
265
|
+
await access(path);
|
|
266
|
+
return true;
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
var ProjectHookRuntime = class {
|
|
272
|
+
hooksPath;
|
|
273
|
+
hooksPromise = null;
|
|
274
|
+
disposeCallbacks = /* @__PURE__ */ new Set();
|
|
275
|
+
constructor(projectDir) {
|
|
276
|
+
this.hooksPath = join(projectDir, OPENSPECUI_HOOKS_RELATIVE_PATH);
|
|
277
|
+
}
|
|
278
|
+
async load() {
|
|
279
|
+
if (this.hooksPromise) return this.hooksPromise;
|
|
280
|
+
this.hooksPromise = this.loadFresh().catch(() => ({}));
|
|
281
|
+
return this.hooksPromise;
|
|
282
|
+
}
|
|
283
|
+
onDispose(cleanup) {
|
|
284
|
+
this.disposeCallbacks.add(cleanup);
|
|
285
|
+
}
|
|
286
|
+
async dispose() {
|
|
287
|
+
const callbacks = [...this.disposeCallbacks];
|
|
288
|
+
this.disposeCallbacks.clear();
|
|
289
|
+
await Promise.allSettled(callbacks.map((cleanup) => cleanup()));
|
|
290
|
+
}
|
|
291
|
+
async loadFresh() {
|
|
292
|
+
if (!await pathExists$1(this.hooksPath)) return {};
|
|
293
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
294
|
+
return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function createHookRuntime(projectDir) {
|
|
298
|
+
return new ProjectHookRuntime(projectDir);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
//#endregion
|
|
24
302
|
//#region src/port-utils.ts
|
|
25
303
|
/**
|
|
26
304
|
* Check if a port is available by trying to listen on it.
|
|
@@ -2313,6 +2591,47 @@ const gitEntrySelectorSchema = z.discriminatedUnion("type", [z.object({ type: z.
|
|
|
2313
2591
|
type: z.literal("commit"),
|
|
2314
2592
|
hash: z.string().min(1)
|
|
2315
2593
|
})]);
|
|
2594
|
+
const workflowRequestedModeSchema = z.enum([
|
|
2595
|
+
"compose",
|
|
2596
|
+
"command",
|
|
2597
|
+
"direct"
|
|
2598
|
+
]);
|
|
2599
|
+
const runWorkflowInputSchema = z.discriminatedUnion("action", [
|
|
2600
|
+
z.object({
|
|
2601
|
+
action: z.enum(["explore", "propose"]),
|
|
2602
|
+
text: z.string()
|
|
2603
|
+
}),
|
|
2604
|
+
z.object({
|
|
2605
|
+
action: z.literal("new"),
|
|
2606
|
+
changeId: z.string(),
|
|
2607
|
+
schema: z.string().optional(),
|
|
2608
|
+
description: z.string().optional(),
|
|
2609
|
+
extraArgs: z.array(z.string()).default([])
|
|
2610
|
+
}),
|
|
2611
|
+
z.object({
|
|
2612
|
+
action: z.enum(["continue", "ff"]),
|
|
2613
|
+
changeId: z.string(),
|
|
2614
|
+
artifactId: z.string(),
|
|
2615
|
+
schema: z.string().optional()
|
|
2616
|
+
}),
|
|
2617
|
+
z.object({
|
|
2618
|
+
action: z.enum([
|
|
2619
|
+
"apply",
|
|
2620
|
+
"archive",
|
|
2621
|
+
"verify",
|
|
2622
|
+
"sync"
|
|
2623
|
+
]),
|
|
2624
|
+
changeId: z.string(),
|
|
2625
|
+
schema: z.string().optional(),
|
|
2626
|
+
strict: z.boolean().optional()
|
|
2627
|
+
}),
|
|
2628
|
+
z.object({
|
|
2629
|
+
action: z.literal("bulk-archive"),
|
|
2630
|
+
changeIds: z.array(z.string()).optional(),
|
|
2631
|
+
schema: z.string().optional()
|
|
2632
|
+
}),
|
|
2633
|
+
z.object({ action: z.literal("onboard") })
|
|
2634
|
+
]);
|
|
2316
2635
|
function requireChangeId(changeId) {
|
|
2317
2636
|
if (!changeId) throw new Error("change is required");
|
|
2318
2637
|
return changeId;
|
|
@@ -2494,7 +2813,7 @@ const specRouter = router({
|
|
|
2494
2813
|
return ctx.adapter.listSpecsWithMeta();
|
|
2495
2814
|
}),
|
|
2496
2815
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2497
|
-
return ctx.
|
|
2816
|
+
return ctx.documentService.readSpec(input.id);
|
|
2498
2817
|
}),
|
|
2499
2818
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2500
2819
|
return ctx.adapter.readSpecRaw(input.id);
|
|
@@ -2513,7 +2832,7 @@ const specRouter = router({
|
|
|
2513
2832
|
return createReactiveSubscription(() => ctx.adapter.listSpecsWithMeta());
|
|
2514
2833
|
}),
|
|
2515
2834
|
subscribeOne: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2516
|
-
return createReactiveSubscriptionWithInput((id) => ctx.
|
|
2835
|
+
return createReactiveSubscriptionWithInput((id) => ctx.documentService.readSpec(id))(input.id);
|
|
2517
2836
|
}),
|
|
2518
2837
|
subscribeRaw: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2519
2838
|
return createReactiveSubscriptionWithInput((id) => ctx.adapter.readSpecRaw(id))(input.id);
|
|
@@ -2533,7 +2852,7 @@ const changeRouter = router({
|
|
|
2533
2852
|
return ctx.adapter.listArchivedChanges();
|
|
2534
2853
|
}),
|
|
2535
2854
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2536
|
-
return ctx.
|
|
2855
|
+
return ctx.documentService.readChange(input.id);
|
|
2537
2856
|
}),
|
|
2538
2857
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2539
2858
|
return ctx.adapter.readChangeRaw(input.id);
|
|
@@ -2585,7 +2904,7 @@ const archiveRouter = router({
|
|
|
2585
2904
|
return ctx.adapter.listArchivedChangesWithMeta();
|
|
2586
2905
|
}),
|
|
2587
2906
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2588
|
-
return ctx.
|
|
2907
|
+
return ctx.documentService.readArchivedChange(input.id);
|
|
2589
2908
|
}),
|
|
2590
2909
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2591
2910
|
return ctx.adapter.readArchivedChangeRaw(input.id);
|
|
@@ -2594,7 +2913,7 @@ const archiveRouter = router({
|
|
|
2594
2913
|
return createReactiveSubscription(() => ctx.adapter.listArchivedChangesWithMeta());
|
|
2595
2914
|
}),
|
|
2596
2915
|
subscribeOne: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2597
|
-
return createReactiveSubscriptionWithInput((id) => ctx.
|
|
2916
|
+
return createReactiveSubscriptionWithInput((id) => ctx.documentService.readArchivedChange(id))(input.id);
|
|
2598
2917
|
}),
|
|
2599
2918
|
subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2600
2919
|
return createReactiveSubscriptionWithInput((id) => ctx.adapter.readArchivedChangeFiles(id))(input.id);
|
|
@@ -2882,6 +3201,12 @@ const cliRouter = router({
|
|
|
2882
3201
|
* OPSX router - CLI-driven workflow data
|
|
2883
3202
|
*/
|
|
2884
3203
|
const opsxRouter = router({
|
|
3204
|
+
runWorkflow: publicProcedure.input(z.object({
|
|
3205
|
+
requestedMode: workflowRequestedModeSchema,
|
|
3206
|
+
input: runWorkflowInputSchema
|
|
3207
|
+
})).mutation(async ({ ctx, input }) => {
|
|
3208
|
+
return ctx.workflowInvocationService.runWorkflow(input.input, input.requestedMode);
|
|
3209
|
+
}),
|
|
2885
3210
|
status: publicProcedure.input(z.object({
|
|
2886
3211
|
change: z.string().optional(),
|
|
2887
3212
|
schema: z.string().optional()
|
|
@@ -3350,11 +3675,11 @@ const appRouter = router({
|
|
|
3350
3675
|
function joinParts(parts) {
|
|
3351
3676
|
return parts.map((part) => part?.trim() ?? "").filter((part) => part.length > 0).join("\n\n");
|
|
3352
3677
|
}
|
|
3353
|
-
async function collectSearchDocuments(adapter) {
|
|
3678
|
+
async function collectSearchDocuments(adapter, documentService) {
|
|
3354
3679
|
const docs = [];
|
|
3355
3680
|
const specs = await adapter.listSpecsWithMeta();
|
|
3356
3681
|
for (const spec of specs) {
|
|
3357
|
-
const raw = await adapter.readSpecRaw(spec.id);
|
|
3682
|
+
const raw = documentService ? await documentService.readSpecRaw(spec.id, "search", "processed") : await adapter.readSpecRaw(spec.id);
|
|
3358
3683
|
if (!raw) continue;
|
|
3359
3684
|
docs.push({
|
|
3360
3685
|
id: `spec:${spec.id}`,
|
|
@@ -3362,13 +3687,13 @@ async function collectSearchDocuments(adapter) {
|
|
|
3362
3687
|
title: spec.name,
|
|
3363
3688
|
href: `/specs/${encodeURIComponent(spec.id)}`,
|
|
3364
3689
|
path: `openspec/specs/${spec.id}/spec.md`,
|
|
3365
|
-
content: raw,
|
|
3690
|
+
content: typeof raw === "string" ? raw : raw.markdown,
|
|
3366
3691
|
updatedAt: spec.updatedAt
|
|
3367
3692
|
});
|
|
3368
3693
|
}
|
|
3369
3694
|
const changes = await adapter.listChangesWithMeta();
|
|
3370
3695
|
for (const change of changes) {
|
|
3371
|
-
const raw = await adapter.readChangeRaw(change.id);
|
|
3696
|
+
const raw = documentService ? await documentService.readChangeRaw(change.id, "search", "processed") : await adapter.readChangeRaw(change.id);
|
|
3372
3697
|
if (!raw) continue;
|
|
3373
3698
|
docs.push({
|
|
3374
3699
|
id: `change:${change.id}`,
|
|
@@ -3377,9 +3702,9 @@ async function collectSearchDocuments(adapter) {
|
|
|
3377
3702
|
href: `/changes/${encodeURIComponent(change.id)}`,
|
|
3378
3703
|
path: `openspec/changes/${change.id}`,
|
|
3379
3704
|
content: joinParts([
|
|
3380
|
-
raw.proposal,
|
|
3381
|
-
raw.tasks,
|
|
3382
|
-
raw.design,
|
|
3705
|
+
typeof raw.proposal === "string" ? raw.proposal : raw.proposal.markdown,
|
|
3706
|
+
typeof raw.tasks === "string" ? raw.tasks : raw.tasks.markdown,
|
|
3707
|
+
typeof raw.design === "string" ? raw.design : raw.design?.markdown,
|
|
3383
3708
|
...raw.deltaSpecs.map((deltaSpec) => deltaSpec.content)
|
|
3384
3709
|
]),
|
|
3385
3710
|
updatedAt: change.updatedAt
|
|
@@ -3387,7 +3712,7 @@ async function collectSearchDocuments(adapter) {
|
|
|
3387
3712
|
}
|
|
3388
3713
|
const archives = await adapter.listArchivedChangesWithMeta();
|
|
3389
3714
|
for (const archive of archives) {
|
|
3390
|
-
const raw = await adapter.readArchivedChangeRaw(archive.id);
|
|
3715
|
+
const raw = documentService ? await documentService.readArchivedChangeRaw(archive.id, "search", "processed") : await adapter.readArchivedChangeRaw(archive.id);
|
|
3391
3716
|
if (!raw) continue;
|
|
3392
3717
|
docs.push({
|
|
3393
3718
|
id: `archive:${archive.id}`,
|
|
@@ -3396,9 +3721,9 @@ async function collectSearchDocuments(adapter) {
|
|
|
3396
3721
|
href: `/archive/${encodeURIComponent(archive.id)}`,
|
|
3397
3722
|
path: `openspec/changes/archive/${archive.id}`,
|
|
3398
3723
|
content: joinParts([
|
|
3399
|
-
raw.proposal,
|
|
3400
|
-
raw.tasks,
|
|
3401
|
-
raw.design,
|
|
3724
|
+
typeof raw.proposal === "string" ? raw.proposal : raw.proposal.markdown,
|
|
3725
|
+
typeof raw.tasks === "string" ? raw.tasks : raw.tasks.markdown,
|
|
3726
|
+
typeof raw.design === "string" ? raw.design : raw.design?.markdown,
|
|
3402
3727
|
...raw.deltaSpecs.map((deltaSpec) => deltaSpec.content)
|
|
3403
3728
|
]),
|
|
3404
3729
|
updatedAt: archive.updatedAt
|
|
@@ -3416,8 +3741,9 @@ var SearchService = class {
|
|
|
3416
3741
|
initPromise = null;
|
|
3417
3742
|
rebuildPromise = null;
|
|
3418
3743
|
rebuildTimer = null;
|
|
3419
|
-
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider()) {
|
|
3744
|
+
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider(), documentService) {
|
|
3420
3745
|
this.adapter = adapter;
|
|
3746
|
+
this.documentService = documentService;
|
|
3421
3747
|
this.provider = provider;
|
|
3422
3748
|
watcher?.on("change", () => {
|
|
3423
3749
|
this.scheduleRebuild();
|
|
@@ -3463,7 +3789,7 @@ var SearchService = class {
|
|
|
3463
3789
|
if (!forceInit && !this.initialized) return;
|
|
3464
3790
|
if (this.rebuildPromise) return this.rebuildPromise;
|
|
3465
3791
|
this.rebuildPromise = (async () => {
|
|
3466
|
-
const docs = await collectSearchDocuments(this.adapter);
|
|
3792
|
+
const docs = await collectSearchDocuments(this.adapter, this.documentService);
|
|
3467
3793
|
if (this.initialized) await this.provider.replaceAll(docs);
|
|
3468
3794
|
else {
|
|
3469
3795
|
await this.provider.init(docs);
|
|
@@ -3478,6 +3804,218 @@ var SearchService = class {
|
|
|
3478
3804
|
}
|
|
3479
3805
|
};
|
|
3480
3806
|
|
|
3807
|
+
//#endregion
|
|
3808
|
+
//#region src/workflow-invocation-service.ts
|
|
3809
|
+
const COMMAND_CAPABLE_ACTIONS = new Set([
|
|
3810
|
+
"propose",
|
|
3811
|
+
"apply",
|
|
3812
|
+
"archive"
|
|
3813
|
+
]);
|
|
3814
|
+
const COMMAND_FALLBACK_REASONS = {
|
|
3815
|
+
continue: "Continue uses the selected artifact context, so compose mode is required.",
|
|
3816
|
+
ff: "Fast-forward from a change page uses the selected ready artifact, so compose mode is required."
|
|
3817
|
+
};
|
|
3818
|
+
function toErrorDiagnostic(error) {
|
|
3819
|
+
return {
|
|
3820
|
+
level: "error",
|
|
3821
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
function withDiagnostics(result, diagnostics) {
|
|
3825
|
+
return {
|
|
3826
|
+
...result,
|
|
3827
|
+
diagnostics: [...result.diagnostics ?? [], ...diagnostics]
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
function resolveInvocationMode(action, requestedMode) {
|
|
3831
|
+
if (requestedMode !== "command" || COMMAND_CAPABLE_ACTIONS.has(action)) return {
|
|
3832
|
+
requestedMode,
|
|
3833
|
+
actualMode: requestedMode,
|
|
3834
|
+
fallbackReason: null
|
|
3835
|
+
};
|
|
3836
|
+
return {
|
|
3837
|
+
requestedMode,
|
|
3838
|
+
actualMode: "compose",
|
|
3839
|
+
fallbackReason: COMMAND_FALLBACK_REASONS[action] ?? "This action requires compose mode."
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
function buildProposeComposePrompt(text) {
|
|
3843
|
+
const normalized = text.trim();
|
|
3844
|
+
if (normalized.length === 0) return ["Propose a new OpenSpec change.", "Ask me what to build before creating files if the request is unclear."].join("\n");
|
|
3845
|
+
return [
|
|
3846
|
+
`Propose a new OpenSpec change for: ${normalized}`,
|
|
3847
|
+
"",
|
|
3848
|
+
"Use the OpenSpec propose workflow. If an openspec-propose skill is available, follow it. Otherwise derive a kebab-case change name, run `openspec new change \"<name>\"`, inspect `openspec status --change \"<name>\" --json`, and create every apply-required artifact using `openspec instructions <artifact-id> --change \"<name>\" --json`."
|
|
3849
|
+
].join("\n");
|
|
3850
|
+
}
|
|
3851
|
+
function buildSlashCommand(input) {
|
|
3852
|
+
switch (input.action) {
|
|
3853
|
+
case "propose": {
|
|
3854
|
+
const normalized = input.text.trim();
|
|
3855
|
+
if (normalized.length === 0) return "/opsx:propose";
|
|
3856
|
+
if (normalized.startsWith("/opsx:")) return normalized;
|
|
3857
|
+
return `/opsx:propose ${normalized}`;
|
|
3858
|
+
}
|
|
3859
|
+
case "apply":
|
|
3860
|
+
case "archive": return `/opsx:${input.action} ${input.changeId.trim()}`;
|
|
3861
|
+
default: return null;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
async function captureCliText(execute, args, fallback) {
|
|
3865
|
+
const result = await execute(args);
|
|
3866
|
+
const text = result.stdout.trim().length > 0 ? result.stdout.trim() : fallback;
|
|
3867
|
+
if (result.success) return { text };
|
|
3868
|
+
return {
|
|
3869
|
+
text,
|
|
3870
|
+
diagnostics: [{
|
|
3871
|
+
level: "warning",
|
|
3872
|
+
message: result.stderr || `openspec command exited with code ${result.exitCode ?? "null"}`
|
|
3873
|
+
}]
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
function buildFallbackPrompt(input) {
|
|
3877
|
+
switch (input.action) {
|
|
3878
|
+
case "continue": return `Continue artifact ${input.artifactId} for change ${input.changeId}.`;
|
|
3879
|
+
case "ff": return `Fast-forward artifact ${input.artifactId} for change ${input.changeId}.`;
|
|
3880
|
+
case "apply": return `Apply change ${input.changeId} based on current completed artifacts.`;
|
|
3881
|
+
case "archive": return `Archive change ${input.changeId} after verifying completion and risks.`;
|
|
3882
|
+
case "sync": return `Sync specs for change ${input.changeId}.`;
|
|
3883
|
+
case "verify": return `Verify change ${input.changeId}.`;
|
|
3884
|
+
case "bulk-archive": return `Archive completed changes${input.changeIds?.length ? `: ${input.changeIds.join(", ")}` : ""}.`;
|
|
3885
|
+
case "explore":
|
|
3886
|
+
case "propose": return buildProposeComposePrompt(input.text);
|
|
3887
|
+
case "new": return `Create OpenSpec change ${input.changeId}.`;
|
|
3888
|
+
case "onboard": return "Start OpenSpec onboarding for this project.";
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
function buildArchivePrompt(changeId, statusText) {
|
|
3892
|
+
const normalized = statusText.trim();
|
|
3893
|
+
return [
|
|
3894
|
+
`Archive planning for change "${changeId}".`,
|
|
3895
|
+
"",
|
|
3896
|
+
"Current openspec status:",
|
|
3897
|
+
"```text",
|
|
3898
|
+
normalized.length > 0 ? normalized : "(no status output)",
|
|
3899
|
+
"```",
|
|
3900
|
+
"",
|
|
3901
|
+
"Please confirm archive readiness, highlight risks, and provide the exact next steps."
|
|
3902
|
+
].join("\n");
|
|
3903
|
+
}
|
|
3904
|
+
var WorkflowInvocationService = class {
|
|
3905
|
+
constructor(options) {
|
|
3906
|
+
this.options = options;
|
|
3907
|
+
}
|
|
3908
|
+
async runWorkflow(input, requestedMode, signal = new AbortController().signal) {
|
|
3909
|
+
const mode = resolveInvocationMode(input.action, requestedMode);
|
|
3910
|
+
const run = () => this.runDefault(input, mode);
|
|
3911
|
+
const hooks = await this.options.hookRuntime.load();
|
|
3912
|
+
if (!hooks.onRunWorkflow) return run();
|
|
3913
|
+
try {
|
|
3914
|
+
return await hooks.onRunWorkflow({
|
|
3915
|
+
version: OPENSPECUI_HOOKS_VERSION,
|
|
3916
|
+
projectDir: this.options.projectDir,
|
|
3917
|
+
action: input.action,
|
|
3918
|
+
requestedMode,
|
|
3919
|
+
input,
|
|
3920
|
+
signal,
|
|
3921
|
+
lifecycle: this.options.hookRuntime
|
|
3922
|
+
}, run);
|
|
3923
|
+
} catch (error) {
|
|
3924
|
+
return withDiagnostics(await run(), [toErrorDiagnostic(error)]);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
async runDefault(input, mode) {
|
|
3928
|
+
if (mode.actualMode === "command") {
|
|
3929
|
+
const text = buildSlashCommand(input);
|
|
3930
|
+
if (text) return {
|
|
3931
|
+
kind: "agent-command",
|
|
3932
|
+
text,
|
|
3933
|
+
mode
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
if (input.action === "new") {
|
|
3937
|
+
const args = [
|
|
3938
|
+
"new",
|
|
3939
|
+
"change",
|
|
3940
|
+
input.changeId.trim()
|
|
3941
|
+
];
|
|
3942
|
+
const schema = input.schema?.trim();
|
|
3943
|
+
const description = input.description?.trim();
|
|
3944
|
+
if (schema) args.push("--schema", schema);
|
|
3945
|
+
if (description) args.push("--description", description);
|
|
3946
|
+
args.push(...input.extraArgs.map((arg) => arg.trim()).filter((arg) => arg.length > 0));
|
|
3947
|
+
return {
|
|
3948
|
+
kind: "cli-command",
|
|
3949
|
+
command: "openspec",
|
|
3950
|
+
args,
|
|
3951
|
+
mode
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
if (input.action === "verify") {
|
|
3955
|
+
const args = [
|
|
3956
|
+
"validate",
|
|
3957
|
+
input.changeId,
|
|
3958
|
+
"--type",
|
|
3959
|
+
"change"
|
|
3960
|
+
];
|
|
3961
|
+
if (input.strict) args.push("--strict");
|
|
3962
|
+
return {
|
|
3963
|
+
kind: "cli-command",
|
|
3964
|
+
command: "openspec",
|
|
3965
|
+
args,
|
|
3966
|
+
mode
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
if (input.action === "propose" || input.action === "explore") return {
|
|
3970
|
+
kind: "agent-prompt",
|
|
3971
|
+
text: buildProposeComposePrompt(input.text),
|
|
3972
|
+
format: "markdown",
|
|
3973
|
+
mode
|
|
3974
|
+
};
|
|
3975
|
+
const executeCli = this.options.executeCli;
|
|
3976
|
+
if (executeCli && (input.action === "continue" || input.action === "ff" || input.action === "apply" || input.action === "archive")) {
|
|
3977
|
+
if ((input.action === "continue" || input.action === "ff") && !input.artifactId.trim()) return {
|
|
3978
|
+
kind: "agent-prompt",
|
|
3979
|
+
text: buildFallbackPrompt(input),
|
|
3980
|
+
format: "markdown",
|
|
3981
|
+
mode,
|
|
3982
|
+
diagnostics: [{
|
|
3983
|
+
level: "warning",
|
|
3984
|
+
message: "Artifact id is required for this action."
|
|
3985
|
+
}]
|
|
3986
|
+
};
|
|
3987
|
+
const captured = await captureCliText(executeCli, input.action === "continue" || input.action === "ff" ? [
|
|
3988
|
+
"instructions",
|
|
3989
|
+
input.artifactId,
|
|
3990
|
+
"--change",
|
|
3991
|
+
input.changeId
|
|
3992
|
+
] : input.action === "apply" ? [
|
|
3993
|
+
"instructions",
|
|
3994
|
+
"apply",
|
|
3995
|
+
"--change",
|
|
3996
|
+
input.changeId
|
|
3997
|
+
] : [
|
|
3998
|
+
"status",
|
|
3999
|
+
"--change",
|
|
4000
|
+
input.changeId
|
|
4001
|
+
], buildFallbackPrompt(input));
|
|
4002
|
+
return {
|
|
4003
|
+
kind: "agent-prompt",
|
|
4004
|
+
text: input.action === "archive" ? buildArchivePrompt(input.changeId, captured.text) : captured.text,
|
|
4005
|
+
format: "markdown",
|
|
4006
|
+
mode,
|
|
4007
|
+
diagnostics: captured.diagnostics
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
return {
|
|
4011
|
+
kind: "agent-prompt",
|
|
4012
|
+
text: buildFallbackPrompt(input),
|
|
4013
|
+
format: "markdown",
|
|
4014
|
+
mode
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
};
|
|
4018
|
+
|
|
3481
4019
|
//#endregion
|
|
3482
4020
|
//#region src/server.ts
|
|
3483
4021
|
/**
|
|
@@ -3514,8 +4052,15 @@ function createServer(config) {
|
|
|
3514
4052
|
const configManager = new ConfigManager(config.projectDir);
|
|
3515
4053
|
const cliExecutor = new CliExecutor(configManager, config.projectDir);
|
|
3516
4054
|
const kernel = config.kernel;
|
|
4055
|
+
const hookRuntime = createHookRuntime(config.projectDir);
|
|
4056
|
+
const documentService = new DocumentService(config.projectDir, adapter, hookRuntime);
|
|
4057
|
+
const workflowInvocationService = new WorkflowInvocationService({
|
|
4058
|
+
projectDir: config.projectDir,
|
|
4059
|
+
hookRuntime,
|
|
4060
|
+
executeCli: (args) => cliExecutor.execute(args)
|
|
4061
|
+
});
|
|
3517
4062
|
const watcher = config.enableWatcher !== false ? new OpenSpecWatcher(config.projectDir) : void 0;
|
|
3518
|
-
const searchService = new SearchService(adapter, watcher);
|
|
4063
|
+
const searchService = new SearchService(adapter, watcher, void 0, documentService);
|
|
3519
4064
|
const dashboardOverviewService = new DashboardOverviewService((reason) => loadDashboardOverview({
|
|
3520
4065
|
adapter,
|
|
3521
4066
|
configManager,
|
|
@@ -3550,8 +4095,10 @@ function createServer(config) {
|
|
|
3550
4095
|
createContext: () => ({
|
|
3551
4096
|
adapter,
|
|
3552
4097
|
configManager,
|
|
4098
|
+
documentService,
|
|
3553
4099
|
cliExecutor,
|
|
3554
4100
|
kernel,
|
|
4101
|
+
workflowInvocationService,
|
|
3555
4102
|
searchService,
|
|
3556
4103
|
dashboardOverviewService,
|
|
3557
4104
|
projectRecoveryService,
|
|
@@ -3564,8 +4111,10 @@ function createServer(config) {
|
|
|
3564
4111
|
const createContext = () => ({
|
|
3565
4112
|
adapter,
|
|
3566
4113
|
configManager,
|
|
4114
|
+
documentService,
|
|
3567
4115
|
cliExecutor,
|
|
3568
4116
|
kernel,
|
|
4117
|
+
workflowInvocationService,
|
|
3569
4118
|
searchService,
|
|
3570
4119
|
dashboardOverviewService,
|
|
3571
4120
|
projectRecoveryService,
|
|
@@ -3577,11 +4126,14 @@ function createServer(config) {
|
|
|
3577
4126
|
app,
|
|
3578
4127
|
adapter,
|
|
3579
4128
|
configManager,
|
|
4129
|
+
documentService,
|
|
3580
4130
|
cliExecutor,
|
|
3581
4131
|
kernel,
|
|
4132
|
+
workflowInvocationService,
|
|
3582
4133
|
searchService,
|
|
3583
4134
|
dashboardOverviewService,
|
|
3584
4135
|
projectRecoveryService,
|
|
4136
|
+
hookRuntime,
|
|
3585
4137
|
watcher,
|
|
3586
4138
|
createContext,
|
|
3587
4139
|
port: config.port ?? 3100
|
|
@@ -3675,6 +4227,7 @@ async function startServer(config, setupApp) {
|
|
|
3675
4227
|
preferredPort,
|
|
3676
4228
|
close: async () => {
|
|
3677
4229
|
kernel.dispose();
|
|
4230
|
+
await server.hookRuntime.dispose();
|
|
3678
4231
|
wsServer.close();
|
|
3679
4232
|
httpServer.close();
|
|
3680
4233
|
}
|
|
@@ -3682,4 +4235,4 @@ async function startServer(config, setupApp) {
|
|
|
3682
4235
|
}
|
|
3683
4236
|
|
|
3684
4237
|
//#endregion
|
|
3685
|
-
export { createServer, createWebSocketServer, findAvailablePort, isPortAvailable, startServer };
|
|
4238
|
+
export { DocumentService, OPENSPECUI_HOOKS_RELATIVE_PATH, ProjectHookRuntime, WorkflowInvocationService, createHookRuntime, createServer, createWebSocketServer, findAvailablePort, isPortAvailable, startServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"@openspecui/search": "workspace:*",
|
|
26
26
|
"@trpc/server": "^11.0.0",
|
|
27
27
|
"hono": "^4.7.3",
|
|
28
|
+
"tsx": "^4.19.2",
|
|
28
29
|
"ws": "^8.18.0",
|
|
29
30
|
"yaml": "^2.8.0",
|
|
30
31
|
"yargs": "^18.0.0",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"@types/ws": "^8.5.13",
|
|
36
37
|
"@types/yargs": "^17.0.35",
|
|
37
38
|
"tsdown": "^0.16.6",
|
|
38
|
-
"tsx": "^4.19.2",
|
|
39
39
|
"typescript": "^5.7.2",
|
|
40
40
|
"vitest": "^4.1.0"
|
|
41
41
|
},
|